| @@ -2,7 +2,8 @@ class RepositoriesController < ApplicationController | |||
| include ApplicationHelper | |||
| include OperateProjectAbilityAble | |||
| before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] | |||
| before_action :find_project, except: [:tags, :commit, :sync_mirror] | |||
| before_action :find_project_with_includes, only: :show | |||
| before_action :find_project, except: [:tags, :commit, :sync_mirror, :show] | |||
| before_action :authorizate!, except: [:sync_mirror, :tags, :commit] | |||
| before_action :find_repository_by_id, only: %i[commit sync_mirror tags] | |||
| before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] | |||
| @@ -10,6 +11,7 @@ class RepositoriesController < ApplicationController | |||
| before_action :get_latest_commit, :get_ref, only: %i[entries sub_entries] | |||
| def show | |||
| @user = current_user | |||
| @branches_count = Gitea::Repository::Branches::ListService.new(@project.owner, @project.identifier).call&.size | |||
| @commits_count = Gitea::Repository::Commits::ListService.new(@project.owner.login, @project.identifier, | |||
| sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call[:total_count] | |||
| @@ -27,8 +29,8 @@ class RepositoriesController < ApplicationController | |||
| def entries | |||
| @project.increment!(:visits) | |||
| @entries = Gitea::Repository::Entries::ListService.new(@project.owner, @project.identifier, ref: @ref).call | |||
| @project_owner = @project.owner | |||
| @entries = Gitea::Repository::Entries::ListService.new(@project_owner, @project.identifier, ref: @ref).call | |||
| @entries = @entries.sort_by{ |hash| hash['type'] } | |||
| end | |||
| @@ -124,6 +126,10 @@ class RepositoriesController < ApplicationController | |||
| render_not_found("未找到相关的仓库") unless @project | |||
| end | |||
| def find_project_with_includes | |||
| @project = Project.includes(:repository, :owner, :watchers, :praise_treads).find params[:id] | |||
| end | |||
| def authorizate! | |||
| if @project.repository.hidden? && !@project.member?(current_user) | |||
| render_forbidden | |||
| @@ -23,13 +23,6 @@ module ProjectsHelper | |||
| Gitea.gitea_config[:domain] | |||
| end | |||
| def render_edit_project_permission(user, project) | |||
| permission = "Reporter" | |||
| member = project.members.includes(:roles).find_by(user: user) | |||
| member&.roles&.last&.name || permission | |||
| end | |||
| def find_user_by_login_or_mail(identifier) | |||
| (User.find_by_login identifier) || (User.find_by_mail identifier) | |||
| end | |||
| @@ -1,7 +1,7 @@ | |||
| module RepositoriesHelper | |||
| def render_decode64_content(str) | |||
| return nil if str.blank? | |||
| Base64.decode64(str) | |||
| Base64.decode64(str).force_encoding('UTF-8') | |||
| end | |||
| def download_type(str) | |||
| @@ -143,4 +143,19 @@ class Project < ApplicationRecord | |||
| self.class.name.constantize.project_types["#{self.project_type}"] | |||
| end | |||
| def watched_by? user | |||
| watchers.pluck(:user_id).include? user&.id | |||
| end | |||
| def praised_by? user | |||
| praise_treads.pluck(:user_id).include? user&.id | |||
| end | |||
| def get_premission user | |||
| permission = "Reporter" | |||
| member = members.find_by(user: user) | |||
| member&.roles&.last&.name || permission | |||
| end | |||
| end | |||
| @@ -18,11 +18,11 @@ class Repositories::CreateService < ApplicationService | |||
| chain_params = { | |||
| type: "create", | |||
| ownername: user.try(:login), | |||
| ownername: user.try(:login), | |||
| reponame: @repository.try(:id) | |||
| } | |||
| ProjectCreateChainJob.perform_later(chain_params) #创建上链操作 | |||
| # ProjectCreateChainJob.perform_later(chain_params) #创建上链操作 | |||
| end | |||
| end | |||
| @repository | |||
| @@ -14,7 +14,14 @@ json.entries do | |||
| json.sha entry['sha'] | |||
| json.type entry['type'] | |||
| json.size entry['size'] | |||
| json.content entry['content'] | |||
| content = | |||
| if entry['name'] === 'README.md' | |||
| content = Gitea::Repository::Entries::GetService.call(@project_owner, @project.identifier, entry['name'], ref: @ref)['content'] | |||
| render_decode64_content content | |||
| else | |||
| entry['content'] | |||
| end | |||
| json.content content | |||
| json.target entry['target'] | |||
| if entry['latest_commit'] | |||
| json.partial! 'last_commit', entry: entry | |||
| @@ -9,19 +9,19 @@ json.praises_count @project.praises_count.to_i | |||
| json.forked_count @project.forked_count.to_i | |||
| json.watchers_count @project.watchers_count.to_i | |||
| json.versions_count @project.versions_count #里程碑数量 | |||
| json.version_releases_count @project.releases_size(current_user.try(:id), "all") | |||
| json.version_releasesed_count @project.releases_size(current_user.try(:id), "released") #已发行的版本 | |||
| json.version_releases_count @project.releases_size(@user.try(:id), "all") | |||
| json.version_releasesed_count @project.releases_size(@user.try(:id), "released") #已发行的版本 | |||
| json.contributor_users_count @project.contributor_users | |||
| json.issue_tags_count @tags_count | |||
| json.branches_count @branches_count | |||
| json.commits_count @commits_count | |||
| json.permission render_edit_project_permission(current_user, @project) if current_user | |||
| json.permission @project.get_premission(@user) | |||
| json.mirror_url @project&.repository.mirror_url | |||
| json.mirror @project&.repository.mirror_url.present? | |||
| json.type @project.numerical_for_project_type | |||
| json.mirror_status @project.repository&.mirror&.numerical_for_status if @project.sync_mirror? | |||
| json.watched current_user&.watched?(@project) | |||
| json.praised current_user&.liked?(@project) | |||
| json.watched @project.watched_by? @user | |||
| json.praised @project.praised_by? @user | |||
| json.status @project.status | |||
| json.forked_from_project_id @project_fork_id | |||
| json.fork_info do | |||
| @@ -1,952 +0,0 @@ | |||
| /*! | |||
| * Quill Editor v1.3.6 | |||
| * https://quilljs.com/ | |||
| * Copyright (c) 2014, Jason Chen | |||
| * Copyright (c) 2013, salesforce.com | |||
| */ | |||
| .ql-container { | |||
| box-sizing: border-box; | |||
| font-family: Helvetica, Arial, sans-serif; | |||
| font-size: 13px; | |||
| height: 100%; | |||
| margin: 0px; | |||
| position: relative; | |||
| } | |||
| .ql-container.ql-disabled .ql-tooltip { | |||
| visibility: hidden; | |||
| } | |||
| .ql-container.ql-disabled .ql-editor ul[data-checked] > li::before { | |||
| pointer-events: none; | |||
| } | |||
| .ql-clipboard { | |||
| left: -100000px; | |||
| height: 1px; | |||
| overflow-y: hidden; | |||
| position: absolute; | |||
| top: 50%; | |||
| } | |||
| .ql-clipboard p { | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| .ql-editor { | |||
| box-sizing: border-box; | |||
| line-height: 1.42; | |||
| height: 100%; | |||
| outline: none; | |||
| overflow-y: auto; | |||
| padding: 12px 15px; | |||
| tab-size: 4; | |||
| -moz-tab-size: 4; | |||
| text-align: left; | |||
| white-space: pre-wrap; | |||
| word-wrap: break-word; | |||
| } | |||
| .ql-editor > * { | |||
| cursor: text; | |||
| } | |||
| .ql-editor p, | |||
| .ql-editor ol, | |||
| .ql-editor ul, | |||
| .ql-editor pre, | |||
| .ql-editor blockquote, | |||
| .ql-editor h1, | |||
| .ql-editor h2, | |||
| .ql-editor h3, | |||
| .ql-editor h4, | |||
| .ql-editor h5, | |||
| .ql-editor h6 { | |||
| margin: 0; | |||
| padding: 0; | |||
| counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol, | |||
| .ql-editor ul { | |||
| padding-left: 1.5em; | |||
| } | |||
| .ql-editor ol > li, | |||
| .ql-editor ul > li { | |||
| list-style-type: none; | |||
| } | |||
| .ql-editor ul > li::before { | |||
| content: '\2022'; | |||
| } | |||
| .ql-editor ul[data-checked=true], | |||
| .ql-editor ul[data-checked=false] { | |||
| pointer-events: none; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li *, | |||
| .ql-editor ul[data-checked=false] > li * { | |||
| pointer-events: all; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li::before, | |||
| .ql-editor ul[data-checked=false] > li::before { | |||
| color: #777; | |||
| cursor: pointer; | |||
| pointer-events: all; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li::before { | |||
| content: '\2611'; | |||
| } | |||
| .ql-editor ul[data-checked=false] > li::before { | |||
| content: '\2610'; | |||
| } | |||
| .ql-editor li::before { | |||
| display: inline-block; | |||
| white-space: nowrap; | |||
| width: 1.2em; | |||
| } | |||
| .ql-editor li:not(.ql-direction-rtl)::before { | |||
| margin-left: -1.5em; | |||
| margin-right: 0.3em; | |||
| text-align: right; | |||
| } | |||
| .ql-editor li.ql-direction-rtl::before { | |||
| margin-left: 0.3em; | |||
| margin-right: -1.5em; | |||
| } | |||
| .ql-editor ol li:not(.ql-direction-rtl), | |||
| .ql-editor ul li:not(.ql-direction-rtl) { | |||
| padding-left: 1.5em; | |||
| } | |||
| .ql-editor ol li.ql-direction-rtl, | |||
| .ql-editor ul li.ql-direction-rtl { | |||
| padding-right: 1.5em; | |||
| } | |||
| .ql-editor ol li { | |||
| counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| counter-increment: list-0; | |||
| } | |||
| .ql-editor ol li:before { | |||
| content: counter(list-0, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-1 { | |||
| counter-increment: list-1; | |||
| } | |||
| .ql-editor ol li.ql-indent-1:before { | |||
| content: counter(list-1, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-1 { | |||
| counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-2 { | |||
| counter-increment: list-2; | |||
| } | |||
| .ql-editor ol li.ql-indent-2:before { | |||
| content: counter(list-2, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-2 { | |||
| counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-3 { | |||
| counter-increment: list-3; | |||
| } | |||
| .ql-editor ol li.ql-indent-3:before { | |||
| content: counter(list-3, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-3 { | |||
| counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-4 { | |||
| counter-increment: list-4; | |||
| } | |||
| .ql-editor ol li.ql-indent-4:before { | |||
| content: counter(list-4, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-4 { | |||
| counter-reset: list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-5 { | |||
| counter-increment: list-5; | |||
| } | |||
| .ql-editor ol li.ql-indent-5:before { | |||
| content: counter(list-5, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-5 { | |||
| counter-reset: list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-6 { | |||
| counter-increment: list-6; | |||
| } | |||
| .ql-editor ol li.ql-indent-6:before { | |||
| content: counter(list-6, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-6 { | |||
| counter-reset: list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-7 { | |||
| counter-increment: list-7; | |||
| } | |||
| .ql-editor ol li.ql-indent-7:before { | |||
| content: counter(list-7, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-7 { | |||
| counter-reset: list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-8 { | |||
| counter-increment: list-8; | |||
| } | |||
| .ql-editor ol li.ql-indent-8:before { | |||
| content: counter(list-8, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-8 { | |||
| counter-reset: list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-9 { | |||
| counter-increment: list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-9:before { | |||
| content: counter(list-9, decimal) '. '; | |||
| } | |||
| .ql-editor .ql-indent-1:not(.ql-direction-rtl) { | |||
| padding-left: 3em; | |||
| } | |||
| .ql-editor li.ql-indent-1:not(.ql-direction-rtl) { | |||
| padding-left: 4.5em; | |||
| } | |||
| .ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { | |||
| padding-right: 3em; | |||
| } | |||
| .ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { | |||
| padding-right: 4.5em; | |||
| } | |||
| .ql-editor .ql-indent-2:not(.ql-direction-rtl) { | |||
| padding-left: 6em; | |||
| } | |||
| .ql-editor li.ql-indent-2:not(.ql-direction-rtl) { | |||
| padding-left: 7.5em; | |||
| } | |||
| .ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { | |||
| padding-right: 6em; | |||
| } | |||
| .ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { | |||
| padding-right: 7.5em; | |||
| } | |||
| .ql-editor .ql-indent-3:not(.ql-direction-rtl) { | |||
| padding-left: 9em; | |||
| } | |||
| .ql-editor li.ql-indent-3:not(.ql-direction-rtl) { | |||
| padding-left: 10.5em; | |||
| } | |||
| .ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { | |||
| padding-right: 9em; | |||
| } | |||
| .ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { | |||
| padding-right: 10.5em; | |||
| } | |||
| .ql-editor .ql-indent-4:not(.ql-direction-rtl) { | |||
| padding-left: 12em; | |||
| } | |||
| .ql-editor li.ql-indent-4:not(.ql-direction-rtl) { | |||
| padding-left: 13.5em; | |||
| } | |||
| .ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { | |||
| padding-right: 12em; | |||
| } | |||
| .ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { | |||
| padding-right: 13.5em; | |||
| } | |||
| .ql-editor .ql-indent-5:not(.ql-direction-rtl) { | |||
| padding-left: 15em; | |||
| } | |||
| .ql-editor li.ql-indent-5:not(.ql-direction-rtl) { | |||
| padding-left: 16.5em; | |||
| } | |||
| .ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { | |||
| padding-right: 15em; | |||
| } | |||
| .ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { | |||
| padding-right: 16.5em; | |||
| } | |||
| .ql-editor .ql-indent-6:not(.ql-direction-rtl) { | |||
| padding-left: 18em; | |||
| } | |||
| .ql-editor li.ql-indent-6:not(.ql-direction-rtl) { | |||
| padding-left: 19.5em; | |||
| } | |||
| .ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { | |||
| padding-right: 18em; | |||
| } | |||
| .ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { | |||
| padding-right: 19.5em; | |||
| } | |||
| .ql-editor .ql-indent-7:not(.ql-direction-rtl) { | |||
| padding-left: 21em; | |||
| } | |||
| .ql-editor li.ql-indent-7:not(.ql-direction-rtl) { | |||
| padding-left: 22.5em; | |||
| } | |||
| .ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { | |||
| padding-right: 21em; | |||
| } | |||
| .ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { | |||
| padding-right: 22.5em; | |||
| } | |||
| .ql-editor .ql-indent-8:not(.ql-direction-rtl) { | |||
| padding-left: 24em; | |||
| } | |||
| .ql-editor li.ql-indent-8:not(.ql-direction-rtl) { | |||
| padding-left: 25.5em; | |||
| } | |||
| .ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { | |||
| padding-right: 24em; | |||
| } | |||
| .ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { | |||
| padding-right: 25.5em; | |||
| } | |||
| .ql-editor .ql-indent-9:not(.ql-direction-rtl) { | |||
| padding-left: 27em; | |||
| } | |||
| .ql-editor li.ql-indent-9:not(.ql-direction-rtl) { | |||
| padding-left: 28.5em; | |||
| } | |||
| .ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { | |||
| padding-right: 27em; | |||
| } | |||
| .ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { | |||
| padding-right: 28.5em; | |||
| } | |||
| .ql-editor .ql-video { | |||
| display: block; | |||
| max-width: 100%; | |||
| } | |||
| .ql-editor .ql-video.ql-align-center { | |||
| margin: 0 auto; | |||
| } | |||
| .ql-editor .ql-video.ql-align-right { | |||
| margin: 0 0 0 auto; | |||
| } | |||
| .ql-editor .ql-bg-black { | |||
| background-color: #000; | |||
| } | |||
| .ql-editor .ql-bg-red { | |||
| background-color: #e60000; | |||
| } | |||
| .ql-editor .ql-bg-orange { | |||
| background-color: #f90; | |||
| } | |||
| .ql-editor .ql-bg-yellow { | |||
| background-color: #ff0; | |||
| } | |||
| .ql-editor .ql-bg-green { | |||
| background-color: #008a00; | |||
| } | |||
| .ql-editor .ql-bg-blue { | |||
| background-color: #06c; | |||
| } | |||
| .ql-editor .ql-bg-purple { | |||
| background-color: #93f; | |||
| } | |||
| .ql-editor .ql-color-white { | |||
| color: #fff; | |||
| } | |||
| .ql-editor .ql-color-red { | |||
| color: #e60000; | |||
| } | |||
| .ql-editor .ql-color-orange { | |||
| color: #f90; | |||
| } | |||
| .ql-editor .ql-color-yellow { | |||
| color: #ff0; | |||
| } | |||
| .ql-editor .ql-color-green { | |||
| color: #008a00; | |||
| } | |||
| .ql-editor .ql-color-blue { | |||
| color: #06c; | |||
| } | |||
| .ql-editor .ql-color-purple { | |||
| color: #93f; | |||
| } | |||
| .ql-editor .ql-font-serif { | |||
| font-family: Georgia, Times New Roman, serif; | |||
| } | |||
| .ql-editor .ql-font-monospace { | |||
| font-family: Monaco, Courier New, monospace; | |||
| } | |||
| .ql-editor .ql-size-small { | |||
| font-size: 0.75em; | |||
| } | |||
| .ql-editor .ql-size-large { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-editor .ql-size-huge { | |||
| font-size: 2.5em; | |||
| } | |||
| .ql-editor .ql-direction-rtl { | |||
| direction: rtl; | |||
| text-align: inherit; | |||
| } | |||
| .ql-editor .ql-align-center { | |||
| text-align: center; | |||
| } | |||
| .ql-editor .ql-align-justify { | |||
| text-align: justify; | |||
| } | |||
| .ql-editor .ql-align-right { | |||
| text-align: right; | |||
| } | |||
| .ql-editor.ql-blank::before { | |||
| color: rgba(0,0,0,0.6); | |||
| content: attr(data-placeholder); | |||
| font-style: italic; | |||
| left: 15px; | |||
| pointer-events: none; | |||
| position: absolute; | |||
| right: 15px; | |||
| } | |||
| .ql-bubble.ql-toolbar:after, | |||
| .ql-bubble .ql-toolbar:after { | |||
| clear: both; | |||
| content: ''; | |||
| display: table; | |||
| } | |||
| .ql-bubble.ql-toolbar button, | |||
| .ql-bubble .ql-toolbar button { | |||
| background: none; | |||
| border: none; | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| float: left; | |||
| height: 24px; | |||
| padding: 3px 5px; | |||
| width: 28px; | |||
| } | |||
| .ql-bubble.ql-toolbar button svg, | |||
| .ql-bubble .ql-toolbar button svg { | |||
| float: left; | |||
| height: 100%; | |||
| } | |||
| .ql-bubble.ql-toolbar button:active:hover, | |||
| .ql-bubble .ql-toolbar button:active:hover { | |||
| outline: none; | |||
| } | |||
| .ql-bubble.ql-toolbar input.ql-image[type=file], | |||
| .ql-bubble .ql-toolbar input.ql-image[type=file] { | |||
| display: none; | |||
| } | |||
| .ql-bubble.ql-toolbar button:hover, | |||
| .ql-bubble .ql-toolbar button:hover, | |||
| .ql-bubble.ql-toolbar button:focus, | |||
| .ql-bubble .ql-toolbar button:focus, | |||
| .ql-bubble.ql-toolbar button.ql-active, | |||
| .ql-bubble .ql-toolbar button.ql-active, | |||
| .ql-bubble.ql-toolbar .ql-picker-label:hover, | |||
| .ql-bubble .ql-toolbar .ql-picker-label:hover, | |||
| .ql-bubble.ql-toolbar .ql-picker-label.ql-active, | |||
| .ql-bubble .ql-toolbar .ql-picker-label.ql-active, | |||
| .ql-bubble.ql-toolbar .ql-picker-item:hover, | |||
| .ql-bubble .ql-toolbar .ql-picker-item:hover, | |||
| .ql-bubble.ql-toolbar .ql-picker-item.ql-selected, | |||
| .ql-bubble .ql-toolbar .ql-picker-item.ql-selected { | |||
| color: #fff; | |||
| } | |||
| .ql-bubble.ql-toolbar button:hover .ql-fill, | |||
| .ql-bubble .ql-toolbar button:hover .ql-fill, | |||
| .ql-bubble.ql-toolbar button:focus .ql-fill, | |||
| .ql-bubble .ql-toolbar button:focus .ql-fill, | |||
| .ql-bubble.ql-toolbar button.ql-active .ql-fill, | |||
| .ql-bubble .ql-toolbar button.ql-active .ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-fill, | |||
| .ql-bubble.ql-toolbar button:hover .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar button:hover .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar button:focus .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar button:focus .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar button.ql-active .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar button.ql-active .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, | |||
| .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { | |||
| fill: #fff; | |||
| } | |||
| .ql-bubble.ql-toolbar button:hover .ql-stroke, | |||
| .ql-bubble .ql-toolbar button:hover .ql-stroke, | |||
| .ql-bubble.ql-toolbar button:focus .ql-stroke, | |||
| .ql-bubble .ql-toolbar button:focus .ql-stroke, | |||
| .ql-bubble.ql-toolbar button.ql-active .ql-stroke, | |||
| .ql-bubble .ql-toolbar button.ql-active .ql-stroke, | |||
| .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke, | |||
| .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke, | |||
| .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke, | |||
| .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke, | |||
| .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke, | |||
| .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke, | |||
| .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, | |||
| .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, | |||
| .ql-bubble.ql-toolbar button:hover .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar button:hover .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar button:focus .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar button:focus .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar button.ql-active .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar button.ql-active .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, | |||
| .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { | |||
| stroke: #fff; | |||
| } | |||
| @media (pointer: coarse) { | |||
| .ql-bubble.ql-toolbar button:hover:not(.ql-active), | |||
| .ql-bubble .ql-toolbar button:hover:not(.ql-active) { | |||
| color: #ccc; | |||
| } | |||
| .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-fill, | |||
| .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-fill, | |||
| .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill, | |||
| .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill { | |||
| fill: #ccc; | |||
| } | |||
| .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke, | |||
| .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke, | |||
| .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter, | |||
| .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter { | |||
| stroke: #ccc; | |||
| } | |||
| } | |||
| .ql-bubble { | |||
| box-sizing: border-box; | |||
| } | |||
| .ql-bubble * { | |||
| box-sizing: border-box; | |||
| } | |||
| .ql-bubble .ql-hidden { | |||
| display: none; | |||
| } | |||
| .ql-bubble .ql-out-bottom, | |||
| .ql-bubble .ql-out-top { | |||
| visibility: hidden; | |||
| } | |||
| .ql-bubble .ql-tooltip { | |||
| position: absolute; | |||
| transform: translateY(10px); | |||
| } | |||
| .ql-bubble .ql-tooltip a { | |||
| cursor: pointer; | |||
| text-decoration: none; | |||
| } | |||
| .ql-bubble .ql-tooltip.ql-flip { | |||
| transform: translateY(-10px); | |||
| } | |||
| .ql-bubble .ql-formats { | |||
| display: inline-block; | |||
| vertical-align: middle; | |||
| } | |||
| .ql-bubble .ql-formats:after { | |||
| clear: both; | |||
| content: ''; | |||
| display: table; | |||
| } | |||
| .ql-bubble .ql-stroke { | |||
| fill: none; | |||
| stroke: #ccc; | |||
| stroke-linecap: round; | |||
| stroke-linejoin: round; | |||
| stroke-width: 2; | |||
| } | |||
| .ql-bubble .ql-stroke-miter { | |||
| fill: none; | |||
| stroke: #ccc; | |||
| stroke-miterlimit: 10; | |||
| stroke-width: 2; | |||
| } | |||
| .ql-bubble .ql-fill, | |||
| .ql-bubble .ql-stroke.ql-fill { | |||
| fill: #ccc; | |||
| } | |||
| .ql-bubble .ql-empty { | |||
| fill: none; | |||
| } | |||
| .ql-bubble .ql-even { | |||
| fill-rule: evenodd; | |||
| } | |||
| .ql-bubble .ql-thin, | |||
| .ql-bubble .ql-stroke.ql-thin { | |||
| stroke-width: 1; | |||
| } | |||
| .ql-bubble .ql-transparent { | |||
| opacity: 0.4; | |||
| } | |||
| .ql-bubble .ql-direction svg:last-child { | |||
| display: none; | |||
| } | |||
| .ql-bubble .ql-direction.ql-active svg:last-child { | |||
| display: inline; | |||
| } | |||
| .ql-bubble .ql-direction.ql-active svg:first-child { | |||
| display: none; | |||
| } | |||
| .ql-bubble .ql-editor h1 { | |||
| font-size: 2em; | |||
| } | |||
| .ql-bubble .ql-editor h2 { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-bubble .ql-editor h3 { | |||
| font-size: 1.17em; | |||
| } | |||
| .ql-bubble .ql-editor h4 { | |||
| font-size: 1em; | |||
| } | |||
| .ql-bubble .ql-editor h5 { | |||
| font-size: 0.83em; | |||
| } | |||
| .ql-bubble .ql-editor h6 { | |||
| font-size: 0.67em; | |||
| } | |||
| .ql-bubble .ql-editor a { | |||
| text-decoration: underline; | |||
| } | |||
| .ql-bubble .ql-editor blockquote { | |||
| border-left: 4px solid #ccc; | |||
| margin-bottom: 5px; | |||
| margin-top: 5px; | |||
| padding-left: 16px; | |||
| } | |||
| .ql-bubble .ql-editor code, | |||
| .ql-bubble .ql-editor pre { | |||
| background-color: #f0f0f0; | |||
| border-radius: 3px; | |||
| } | |||
| .ql-bubble .ql-editor pre { | |||
| white-space: pre-wrap; | |||
| margin-bottom: 5px; | |||
| margin-top: 5px; | |||
| padding: 5px 10px; | |||
| } | |||
| .ql-bubble .ql-editor code { | |||
| font-size: 85%; | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-bubble .ql-editor pre.ql-syntax { | |||
| background-color: #23241f; | |||
| color: #f8f8f2; | |||
| overflow: visible; | |||
| } | |||
| .ql-bubble .ql-editor img { | |||
| max-width: 100%; | |||
| } | |||
| .ql-bubble .ql-picker { | |||
| color: #ccc; | |||
| display: inline-block; | |||
| float: left; | |||
| font-size: 14px; | |||
| font-weight: 500; | |||
| height: 24px; | |||
| position: relative; | |||
| vertical-align: middle; | |||
| } | |||
| .ql-bubble .ql-picker-label { | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| height: 100%; | |||
| padding-left: 8px; | |||
| padding-right: 2px; | |||
| position: relative; | |||
| width: 100%; | |||
| } | |||
| .ql-bubble .ql-picker-label::before { | |||
| display: inline-block; | |||
| line-height: 22px; | |||
| } | |||
| .ql-bubble .ql-picker-options { | |||
| background-color: #444; | |||
| display: none; | |||
| min-width: 100%; | |||
| padding: 4px 8px; | |||
| position: absolute; | |||
| white-space: nowrap; | |||
| } | |||
| .ql-bubble .ql-picker-options .ql-picker-item { | |||
| cursor: pointer; | |||
| display: block; | |||
| padding-bottom: 5px; | |||
| padding-top: 5px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-expanded .ql-picker-label { | |||
| color: #777; | |||
| z-index: 2; | |||
| } | |||
| .ql-bubble .ql-picker.ql-expanded .ql-picker-label .ql-fill { | |||
| fill: #777; | |||
| } | |||
| .ql-bubble .ql-picker.ql-expanded .ql-picker-label .ql-stroke { | |||
| stroke: #777; | |||
| } | |||
| .ql-bubble .ql-picker.ql-expanded .ql-picker-options { | |||
| display: block; | |||
| margin-top: -1px; | |||
| top: 100%; | |||
| z-index: 1; | |||
| } | |||
| .ql-bubble .ql-color-picker, | |||
| .ql-bubble .ql-icon-picker { | |||
| width: 28px; | |||
| } | |||
| .ql-bubble .ql-color-picker .ql-picker-label, | |||
| .ql-bubble .ql-icon-picker .ql-picker-label { | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-bubble .ql-color-picker .ql-picker-label svg, | |||
| .ql-bubble .ql-icon-picker .ql-picker-label svg { | |||
| right: 4px; | |||
| } | |||
| .ql-bubble .ql-icon-picker .ql-picker-options { | |||
| padding: 4px 0px; | |||
| } | |||
| .ql-bubble .ql-icon-picker .ql-picker-item { | |||
| height: 24px; | |||
| width: 24px; | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-bubble .ql-color-picker .ql-picker-options { | |||
| padding: 3px 5px; | |||
| width: 152px; | |||
| } | |||
| .ql-bubble .ql-color-picker .ql-picker-item { | |||
| border: 1px solid transparent; | |||
| float: left; | |||
| height: 16px; | |||
| margin: 2px; | |||
| padding: 0px; | |||
| width: 16px; | |||
| } | |||
| .ql-bubble .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { | |||
| position: absolute; | |||
| margin-top: -9px; | |||
| right: 0; | |||
| top: 50%; | |||
| width: 18px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before, | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before { | |||
| content: attr(data-label); | |||
| } | |||
| .ql-bubble .ql-picker.ql-header { | |||
| width: 98px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item::before { | |||
| content: 'Normal'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { | |||
| content: 'Heading 1'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { | |||
| content: 'Heading 2'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { | |||
| content: 'Heading 3'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { | |||
| content: 'Heading 4'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { | |||
| content: 'Heading 5'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { | |||
| content: 'Heading 6'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { | |||
| font-size: 2em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { | |||
| font-size: 1.17em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { | |||
| font-size: 1em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { | |||
| font-size: 0.83em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { | |||
| font-size: 0.67em; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font { | |||
| width: 108px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-label::before, | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item::before { | |||
| content: 'Sans Serif'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-label[data-value=serif]::before, | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { | |||
| content: 'Serif'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before, | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { | |||
| content: 'Monospace'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { | |||
| font-family: Georgia, Times New Roman, serif; | |||
| } | |||
| .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { | |||
| font-family: Monaco, Courier New, monospace; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size { | |||
| width: 98px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-label::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item::before { | |||
| content: 'Normal'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=small]::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=small]::before { | |||
| content: 'Small'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=large]::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=large]::before { | |||
| content: 'Large'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=huge]::before, | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { | |||
| content: 'Huge'; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=small]::before { | |||
| font-size: 10px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=large]::before { | |||
| font-size: 18px; | |||
| } | |||
| .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { | |||
| font-size: 32px; | |||
| } | |||
| .ql-bubble .ql-color-picker.ql-background .ql-picker-item { | |||
| background-color: #fff; | |||
| } | |||
| .ql-bubble .ql-color-picker.ql-color .ql-picker-item { | |||
| background-color: #000; | |||
| } | |||
| .ql-bubble .ql-toolbar .ql-formats { | |||
| margin: 8px 12px 8px 0px; | |||
| } | |||
| .ql-bubble .ql-toolbar .ql-formats:first-child { | |||
| margin-left: 12px; | |||
| } | |||
| .ql-bubble .ql-color-picker svg { | |||
| margin: 1px; | |||
| } | |||
| .ql-bubble .ql-color-picker .ql-picker-item.ql-selected, | |||
| .ql-bubble .ql-color-picker .ql-picker-item:hover { | |||
| border-color: #fff; | |||
| } | |||
| .ql-bubble .ql-tooltip { | |||
| background-color: #444; | |||
| border-radius: 25px; | |||
| color: #fff; | |||
| } | |||
| .ql-bubble .ql-tooltip-arrow { | |||
| border-left: 6px solid transparent; | |||
| border-right: 6px solid transparent; | |||
| content: " "; | |||
| display: block; | |||
| left: 50%; | |||
| margin-left: -6px; | |||
| position: absolute; | |||
| } | |||
| .ql-bubble .ql-tooltip:not(.ql-flip) .ql-tooltip-arrow { | |||
| border-bottom: 6px solid #444; | |||
| top: -6px; | |||
| } | |||
| .ql-bubble .ql-tooltip.ql-flip .ql-tooltip-arrow { | |||
| border-top: 6px solid #444; | |||
| bottom: -6px; | |||
| } | |||
| .ql-bubble .ql-tooltip.ql-editing .ql-tooltip-editor { | |||
| display: block; | |||
| } | |||
| .ql-bubble .ql-tooltip.ql-editing .ql-formats { | |||
| visibility: hidden; | |||
| } | |||
| .ql-bubble .ql-tooltip-editor { | |||
| display: none; | |||
| } | |||
| .ql-bubble .ql-tooltip-editor input[type=text] { | |||
| background: transparent; | |||
| border: none; | |||
| color: #fff; | |||
| font-size: 13px; | |||
| height: 100%; | |||
| outline: none; | |||
| padding: 10px 20px; | |||
| position: absolute; | |||
| width: 100%; | |||
| } | |||
| .ql-bubble .ql-tooltip-editor a { | |||
| top: 10px; | |||
| position: absolute; | |||
| right: 20px; | |||
| } | |||
| .ql-bubble .ql-tooltip-editor a:before { | |||
| color: #ccc; | |||
| content: "\D7"; | |||
| font-size: 16px; | |||
| font-weight: bold; | |||
| } | |||
| .ql-container.ql-bubble:not(.ql-disabled) a { | |||
| position: relative; | |||
| white-space: nowrap; | |||
| } | |||
| .ql-container.ql-bubble:not(.ql-disabled) a::before { | |||
| background-color: #444; | |||
| border-radius: 15px; | |||
| top: -5px; | |||
| font-size: 12px; | |||
| color: #fff; | |||
| content: attr(href); | |||
| font-weight: normal; | |||
| overflow: hidden; | |||
| padding: 5px 15px; | |||
| text-decoration: none; | |||
| z-index: 1; | |||
| } | |||
| .ql-container.ql-bubble:not(.ql-disabled) a::after { | |||
| border-top: 6px solid #444; | |||
| border-left: 6px solid transparent; | |||
| border-right: 6px solid transparent; | |||
| top: 0; | |||
| content: " "; | |||
| height: 0; | |||
| width: 0; | |||
| } | |||
| .ql-container.ql-bubble:not(.ql-disabled) a::before, | |||
| .ql-container.ql-bubble:not(.ql-disabled) a::after { | |||
| left: 0; | |||
| margin-left: 50%; | |||
| position: absolute; | |||
| transform: translate(-50%, -100%); | |||
| transition: visibility 0s ease 200ms; | |||
| visibility: hidden; | |||
| } | |||
| .ql-container.ql-bubble:not(.ql-disabled) a:hover::before, | |||
| .ql-container.ql-bubble:not(.ql-disabled) a:hover::after { | |||
| visibility: visible; | |||
| } | |||
| @@ -1,945 +0,0 @@ | |||
| /*! | |||
| * Quill Editor v1.3.6 | |||
| * https://quilljs.com/ | |||
| * Copyright (c) 2014, Jason Chen | |||
| * Copyright (c) 2013, salesforce.com | |||
| */ | |||
| .ql-container { | |||
| box-sizing: border-box; | |||
| font-family: Helvetica, Arial, sans-serif; | |||
| font-size: 13px; | |||
| height: 100%; | |||
| margin: 0px; | |||
| position: relative; | |||
| } | |||
| .ql-container.ql-disabled .ql-tooltip { | |||
| visibility: hidden; | |||
| } | |||
| .ql-container.ql-disabled .ql-editor ul[data-checked] > li::before { | |||
| pointer-events: none; | |||
| } | |||
| .ql-clipboard { | |||
| left: -100000px; | |||
| height: 1px; | |||
| overflow-y: hidden; | |||
| position: absolute; | |||
| top: 50%; | |||
| } | |||
| .ql-clipboard p { | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| .ql-editor { | |||
| box-sizing: border-box; | |||
| line-height: 1.42; | |||
| height: 100%; | |||
| outline: none; | |||
| overflow-y: auto; | |||
| padding: 12px 15px; | |||
| tab-size: 4; | |||
| -moz-tab-size: 4; | |||
| text-align: left; | |||
| white-space: pre-wrap; | |||
| word-wrap: break-word; | |||
| } | |||
| .ql-editor > * { | |||
| cursor: text; | |||
| } | |||
| .ql-editor p, | |||
| .ql-editor ol, | |||
| .ql-editor ul, | |||
| .ql-editor pre, | |||
| .ql-editor blockquote, | |||
| .ql-editor h1, | |||
| .ql-editor h2, | |||
| .ql-editor h3, | |||
| .ql-editor h4, | |||
| .ql-editor h5, | |||
| .ql-editor h6 { | |||
| margin: 0; | |||
| padding: 0; | |||
| counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol, | |||
| .ql-editor ul { | |||
| padding-left: 1.5em; | |||
| } | |||
| .ql-editor ol > li, | |||
| .ql-editor ul > li { | |||
| list-style-type: none; | |||
| } | |||
| .ql-editor ul > li::before { | |||
| content: '\2022'; | |||
| } | |||
| .ql-editor ul[data-checked=true], | |||
| .ql-editor ul[data-checked=false] { | |||
| pointer-events: none; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li *, | |||
| .ql-editor ul[data-checked=false] > li * { | |||
| pointer-events: all; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li::before, | |||
| .ql-editor ul[data-checked=false] > li::before { | |||
| color: #777; | |||
| cursor: pointer; | |||
| pointer-events: all; | |||
| } | |||
| .ql-editor ul[data-checked=true] > li::before { | |||
| content: '\2611'; | |||
| } | |||
| .ql-editor ul[data-checked=false] > li::before { | |||
| content: '\2610'; | |||
| } | |||
| .ql-editor li::before { | |||
| display: inline-block; | |||
| white-space: nowrap; | |||
| width: 1.2em; | |||
| } | |||
| .ql-editor li:not(.ql-direction-rtl)::before { | |||
| margin-left: -1.5em; | |||
| margin-right: 0.3em; | |||
| text-align: right; | |||
| } | |||
| .ql-editor li.ql-direction-rtl::before { | |||
| margin-left: 0.3em; | |||
| margin-right: -1.5em; | |||
| } | |||
| .ql-editor ol li:not(.ql-direction-rtl), | |||
| .ql-editor ul li:not(.ql-direction-rtl) { | |||
| padding-left: 1.5em; | |||
| } | |||
| .ql-editor ol li.ql-direction-rtl, | |||
| .ql-editor ul li.ql-direction-rtl { | |||
| padding-right: 1.5em; | |||
| } | |||
| .ql-editor ol li { | |||
| counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| counter-increment: list-0; | |||
| } | |||
| .ql-editor ol li:before { | |||
| content: counter(list-0, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-1 { | |||
| counter-increment: list-1; | |||
| } | |||
| .ql-editor ol li.ql-indent-1:before { | |||
| content: counter(list-1, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-1 { | |||
| counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-2 { | |||
| counter-increment: list-2; | |||
| } | |||
| .ql-editor ol li.ql-indent-2:before { | |||
| content: counter(list-2, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-2 { | |||
| counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-3 { | |||
| counter-increment: list-3; | |||
| } | |||
| .ql-editor ol li.ql-indent-3:before { | |||
| content: counter(list-3, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-3 { | |||
| counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-4 { | |||
| counter-increment: list-4; | |||
| } | |||
| .ql-editor ol li.ql-indent-4:before { | |||
| content: counter(list-4, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-4 { | |||
| counter-reset: list-5 list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-5 { | |||
| counter-increment: list-5; | |||
| } | |||
| .ql-editor ol li.ql-indent-5:before { | |||
| content: counter(list-5, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-5 { | |||
| counter-reset: list-6 list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-6 { | |||
| counter-increment: list-6; | |||
| } | |||
| .ql-editor ol li.ql-indent-6:before { | |||
| content: counter(list-6, decimal) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-6 { | |||
| counter-reset: list-7 list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-7 { | |||
| counter-increment: list-7; | |||
| } | |||
| .ql-editor ol li.ql-indent-7:before { | |||
| content: counter(list-7, lower-alpha) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-7 { | |||
| counter-reset: list-8 list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-8 { | |||
| counter-increment: list-8; | |||
| } | |||
| .ql-editor ol li.ql-indent-8:before { | |||
| content: counter(list-8, lower-roman) '. '; | |||
| } | |||
| .ql-editor ol li.ql-indent-8 { | |||
| counter-reset: list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-9 { | |||
| counter-increment: list-9; | |||
| } | |||
| .ql-editor ol li.ql-indent-9:before { | |||
| content: counter(list-9, decimal) '. '; | |||
| } | |||
| .ql-editor .ql-indent-1:not(.ql-direction-rtl) { | |||
| padding-left: 3em; | |||
| } | |||
| .ql-editor li.ql-indent-1:not(.ql-direction-rtl) { | |||
| padding-left: 4.5em; | |||
| } | |||
| .ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { | |||
| padding-right: 3em; | |||
| } | |||
| .ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { | |||
| padding-right: 4.5em; | |||
| } | |||
| .ql-editor .ql-indent-2:not(.ql-direction-rtl) { | |||
| padding-left: 6em; | |||
| } | |||
| .ql-editor li.ql-indent-2:not(.ql-direction-rtl) { | |||
| padding-left: 7.5em; | |||
| } | |||
| .ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { | |||
| padding-right: 6em; | |||
| } | |||
| .ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { | |||
| padding-right: 7.5em; | |||
| } | |||
| .ql-editor .ql-indent-3:not(.ql-direction-rtl) { | |||
| padding-left: 9em; | |||
| } | |||
| .ql-editor li.ql-indent-3:not(.ql-direction-rtl) { | |||
| padding-left: 10.5em; | |||
| } | |||
| .ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { | |||
| padding-right: 9em; | |||
| } | |||
| .ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { | |||
| padding-right: 10.5em; | |||
| } | |||
| .ql-editor .ql-indent-4:not(.ql-direction-rtl) { | |||
| padding-left: 12em; | |||
| } | |||
| .ql-editor li.ql-indent-4:not(.ql-direction-rtl) { | |||
| padding-left: 13.5em; | |||
| } | |||
| .ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { | |||
| padding-right: 12em; | |||
| } | |||
| .ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { | |||
| padding-right: 13.5em; | |||
| } | |||
| .ql-editor .ql-indent-5:not(.ql-direction-rtl) { | |||
| padding-left: 15em; | |||
| } | |||
| .ql-editor li.ql-indent-5:not(.ql-direction-rtl) { | |||
| padding-left: 16.5em; | |||
| } | |||
| .ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { | |||
| padding-right: 15em; | |||
| } | |||
| .ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { | |||
| padding-right: 16.5em; | |||
| } | |||
| .ql-editor .ql-indent-6:not(.ql-direction-rtl) { | |||
| padding-left: 18em; | |||
| } | |||
| .ql-editor li.ql-indent-6:not(.ql-direction-rtl) { | |||
| padding-left: 19.5em; | |||
| } | |||
| .ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { | |||
| padding-right: 18em; | |||
| } | |||
| .ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { | |||
| padding-right: 19.5em; | |||
| } | |||
| .ql-editor .ql-indent-7:not(.ql-direction-rtl) { | |||
| padding-left: 21em; | |||
| } | |||
| .ql-editor li.ql-indent-7:not(.ql-direction-rtl) { | |||
| padding-left: 22.5em; | |||
| } | |||
| .ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { | |||
| padding-right: 21em; | |||
| } | |||
| .ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { | |||
| padding-right: 22.5em; | |||
| } | |||
| .ql-editor .ql-indent-8:not(.ql-direction-rtl) { | |||
| padding-left: 24em; | |||
| } | |||
| .ql-editor li.ql-indent-8:not(.ql-direction-rtl) { | |||
| padding-left: 25.5em; | |||
| } | |||
| .ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { | |||
| padding-right: 24em; | |||
| } | |||
| .ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { | |||
| padding-right: 25.5em; | |||
| } | |||
| .ql-editor .ql-indent-9:not(.ql-direction-rtl) { | |||
| padding-left: 27em; | |||
| } | |||
| .ql-editor li.ql-indent-9:not(.ql-direction-rtl) { | |||
| padding-left: 28.5em; | |||
| } | |||
| .ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { | |||
| padding-right: 27em; | |||
| } | |||
| .ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { | |||
| padding-right: 28.5em; | |||
| } | |||
| .ql-editor .ql-video { | |||
| display: block; | |||
| max-width: 100%; | |||
| } | |||
| .ql-editor .ql-video.ql-align-center { | |||
| margin: 0 auto; | |||
| } | |||
| .ql-editor .ql-video.ql-align-right { | |||
| margin: 0 0 0 auto; | |||
| } | |||
| .ql-editor .ql-bg-black { | |||
| background-color: #000; | |||
| } | |||
| .ql-editor .ql-bg-red { | |||
| background-color: #e60000; | |||
| } | |||
| .ql-editor .ql-bg-orange { | |||
| background-color: #f90; | |||
| } | |||
| .ql-editor .ql-bg-yellow { | |||
| background-color: #ff0; | |||
| } | |||
| .ql-editor .ql-bg-green { | |||
| background-color: #008a00; | |||
| } | |||
| .ql-editor .ql-bg-blue { | |||
| background-color: #06c; | |||
| } | |||
| .ql-editor .ql-bg-purple { | |||
| background-color: #93f; | |||
| } | |||
| .ql-editor .ql-color-white { | |||
| color: #fff; | |||
| } | |||
| .ql-editor .ql-color-red { | |||
| color: #e60000; | |||
| } | |||
| .ql-editor .ql-color-orange { | |||
| color: #f90; | |||
| } | |||
| .ql-editor .ql-color-yellow { | |||
| color: #ff0; | |||
| } | |||
| .ql-editor .ql-color-green { | |||
| color: #008a00; | |||
| } | |||
| .ql-editor .ql-color-blue { | |||
| color: #06c; | |||
| } | |||
| .ql-editor .ql-color-purple { | |||
| color: #93f; | |||
| } | |||
| .ql-editor .ql-font-serif { | |||
| font-family: Georgia, Times New Roman, serif; | |||
| } | |||
| .ql-editor .ql-font-monospace { | |||
| font-family: Monaco, Courier New, monospace; | |||
| } | |||
| .ql-editor .ql-size-small { | |||
| font-size: 0.75em; | |||
| } | |||
| .ql-editor .ql-size-large { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-editor .ql-size-huge { | |||
| font-size: 2.5em; | |||
| } | |||
| .ql-editor .ql-direction-rtl { | |||
| direction: rtl; | |||
| text-align: inherit; | |||
| } | |||
| .ql-editor .ql-align-center { | |||
| text-align: center; | |||
| } | |||
| .ql-editor .ql-align-justify { | |||
| text-align: justify; | |||
| } | |||
| .ql-editor .ql-align-right { | |||
| text-align: right; | |||
| } | |||
| .ql-editor.ql-blank::before { | |||
| color: rgba(0,0,0,0.6); | |||
| content: attr(data-placeholder); | |||
| font-style: italic; | |||
| left: 15px; | |||
| pointer-events: none; | |||
| position: absolute; | |||
| right: 15px; | |||
| } | |||
| .ql-snow.ql-toolbar:after, | |||
| .ql-snow .ql-toolbar:after { | |||
| clear: both; | |||
| content: ''; | |||
| display: table; | |||
| } | |||
| .ql-snow.ql-toolbar button, | |||
| .ql-snow .ql-toolbar button { | |||
| background: none; | |||
| border: none; | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| float: left; | |||
| height: 24px; | |||
| padding: 3px 5px; | |||
| width: 28px; | |||
| } | |||
| .ql-snow.ql-toolbar button svg, | |||
| .ql-snow .ql-toolbar button svg { | |||
| float: left; | |||
| height: 100%; | |||
| } | |||
| .ql-snow.ql-toolbar button:active:hover, | |||
| .ql-snow .ql-toolbar button:active:hover { | |||
| outline: none; | |||
| } | |||
| .ql-snow.ql-toolbar input.ql-image[type=file], | |||
| .ql-snow .ql-toolbar input.ql-image[type=file] { | |||
| display: none; | |||
| } | |||
| .ql-snow.ql-toolbar button:hover, | |||
| .ql-snow .ql-toolbar button:hover, | |||
| .ql-snow.ql-toolbar button:focus, | |||
| .ql-snow .ql-toolbar button:focus, | |||
| .ql-snow.ql-toolbar button.ql-active, | |||
| .ql-snow .ql-toolbar button.ql-active, | |||
| .ql-snow.ql-toolbar .ql-picker-label:hover, | |||
| .ql-snow .ql-toolbar .ql-picker-label:hover, | |||
| .ql-snow.ql-toolbar .ql-picker-label.ql-active, | |||
| .ql-snow .ql-toolbar .ql-picker-label.ql-active, | |||
| .ql-snow.ql-toolbar .ql-picker-item:hover, | |||
| .ql-snow .ql-toolbar .ql-picker-item:hover, | |||
| .ql-snow.ql-toolbar .ql-picker-item.ql-selected, | |||
| .ql-snow .ql-toolbar .ql-picker-item.ql-selected { | |||
| color: #06c; | |||
| } | |||
| .ql-snow.ql-toolbar button:hover .ql-fill, | |||
| .ql-snow .ql-toolbar button:hover .ql-fill, | |||
| .ql-snow.ql-toolbar button:focus .ql-fill, | |||
| .ql-snow .ql-toolbar button:focus .ql-fill, | |||
| .ql-snow.ql-toolbar button.ql-active .ql-fill, | |||
| .ql-snow .ql-toolbar button.ql-active .ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill, | |||
| .ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, | |||
| .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { | |||
| fill: #06c; | |||
| } | |||
| .ql-snow.ql-toolbar button:hover .ql-stroke, | |||
| .ql-snow .ql-toolbar button:hover .ql-stroke, | |||
| .ql-snow.ql-toolbar button:focus .ql-stroke, | |||
| .ql-snow .ql-toolbar button:focus .ql-stroke, | |||
| .ql-snow.ql-toolbar button.ql-active .ql-stroke, | |||
| .ql-snow .ql-toolbar button.ql-active .ql-stroke, | |||
| .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, | |||
| .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, | |||
| .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, | |||
| .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, | |||
| .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, | |||
| .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, | |||
| .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, | |||
| .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, | |||
| .ql-snow.ql-toolbar button:hover .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar button:hover .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar button:focus .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar button:focus .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, | |||
| .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { | |||
| stroke: #06c; | |||
| } | |||
| @media (pointer: coarse) { | |||
| .ql-snow.ql-toolbar button:hover:not(.ql-active), | |||
| .ql-snow .ql-toolbar button:hover:not(.ql-active) { | |||
| color: #444; | |||
| } | |||
| .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill, | |||
| .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill, | |||
| .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill, | |||
| .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill { | |||
| fill: #444; | |||
| } | |||
| .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke, | |||
| .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke, | |||
| .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter, | |||
| .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter { | |||
| stroke: #444; | |||
| } | |||
| } | |||
| .ql-snow { | |||
| box-sizing: border-box; | |||
| } | |||
| .ql-snow * { | |||
| box-sizing: border-box; | |||
| } | |||
| .ql-snow .ql-hidden { | |||
| display: none; | |||
| } | |||
| .ql-snow .ql-out-bottom, | |||
| .ql-snow .ql-out-top { | |||
| visibility: hidden; | |||
| } | |||
| .ql-snow .ql-tooltip { | |||
| position: absolute; | |||
| transform: translateY(10px); | |||
| } | |||
| .ql-snow .ql-tooltip a { | |||
| cursor: pointer; | |||
| text-decoration: none; | |||
| } | |||
| .ql-snow .ql-tooltip.ql-flip { | |||
| transform: translateY(-10px); | |||
| } | |||
| .ql-snow .ql-formats { | |||
| display: inline-block; | |||
| vertical-align: middle; | |||
| } | |||
| .ql-snow .ql-formats:after { | |||
| clear: both; | |||
| content: ''; | |||
| display: table; | |||
| } | |||
| .ql-snow .ql-stroke { | |||
| fill: none; | |||
| stroke: #444; | |||
| stroke-linecap: round; | |||
| stroke-linejoin: round; | |||
| stroke-width: 2; | |||
| } | |||
| .ql-snow .ql-stroke-miter { | |||
| fill: none; | |||
| stroke: #444; | |||
| stroke-miterlimit: 10; | |||
| stroke-width: 2; | |||
| } | |||
| .ql-snow .ql-fill, | |||
| .ql-snow .ql-stroke.ql-fill { | |||
| fill: #444; | |||
| } | |||
| .ql-snow .ql-empty { | |||
| fill: none; | |||
| } | |||
| .ql-snow .ql-even { | |||
| fill-rule: evenodd; | |||
| } | |||
| .ql-snow .ql-thin, | |||
| .ql-snow .ql-stroke.ql-thin { | |||
| stroke-width: 1; | |||
| } | |||
| .ql-snow .ql-transparent { | |||
| opacity: 0.4; | |||
| } | |||
| .ql-snow .ql-direction svg:last-child { | |||
| display: none; | |||
| } | |||
| .ql-snow .ql-direction.ql-active svg:last-child { | |||
| display: inline; | |||
| } | |||
| .ql-snow .ql-direction.ql-active svg:first-child { | |||
| display: none; | |||
| } | |||
| .ql-snow .ql-editor h1 { | |||
| font-size: 2em; | |||
| } | |||
| .ql-snow .ql-editor h2 { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-snow .ql-editor h3 { | |||
| font-size: 1.17em; | |||
| } | |||
| .ql-snow .ql-editor h4 { | |||
| font-size: 1em; | |||
| } | |||
| .ql-snow .ql-editor h5 { | |||
| font-size: 0.83em; | |||
| } | |||
| .ql-snow .ql-editor h6 { | |||
| font-size: 0.67em; | |||
| } | |||
| .ql-snow .ql-editor a { | |||
| text-decoration: underline; | |||
| } | |||
| .ql-snow .ql-editor blockquote { | |||
| border-left: 4px solid #ccc; | |||
| margin-bottom: 5px; | |||
| margin-top: 5px; | |||
| padding-left: 16px; | |||
| } | |||
| .ql-snow .ql-editor code, | |||
| .ql-snow .ql-editor pre { | |||
| background-color: #f0f0f0; | |||
| border-radius: 3px; | |||
| } | |||
| .ql-snow .ql-editor pre { | |||
| white-space: pre-wrap; | |||
| margin-bottom: 5px; | |||
| margin-top: 5px; | |||
| padding: 5px 10px; | |||
| } | |||
| .ql-snow .ql-editor code { | |||
| font-size: 85%; | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-snow .ql-editor pre.ql-syntax { | |||
| background-color: #23241f; | |||
| color: #f8f8f2; | |||
| overflow: visible; | |||
| } | |||
| .ql-snow .ql-editor img { | |||
| max-width: 100%; | |||
| } | |||
| .ql-snow .ql-picker { | |||
| color: #444; | |||
| display: inline-block; | |||
| float: left; | |||
| font-size: 14px; | |||
| font-weight: 500; | |||
| height: 24px; | |||
| position: relative; | |||
| vertical-align: middle; | |||
| } | |||
| .ql-snow .ql-picker-label { | |||
| cursor: pointer; | |||
| display: inline-block; | |||
| height: 100%; | |||
| padding-left: 8px; | |||
| padding-right: 2px; | |||
| position: relative; | |||
| width: 100%; | |||
| } | |||
| .ql-snow .ql-picker-label::before { | |||
| display: inline-block; | |||
| line-height: 22px; | |||
| } | |||
| .ql-snow .ql-picker-options { | |||
| background-color: #fff; | |||
| display: none; | |||
| min-width: 100%; | |||
| padding: 4px 8px; | |||
| position: absolute; | |||
| white-space: nowrap; | |||
| } | |||
| .ql-snow .ql-picker-options .ql-picker-item { | |||
| cursor: pointer; | |||
| display: block; | |||
| padding-bottom: 5px; | |||
| padding-top: 5px; | |||
| } | |||
| .ql-snow .ql-picker.ql-expanded .ql-picker-label { | |||
| color: #ccc; | |||
| z-index: 2; | |||
| } | |||
| .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill { | |||
| fill: #ccc; | |||
| } | |||
| .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke { | |||
| stroke: #ccc; | |||
| } | |||
| .ql-snow .ql-picker.ql-expanded .ql-picker-options { | |||
| display: block; | |||
| margin-top: -1px; | |||
| top: 100%; | |||
| z-index: 1; | |||
| } | |||
| .ql-snow .ql-color-picker, | |||
| .ql-snow .ql-icon-picker { | |||
| width: 28px; | |||
| } | |||
| .ql-snow .ql-color-picker .ql-picker-label, | |||
| .ql-snow .ql-icon-picker .ql-picker-label { | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-snow .ql-color-picker .ql-picker-label svg, | |||
| .ql-snow .ql-icon-picker .ql-picker-label svg { | |||
| right: 4px; | |||
| } | |||
| .ql-snow .ql-icon-picker .ql-picker-options { | |||
| padding: 4px 0px; | |||
| } | |||
| .ql-snow .ql-icon-picker .ql-picker-item { | |||
| height: 24px; | |||
| width: 24px; | |||
| padding: 2px 4px; | |||
| } | |||
| .ql-snow .ql-color-picker .ql-picker-options { | |||
| padding: 3px 5px; | |||
| width: 152px; | |||
| } | |||
| .ql-snow .ql-color-picker .ql-picker-item { | |||
| border: 1px solid transparent; | |||
| float: left; | |||
| height: 16px; | |||
| margin: 2px; | |||
| padding: 0px; | |||
| width: 16px; | |||
| } | |||
| .ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { | |||
| position: absolute; | |||
| margin-top: -9px; | |||
| right: 0; | |||
| top: 50%; | |||
| width: 18px; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before { | |||
| content: attr(data-label); | |||
| } | |||
| .ql-snow .ql-picker.ql-header { | |||
| width: 98px; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item::before { | |||
| content: 'Normal'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { | |||
| content: 'Heading 1'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { | |||
| content: 'Heading 2'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { | |||
| content: 'Heading 3'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { | |||
| content: 'Heading 4'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { | |||
| content: 'Heading 5'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { | |||
| content: 'Heading 6'; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { | |||
| font-size: 2em; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { | |||
| font-size: 1.5em; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { | |||
| font-size: 1.17em; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { | |||
| font-size: 1em; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { | |||
| font-size: 0.83em; | |||
| } | |||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { | |||
| font-size: 0.67em; | |||
| } | |||
| .ql-snow .ql-picker.ql-font { | |||
| width: 108px; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item::before { | |||
| content: 'Sans Serif'; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { | |||
| content: 'Serif'; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { | |||
| content: 'Monospace'; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { | |||
| font-family: Georgia, Times New Roman, serif; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { | |||
| font-family: Monaco, Courier New, monospace; | |||
| } | |||
| .ql-snow .ql-picker.ql-size { | |||
| width: 98px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item::before { | |||
| content: 'Normal'; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { | |||
| content: 'Small'; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { | |||
| content: 'Large'; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { | |||
| content: 'Huge'; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { | |||
| font-size: 10px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { | |||
| font-size: 18px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { | |||
| font-size: 32px; | |||
| } | |||
| .ql-snow .ql-color-picker.ql-background .ql-picker-item { | |||
| background-color: #fff; | |||
| } | |||
| .ql-snow .ql-color-picker.ql-color .ql-picker-item { | |||
| background-color: #000; | |||
| } | |||
| .ql-toolbar.ql-snow { | |||
| border: 1px solid #ccc; | |||
| box-sizing: border-box; | |||
| font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; | |||
| padding: 8px; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-formats { | |||
| margin-right: 15px; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-picker-label { | |||
| border: 1px solid transparent; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-picker-options { | |||
| border: 1px solid transparent; | |||
| box-shadow: rgba(0,0,0,0.2) 0 2px 8px; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label { | |||
| border-color: #ccc; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options { | |||
| border-color: #ccc; | |||
| } | |||
| .ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected, | |||
| .ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover { | |||
| border-color: #000; | |||
| } | |||
| .ql-toolbar.ql-snow + .ql-container.ql-snow { | |||
| border-top: 0px; | |||
| } | |||
| .ql-snow .ql-tooltip { | |||
| background-color: #fff; | |||
| border: 1px solid #ccc; | |||
| box-shadow: 0px 0px 5px #ddd; | |||
| color: #444; | |||
| padding: 5px 12px; | |||
| white-space: nowrap; | |||
| } | |||
| .ql-snow .ql-tooltip::before { | |||
| content: "Visit URL:"; | |||
| line-height: 26px; | |||
| margin-right: 8px; | |||
| } | |||
| .ql-snow .ql-tooltip input[type=text] { | |||
| display: none; | |||
| border: 1px solid #ccc; | |||
| font-size: 13px; | |||
| height: 26px; | |||
| margin: 0px; | |||
| padding: 3px 5px; | |||
| width: 170px; | |||
| } | |||
| .ql-snow .ql-tooltip a.ql-preview { | |||
| display: inline-block; | |||
| max-width: 200px; | |||
| overflow-x: hidden; | |||
| text-overflow: ellipsis; | |||
| vertical-align: top; | |||
| } | |||
| .ql-snow .ql-tooltip a.ql-action::after { | |||
| border-right: 1px solid #ccc; | |||
| content: 'Edit'; | |||
| margin-left: 16px; | |||
| padding-right: 8px; | |||
| } | |||
| .ql-snow .ql-tooltip a.ql-remove::before { | |||
| content: 'Remove'; | |||
| margin-left: 8px; | |||
| } | |||
| .ql-snow .ql-tooltip a { | |||
| line-height: 26px; | |||
| } | |||
| .ql-snow .ql-tooltip.ql-editing a.ql-preview, | |||
| .ql-snow .ql-tooltip.ql-editing a.ql-remove { | |||
| display: none; | |||
| } | |||
| .ql-snow .ql-tooltip.ql-editing input[type=text] { | |||
| display: inline-block; | |||
| } | |||
| .ql-snow .ql-tooltip.ql-editing a.ql-action::after { | |||
| border-right: 0px; | |||
| content: 'Save'; | |||
| padding-right: 0px; | |||
| } | |||
| .ql-snow .ql-tooltip[data-mode=link]::before { | |||
| content: "Enter link:"; | |||
| } | |||
| .ql-snow .ql-tooltip[data-mode=formula]::before { | |||
| content: "Enter formula:"; | |||
| } | |||
| .ql-snow .ql-tooltip[data-mode=video]::before { | |||
| content: "Enter video:"; | |||
| } | |||
| .ql-snow a { | |||
| color: #06c; | |||
| } | |||
| .ql-container.ql-snow { | |||
| border: 1px solid #ccc; | |||
| } | |||
| @@ -1,21 +0,0 @@ | |||
| MIT License | |||
| Copyright (c) [year] [fullname] | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| of this software and associated documentation files (the "Software"), to deal | |||
| in the Software without restriction, including without limitation the rights | |||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| copies of the Software, and to permit persons to whom the Software is | |||
| furnished to do so, subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| SOFTWARE. | |||
| @@ -1,34 +0,0 @@ | |||
| 新版tpi改动的文件: | |||
| Index.js | |||
| contex/TPIContextProvider.js | |||
| page/main/LeftViewContainer.js | |||
| taskList/TaskList.js | |||
| TPMIndexHOC.js | |||
| App.js | |||
| CodeRepositoryViewContainer.js | |||
| Index.js | |||
| choose={context.chooses} | |||
| TPIContextProvider.js | |||
| LeftViewContainer.js | |||
| TaskList.js | |||
| TPMIndexHOC.js | |||
| MainContentContainer | |||
| 新:rep_content返回值多了一层 {content: '...'} | |||
| TODO | |||
| 待同步 | |||
| 1、timer图标样式更换 | |||
| index.html | |||
| WebSSHTimer.css | |||
| WebSSHTimer.js | |||
| @@ -0,0 +1 @@ | |||
| Subproject commit 447edde157092e65f05b36bc41c472659c482c6a | |||
| @@ -1,93 +0,0 @@ | |||
| 'use strict'; | |||
| const fs = require('fs'); | |||
| const path = require('path'); | |||
| const paths = require('./paths'); | |||
| // Make sure that including paths.js after env.js will read .env variables. | |||
| delete require.cache[require.resolve('./paths')]; | |||
| const NODE_ENV = process.env.NODE_ENV; | |||
| if (!NODE_ENV) { | |||
| throw new Error( | |||
| 'The NODE_ENV environment variable is required but was not specified.' | |||
| ); | |||
| } | |||
| // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use | |||
| var dotenvFiles = [ | |||
| `${paths.dotenv}.${NODE_ENV}.local`, | |||
| `${paths.dotenv}.${NODE_ENV}`, | |||
| // Don't include `.env.local` for `test` environment | |||
| // since normally you expect tests to produce the same | |||
| // results for everyone | |||
| NODE_ENV !== 'test' && `${paths.dotenv}.local`, | |||
| paths.dotenv, | |||
| ].filter(Boolean); | |||
| // Load environment variables from .env* files. Suppress warnings using silent | |||
| // if this file is missing. dotenv will never modify any environment variables | |||
| // that have already been set. Variable expansion is supported in .env files. | |||
| // https://github.com/motdotla/dotenv | |||
| // https://github.com/motdotla/dotenv-expand | |||
| dotenvFiles.forEach(dotenvFile => { | |||
| if (fs.existsSync(dotenvFile)) { | |||
| require('dotenv-expand')( | |||
| require('dotenv').config({ | |||
| path: dotenvFile, | |||
| }) | |||
| ); | |||
| } | |||
| }); | |||
| // We support resolving modules according to `NODE_PATH`. | |||
| // This lets you use absolute paths in imports inside large monorepos: | |||
| // https://github.com/facebookincubator/create-react-app/issues/253. | |||
| // It works similar to `NODE_PATH` in Node itself: | |||
| // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders | |||
| // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. | |||
| // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. | |||
| // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 | |||
| // We also resolve them to make sure all tools using them work consistently. | |||
| const appDirectory = fs.realpathSync(process.cwd()); | |||
| process.env.NODE_PATH = (process.env.NODE_PATH || '') | |||
| .split(path.delimiter) | |||
| .filter(folder => folder && !path.isAbsolute(folder)) | |||
| .map(folder => path.resolve(appDirectory, folder)) | |||
| .join(path.delimiter); | |||
| // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be | |||
| // injected into the application via DefinePlugin in Webpack configuration. | |||
| const REACT_APP = /^REACT_APP_/i; | |||
| function getClientEnvironment(publicUrl) { | |||
| const raw = Object.keys(process.env) | |||
| .filter(key => REACT_APP.test(key)) | |||
| .reduce( | |||
| (env, key) => { | |||
| env[key] = process.env[key]; | |||
| return env; | |||
| }, | |||
| { | |||
| // Useful for determining whether we’re running in production mode. | |||
| // Most importantly, it switches React into the correct mode. | |||
| NODE_ENV: process.env.NODE_ENV || 'development', | |||
| // Useful for resolving the correct path to static assets in `public`. | |||
| // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. | |||
| // This should only be used as an escape hatch. Normally you would put | |||
| // images into the `src` and `import` them in code to get their paths. | |||
| PUBLIC_URL: '/forgeplus-react/build/.', | |||
| } | |||
| ); | |||
| // Stringify all values so we can feed into Webpack DefinePlugin | |||
| const stringified = { | |||
| 'process.env': Object.keys(raw).reduce((env, key) => { | |||
| env[key] = JSON.stringify(raw[key]); | |||
| return env; | |||
| }, {}), | |||
| }; | |||
| return { raw, stringified }; | |||
| } | |||
| module.exports = getClientEnvironment; | |||
| @@ -1,14 +0,0 @@ | |||
| 'use strict'; | |||
| // This is a custom Jest transformer turning style imports into empty objects. | |||
| // http://facebook.github.io/jest/docs/en/webpack.html | |||
| module.exports = { | |||
| process() { | |||
| return 'module.exports = {};'; | |||
| }, | |||
| getCacheKey() { | |||
| // The output is always the same. | |||
| return 'cssTransform'; | |||
| }, | |||
| }; | |||
| @@ -1,12 +0,0 @@ | |||
| 'use strict'; | |||
| const path = require('path'); | |||
| // This is a custom Jest transformer turning file imports into filenames. | |||
| // http://facebook.github.io/jest/docs/en/webpack.html | |||
| module.exports = { | |||
| process(src, filename) { | |||
| return `module.exports = ${JSON.stringify(path.basename(filename))};`; | |||
| }, | |||
| }; | |||
| @@ -1,55 +0,0 @@ | |||
| 'use strict'; | |||
| const path = require('path'); | |||
| const fs = require('fs'); | |||
| const url = require('url'); | |||
| // Make sure any symlinks in the project folder are resolved: | |||
| // https://github.com/facebookincubator/create-react-app/issues/637 | |||
| const appDirectory = fs.realpathSync(process.cwd()); | |||
| const resolveApp = relativePath => path.resolve(appDirectory, relativePath); | |||
| const envPublicUrl = process.env.PUBLIC_URL; | |||
| function ensureSlash(path, needsSlash) { | |||
| const hasSlash = path.endsWith('/'); | |||
| if (hasSlash && !needsSlash) { | |||
| return path.substr(path, path.length - 1); | |||
| } else if (!hasSlash && needsSlash) { | |||
| return `${path}/`; | |||
| } else { | |||
| return path; | |||
| } | |||
| } | |||
| const getPublicUrl = appPackageJson => | |||
| envPublicUrl || require(appPackageJson).homepage; | |||
| // We use `PUBLIC_URL` environment variable or "homepage" field to infer | |||
| // "public path" at which the app is served. | |||
| // Webpack needs to know it to put the right <script> hrefs into HTML even in | |||
| // single-page apps that may serve index.html for nested URLs like /todos/42. | |||
| // We can't use a relative path in HTML because we don't want to load something | |||
| // like /todos/42/static/js/bundle.7289d.js. We have to know the root. | |||
| function getServedPath(appPackageJson) { | |||
| const publicUrl = getPublicUrl(appPackageJson); | |||
| const servedUrl = | |||
| envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); | |||
| return ensureSlash(servedUrl, true); | |||
| } | |||
| // config after eject: we're in ./config/ | |||
| module.exports = { | |||
| dotenv: resolveApp('.env'), | |||
| appBuild: resolveApp('build'), | |||
| appPublic: resolveApp('public'), | |||
| appHtml: resolveApp('public/index.html'), | |||
| appIndexJs: resolveApp('src/index.js'), | |||
| appPackageJson: resolveApp('package.json'), | |||
| appSrc: resolveApp('src'), | |||
| yarnLockFile: resolveApp('yarn.lock'), | |||
| testsSetup: resolveApp('src/setupTests.js'), | |||
| appNodeModules: resolveApp('node_modules'), | |||
| publicUrl: getPublicUrl(resolveApp('package.json')), | |||
| servedPath: getServedPath(resolveApp('package.json')), | |||
| }; | |||
| @@ -1,22 +0,0 @@ | |||
| 'use strict'; | |||
| if (typeof Promise === 'undefined') { | |||
| // Rejection tracking prevents a common issue where React gets into an | |||
| // inconsistent state due to an error, but it gets swallowed by a Promise, | |||
| // and the user has no idea what causes React's erratic future behavior. | |||
| require('promise/lib/rejection-tracking').enable(); | |||
| window.Promise = require('promise/lib/es6-extensions.js'); | |||
| } | |||
| // fetch() polyfill for making API calls. | |||
| require('whatwg-fetch'); | |||
| // Object.assign() is commonly used with React. | |||
| // It will use the native implementation if it's present and isn't buggy. | |||
| Object.assign = require('object-assign'); | |||
| // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. | |||
| // We don't polyfill it in the browser--this is user's responsibility. | |||
| if (process.env.NODE_ENV === 'test') { | |||
| require('raf').polyfill(global); | |||
| } | |||
| @@ -1,287 +0,0 @@ | |||
| 'use strict'; | |||
| const autoprefixer = require('autoprefixer'); | |||
| const path = require('path'); | |||
| const webpack = require('webpack'); | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin'); | |||
| const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); | |||
| const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); | |||
| const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); | |||
| const eslintFormatter = require('react-dev-utils/eslintFormatter'); | |||
| const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); | |||
| // const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); | |||
| const getClientEnvironment = require('./env'); | |||
| const paths = require('./paths'); | |||
| // Webpack uses `publicPath` to determine where the app is being served from. | |||
| // In development, we always serve from the root. This makes config easier. | |||
| const publicPath = '/'; | |||
| // `publicUrl` is just like `publicPath`, but we will provide it to our app | |||
| // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. | |||
| // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. | |||
| const publicUrl = ''; | |||
| // Get environment variables to inject into our app. | |||
| const env = getClientEnvironment(publicUrl); | |||
| // This is the development configuration. | |||
| // It is focused on developer experience and fast rebuilds. | |||
| // The production configuration is different and lives in a separate file. | |||
| // 测试用的 | |||
| module.exports = { | |||
| // You may want 'eval' instead if you prefer to see the compiled output in DevTools. | |||
| // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s | |||
| //devtool: "cheap-module-eval-source-map", | |||
| // 开启调试 | |||
| devtool: "source-map", // 开启调试 | |||
| // These are the "entry points" to our application. | |||
| // This means they will be the "root" imports that are included in JS bundle. | |||
| // The first two entry points enable "hot" CSS and auto-refreshes for JS. | |||
| entry: [ | |||
| // We ship a few polyfills by default: | |||
| require.resolve('./polyfills'), | |||
| // Include an alternative client for WebpackDevServer. A client's job is to | |||
| // connect to WebpackDevServer by a socket and get notified about changes. | |||
| // When you save a file, the client will either apply hot updates (in case | |||
| // of CSS changes), or refresh the page (in case of JS changes). When you | |||
| // make a syntax error, this client will display a syntax error overlay. | |||
| // Note: instead of the default WebpackDevServer client, we use a custom one | |||
| // to bring better experience for Create React App users. You can replace | |||
| // the line below with these two lines if you prefer the stock client: | |||
| // require.resolve('webpack-dev-server/client') + '?/', | |||
| // require.resolve('webpack/hot/dev-server'), | |||
| require.resolve('react-dev-utils/webpackHotDevClient'), | |||
| // Finally, this is your app's code: | |||
| paths.appIndexJs, | |||
| // We include the app code last so that if there is a runtime error during | |||
| // initialization, it doesn't blow up the WebpackDevServer client, and | |||
| // changing JS code would still trigger a refresh. | |||
| ], | |||
| output: { | |||
| // Add /* filename */ comments to generated require()s in the output. | |||
| pathinfo: true, | |||
| // This does not produce a real file. It's just the virtual path that is | |||
| // served by WebpackDevServer in development. This is the JS bundle | |||
| // containing code from all our entry points, and the Webpack runtime. | |||
| filename: 'static/js/bundle.js', | |||
| // There are also additional JS chunk files if you use code splitting. | |||
| chunkFilename: 'static/js/[name].chunk.js', | |||
| // This is the URL that app is served from. We use "/" in development. | |||
| publicPath: publicPath, | |||
| // Point sourcemap entries to original disk location (format as URL on Windows) | |||
| devtoolModuleFilenameTemplate: info => | |||
| path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), | |||
| }, | |||
| resolve: { | |||
| // This allows you to set a fallback for where Webpack should look for modules. | |||
| // We placed these paths second because we want `node_modules` to "win" | |||
| // if there are any conflicts. This matches Node resolution mechanism. | |||
| // https://github.com/facebookincubator/create-react-app/issues/253 | |||
| modules: ['node_modules', paths.appNodeModules].concat( | |||
| // It is guaranteed to exist because we tweak it in `env.js` | |||
| process.env.NODE_PATH.split(path.delimiter).filter(Boolean) | |||
| ), | |||
| // These are the reasonable defaults supported by the Node ecosystem. | |||
| // We also include JSX as a common component filename extension to support | |||
| // some tools, although we do not recommend using it, see: | |||
| // https://github.com/facebookincubator/create-react-app/issues/290 | |||
| // `web` extension prefixes have been added for better support | |||
| // for React Native Web. | |||
| extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], | |||
| alias: { | |||
| "educoder": __dirname + "/../src/common/educoder.js", | |||
| // Support React Native Web | |||
| // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ | |||
| 'react-native': 'react-native-web', | |||
| }, | |||
| plugins: [ | |||
| // Prevents users from importing files from outside of src/ (or node_modules/). | |||
| // This often causes confusion because we only process files within src/ with babel. | |||
| // To fix this, we prevent you from importing files out of src/ -- if you'd like to, | |||
| // please link the files into your node_modules/ and let module-resolution kick in. | |||
| // Make sure your source files are compiled, as they will not be processed in any way. | |||
| new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), | |||
| // MonacoEditor | |||
| // https://github.com/Microsoft/monaco-editor/blob/master/docs/integrate-esm.md | |||
| // https://github.com/Microsoft/monaco-editor-webpack-plugin/issues/56 | |||
| // new MonacoWebpackPlugin(), | |||
| ], | |||
| }, | |||
| module: { | |||
| strictExportPresence: true, | |||
| rules: [ | |||
| // TODO: Disable require.ensure as it's not a standard language feature. | |||
| // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. | |||
| // { parser: { requireEnsure: false } }, | |||
| // First, run the linter. | |||
| // It's important to do this before Babel processes the JS. | |||
| // { | |||
| // test: /\.(js|jsx|mjs)$/, | |||
| // enforce: 'pre', | |||
| // use: [ | |||
| // { | |||
| // options: { | |||
| // formatter: eslintFormatter, | |||
| // eslintPath: require.resolve('eslint'), | |||
| // | |||
| // }, | |||
| // loader: require.resolve('eslint-loader'), | |||
| // }, | |||
| // ], | |||
| // include: paths.appSrc, | |||
| // }, | |||
| { | |||
| // "oneOf" will traverse all following loaders until one will | |||
| // match the requirements. When no loader matches it will fall | |||
| // back to the "file" loader at the end of the loader list. | |||
| oneOf: [ | |||
| // "url" loader works like "file" loader except that it embeds assets | |||
| // smaller than specified limit in bytes as data URLs to avoid requests. | |||
| // A missing `test` is equivalent to a match. | |||
| { | |||
| test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], | |||
| loader: require.resolve('url-loader'), | |||
| options: { | |||
| limit: 10000, | |||
| name: 'static/media/[name].[hash:8].[ext]', | |||
| }, | |||
| }, | |||
| // Process JS with Babel. | |||
| { | |||
| test: /\.(js|jsx|mjs)$/, | |||
| include: paths.appSrc, | |||
| loader: require.resolve('babel-loader'), | |||
| options: { | |||
| // This is a feature of `babel-loader` for webpack (not Babel itself). | |||
| // It enables caching results in ./node_modules/.cache/babel-loader/ | |||
| // directory for faster rebuilds. | |||
| cacheDirectory: true, | |||
| }, | |||
| }, | |||
| // "postcss" loader applies autoprefixer to our CSS. | |||
| // "css" loader resolves paths in CSS and adds assets as dependencies. | |||
| // "style" loader turns CSS into JS modules that inject <style> tags. | |||
| // In production, we use a plugin to extract that CSS to a file, but | |||
| // in development "style" loader enables hot editing of CSS. | |||
| { | |||
| test: /\.css$/, | |||
| use: [ | |||
| require.resolve('style-loader'), | |||
| { | |||
| loader: require.resolve('css-loader'), | |||
| options: { | |||
| importLoaders: 1, | |||
| }, | |||
| }, | |||
| { | |||
| loader: require.resolve('postcss-loader'), | |||
| options: { | |||
| // Necessary for external CSS imports to work | |||
| // https://github.com/facebookincubator/create-react-app/issues/2677 | |||
| ident: 'postcss', | |||
| plugins: () => [ | |||
| require('postcss-flexbugs-fixes'), | |||
| autoprefixer({ | |||
| browsers: [ | |||
| '>1%', | |||
| 'last 4 versions', | |||
| 'Firefox ESR', | |||
| 'not ie < 9', // React doesn't support IE8 anyway | |||
| ], | |||
| flexbox: 'no-2009', | |||
| }), | |||
| ], | |||
| }, | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| test: /\.scss$/, | |||
| use: [ | |||
| require.resolve("style-loader"), | |||
| { | |||
| loader: require.resolve("css-loader"), | |||
| options: { | |||
| importLoaders: 1, | |||
| }, | |||
| }, | |||
| { | |||
| loader: require.resolve("sass-loader") | |||
| } | |||
| ], | |||
| }, | |||
| // "file" loader makes sure those assets get served by WebpackDevServer. | |||
| // When you `import` an asset, you get its (virtual) filename. | |||
| // In production, they would get copied to the `build` folder. | |||
| // This loader doesn't use a "test" so it will catch all modules | |||
| // that fall through the other loaders. | |||
| { | |||
| // Exclude `js` files to keep "css" loader working as it injects | |||
| // its runtime that would otherwise processed through "file" loader. | |||
| // Also exclude `html` and `json` extensions so they get processed | |||
| // by webpacks internal loaders. | |||
| exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], | |||
| loader: require.resolve('file-loader'), | |||
| options: { | |||
| name: 'static/media/[name].[hash:8].[ext]', | |||
| }, | |||
| }, | |||
| ], | |||
| }, | |||
| // ** STOP ** Are you adding a new loader? | |||
| // Make sure to add the new loader(s) before the "file" loader. | |||
| ], | |||
| }, | |||
| plugins: [ | |||
| // Makes some environment variables available in index.html. | |||
| // The public URL is available as %PUBLIC_URL% in index.html, e.g.: | |||
| // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | |||
| // In development, this will be an empty string. | |||
| new InterpolateHtmlPlugin(env.raw), | |||
| // Generates an `index.html` file with the <script> injected. | |||
| new HtmlWebpackPlugin({ | |||
| inject: true, | |||
| template: paths.appHtml, | |||
| }), | |||
| // Add module names to factory functions so they appear in browser profiler. | |||
| new webpack.NamedModulesPlugin(), | |||
| // Makes some environment variables available to the JS code, for example: | |||
| // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`. | |||
| new webpack.DefinePlugin(env.stringified), | |||
| // This is necessary to emit hot updates (currently CSS only): | |||
| new webpack.HotModuleReplacementPlugin(), | |||
| // Watcher doesn't work well if you mistype casing in a path so we use | |||
| // a plugin that prints an error when you attempt to do this. | |||
| // See https://github.com/facebookincubator/create-react-app/issues/240 | |||
| new CaseSensitivePathsPlugin(), | |||
| // If you require a missing module and then `npm install` it, you still have | |||
| // to restart the development server for Webpack to discover it. This plugin | |||
| // makes the discovery automatic so you don't have to restart. | |||
| // See https://github.com/facebookincubator/create-react-app/issues/186 | |||
| new WatchMissingNodeModulesPlugin(paths.appNodeModules), | |||
| // Moment.js is an extremely popular library that bundles large locale files | |||
| // by default due to how Webpack interprets its code. This is a practical | |||
| // solution that requires the user to opt into importing specific locales. | |||
| // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack | |||
| // You can remove this if you don't use Moment.js: | |||
| new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | |||
| // new MonacoWebpackPlugin(), | |||
| ], | |||
| // Some libraries import Node modules but don't use them in the browser. | |||
| // Tell Webpack to provide empty mocks for them so importing them works. | |||
| node: { | |||
| dgram: 'empty', | |||
| fs: 'empty', | |||
| net: 'empty', | |||
| tls: 'empty', | |||
| child_process: 'empty', | |||
| }, | |||
| // Turn off performance hints during development because we don't do any | |||
| // splitting or minification in interest of speed. These warnings become | |||
| // cumbersome. | |||
| performance: { | |||
| hints: false, | |||
| }, | |||
| }; | |||
| @@ -1,391 +0,0 @@ | |||
| 'use strict'; | |||
| // extract-css-assets-webpack-plugin | |||
| const autoprefixer = require('autoprefixer'); | |||
| const path = require('path'); | |||
| const webpack = require('webpack'); | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin'); | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin'); | |||
| const ManifestPlugin = require('webpack-manifest-plugin'); | |||
| const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); | |||
| const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); | |||
| const eslintFormatter = require('react-dev-utils/eslintFormatter'); | |||
| const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); | |||
| const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); | |||
| // const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); | |||
| // const TerserPlugin = require('terser-webpack-plugin'); | |||
| const paths = require('./paths'); | |||
| const getClientEnvironment = require('./env'); | |||
| // Webpack uses `publicPath` to determine where the app is being served from. | |||
| // It requires a trailing slash, or the file assets will get an incorrect path. | |||
| const publicPath = paths.servedPath; | |||
| // Some apps do not use client-side routing with pushState. | |||
| // For these, "homepage" can be set to "." to enable relative asset paths. | |||
| const shouldUseRelativeAssetPaths = publicPath === './'; | |||
| // Source maps are resource heavy and can cause out of memory issue for large source files. | |||
| const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; | |||
| // `publicUrl` is just like `publicPath`, but we will provide it to our app | |||
| // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. | |||
| // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. | |||
| const publicUrl = publicPath.slice(0, -1); | |||
| // Get environment variables to inject into our app. | |||
| const env = getClientEnvironment(publicUrl); | |||
| // Assert this just to be safe. | |||
| // Development builds of React are slow and not intended for production. | |||
| if (env.stringified['process.env'].NODE_ENV !== '"production"') { | |||
| throw new Error('Production builds must have NODE_ENV=production.'); | |||
| } | |||
| // Note: defined here because it will be used more than once. | |||
| const cssFilename = './static/css/[name].[contenthash:8].css'; | |||
| // ExtractTextPlugin expects the build output to be flat. | |||
| // (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27) | |||
| // However, our output is structured with css, js and media folders. | |||
| // To have this structure working with relative paths, we have to use custom options. | |||
| const extractTextPluginOptions = shouldUseRelativeAssetPaths | |||
| ? // Making sure that the publicPath goes back to to build folder. | |||
| { publicPath: Array(cssFilename.split('/').length).join('../') } | |||
| : {}; | |||
| // This is the production configuration. | |||
| // It compiles slowly and is focused on producing a fast and minimal bundle. | |||
| // The development configuration is different and lives in a separate file. | |||
| // 上线用的 | |||
| // console.log('publicPath ', publicPath) | |||
| module.exports = { | |||
| // optimization: { | |||
| // minimize: true, | |||
| // minimizer: [new TerserPlugin()], | |||
| // }, | |||
| // externals: { | |||
| // 'react': 'window.React' | |||
| // }, | |||
| // Don't attempt to continue if there are any errors. | |||
| bail: true, | |||
| // We generate sourcemaps in production. This is slow but gives good results. | |||
| // You can exclude the *.map files from the build during deployment. | |||
| // devtool: shouldUseSourceMap ? 'nosources-source-map' : false, //正式版 | |||
| devtool:false,//测试版 | |||
| // In production, we only want to load the polyfills and the app code. | |||
| entry: [require.resolve('./polyfills'), paths.appIndexJs], | |||
| output: { | |||
| // The build folder. | |||
| path: paths.appBuild, | |||
| // Generated JS file names (with nested folders). | |||
| // There will be one main bundle, and one file per asynchronous chunk. | |||
| // We don't currently advertise code splitting but Webpack supports it. | |||
| filename: './static/js/[name].[chunkhash:8].js', | |||
| chunkFilename: './static/js/[name].[chunkhash:8].chunk.js', | |||
| // We inferred the "public path" (such as / or /my-project) from homepage. | |||
| // cdn | |||
| // publicPath: 'https://shixun.educoder.net/react/build/', //publicPath, https://cdn.educoder.net | |||
| // publicPath: 'https://cdn-testeduplus2.educoder.net/react/build/', //publicPath, https://cdn.educoder.net | |||
| publicPath: '/forgeplus-react/build/', //publicPath, https://cdn.educoder.net | |||
| // Point sourcemap entries to original disk location (format as URL on Windows) | |||
| devtoolModuleFilenameTemplate: info => | |||
| path | |||
| .relative(paths.appSrc, info.absoluteResourcePath) | |||
| .replace(/\\/g, '/'), | |||
| }, | |||
| resolve: { | |||
| // This allows you to set a fallback for where Webpack should look for modules. | |||
| // We placed these paths second because we want `node_modules` to "win" | |||
| // if there are any conflicts. This matches Node resolution mechanism. | |||
| // https://github.com/facebookincubator/create-react-app/issues/253 | |||
| modules: ['node_modules', paths.appNodeModules].concat( | |||
| // It is guaranteed to exist because we tweak it in `env.js` | |||
| process.env.NODE_PATH.split(path.delimiter).filter(Boolean) | |||
| ), | |||
| // These are the reasonable defaults supported by the Node ecosystem. | |||
| // We also include JSX as a common component filename extension to support | |||
| // some tools, although we do not recommend using it, see: | |||
| // https://github.com/facebookincubator/create-react-app/issues/290 | |||
| // `web` extension prefixes have been added for better support | |||
| // for React Native Web. | |||
| extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], | |||
| alias: { | |||
| "educoder": __dirname + "/../src/common/educoder.js", | |||
| // Support React Native Web | |||
| // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ | |||
| 'react-native': 'react-native-web', | |||
| }, | |||
| plugins: [ | |||
| // Prevents users from importing files from outside of src/ (or node_modules/). | |||
| // This often causes confusion because we only process files within src/ with babel. | |||
| // To fix this, we prevent you from importing files out of src/ -- if you'd like to, | |||
| // please link the files into your node_modules/ and let module-resolution kick in. | |||
| // Make sure your source files are compiled, as they will not be processed in any way. | |||
| new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), | |||
| ], | |||
| }, | |||
| module: { | |||
| strictExportPresence: true, | |||
| rules: [ | |||
| // TODO: Disable require.ensure as it's not a standard language feature. | |||
| // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. | |||
| // { parser: { requireEnsure: false } }, | |||
| // First, run the linter. | |||
| // It's important to do this before Babel processes the JS. | |||
| { | |||
| test: /\.(js|jsx|mjs)$/, | |||
| enforce: 'pre', | |||
| use: [ | |||
| { | |||
| options: { | |||
| formatter: eslintFormatter, | |||
| eslintPath: require.resolve('eslint'), | |||
| }, | |||
| loader: require.resolve('eslint-loader'), | |||
| }, | |||
| ], | |||
| include: paths.appSrc, | |||
| }, | |||
| { | |||
| // "oneOf" will traverse all following loaders until one will | |||
| // match the requirements. When no loader matches it will fall | |||
| // back to the "file" loader at the end of the loader list. | |||
| oneOf: [ | |||
| // "url" loader works just like "file" loader but it also embeds | |||
| // assets smaller than specified size as data URLs to avoid requests. | |||
| { | |||
| test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], | |||
| loader: require.resolve('url-loader'), | |||
| options: { | |||
| limit: 10000, | |||
| name: 'static/media/[name].[hash:8].[ext]', | |||
| }, | |||
| }, | |||
| // Process JS with Babel. | |||
| { | |||
| test: /\.(js|jsx|mjs)$/, | |||
| include: paths.appSrc, | |||
| loader: require.resolve('babel-loader'), | |||
| options: { | |||
| compact: true, | |||
| }, | |||
| }, | |||
| // The notation here is somewhat confusing. | |||
| // "postcss" loader applies autoprefixer to our CSS. | |||
| // "css" loader resolves paths in CSS and adds assets as dependencies. | |||
| // "style" loader normally turns CSS into JS modules injecting <style>, | |||
| // but unlike in development configuration, we do something different. | |||
| // `ExtractTextPlugin` first applies the "postcss" and "css" loaders | |||
| // (second argument), then grabs the result CSS and puts it into a | |||
| // separate file in our build process. This way we actually ship | |||
| // a single CSS file in production instead of JS code injecting <style> | |||
| // tags. If you use code splitting, however, any async bundles will still | |||
| // use the "style" loader inside the async code so CSS from them won't be | |||
| // in the main CSS file. | |||
| { | |||
| test: /\.css$/, | |||
| loader: ExtractTextPlugin.extract( | |||
| Object.assign( | |||
| { | |||
| fallback: { | |||
| loader: require.resolve('style-loader'), | |||
| options: { | |||
| hmr: false, | |||
| }, | |||
| }, | |||
| use: [ | |||
| { | |||
| loader: require.resolve('css-loader'), | |||
| options: { | |||
| importLoaders: 1, | |||
| minimize: true, | |||
| sourceMap: shouldUseSourceMap, | |||
| }, | |||
| }, | |||
| { | |||
| loader: require.resolve('postcss-loader'), | |||
| options: { | |||
| // Necessary for external CSS imports to work | |||
| // https://github.com/facebookincubator/create-react-app/issues/2677 | |||
| ident: 'postcss', | |||
| plugins: () => [ | |||
| require('postcss-flexbugs-fixes'), | |||
| autoprefixer({ | |||
| browsers: [ | |||
| '>1%', | |||
| 'last 4 versions', | |||
| 'Firefox ESR', | |||
| 'not ie < 9', // React doesn't support IE8 anyway | |||
| ], | |||
| flexbox: 'no-2009', | |||
| }), | |||
| ], | |||
| }, | |||
| }, | |||
| ], | |||
| }, | |||
| extractTextPluginOptions | |||
| ) | |||
| ), | |||
| // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. | |||
| }, | |||
| { | |||
| test: /\.scss$/, | |||
| use: [ | |||
| require.resolve("style-loader"), | |||
| { | |||
| loader: require.resolve("css-loader"), | |||
| options: { | |||
| importLoaders: 1, | |||
| minimize: true, | |||
| sourceMap: shouldUseSourceMap, | |||
| }, | |||
| }, | |||
| { | |||
| loader: require.resolve("sass-loader") | |||
| } | |||
| ], | |||
| }, | |||
| // "file" loader makes sure assets end up in the `build` folder. | |||
| // When you `import` an asset, you get its filename. | |||
| // This loader doesn't use a "test" so it will catch all modules | |||
| // that fall through the other loaders. | |||
| { | |||
| loader: require.resolve('file-loader'), | |||
| // Exclude `js` files to keep "css" loader working as it injects | |||
| // it's runtime that would otherwise processed through "file" loader. | |||
| // Also exclude `html` and `json` extensions so they get processed | |||
| // by webpacks internal loaders. | |||
| exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], | |||
| options: { | |||
| name: 'static/media/[name].[hash:8].[ext]', | |||
| }, | |||
| }, | |||
| // ** STOP ** Are you adding a new loader? | |||
| // Make sure to add the new loader(s) before the "file" loader. | |||
| ], | |||
| }, | |||
| ], | |||
| }, | |||
| plugins: [ | |||
| // Makes some environment variables available in index.html. | |||
| // The public URL is available as %PUBLIC_URL% in index.html, e.g.: | |||
| // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | |||
| // In production, it will be an empty string unless you specify "homepage" | |||
| // in `package.json`, in which case it will be the pathname of that URL. | |||
| new InterpolateHtmlPlugin(env.raw), | |||
| // Generates an `index.html` file with the <script> injected. | |||
| new HtmlWebpackPlugin({ | |||
| inject: true, | |||
| template: paths.appHtml, | |||
| minify: { | |||
| removeComments: true, | |||
| collapseWhitespace: true, | |||
| removeRedundantAttributes: true, | |||
| useShortDoctype: true, | |||
| removeEmptyAttributes: true, | |||
| removeStyleLinkTypeAttributes: true, | |||
| keepClosingSlash: true, | |||
| minifyJS: true, | |||
| minifyCSS: true, | |||
| minifyURLs: true, | |||
| }, | |||
| }), | |||
| // Makes some environment variables available to the JS code, for example: | |||
| // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. | |||
| // It is absolutely essential that NODE_ENV was set to production here. | |||
| // Otherwise React will be compiled in the very slow development mode. | |||
| new webpack.DefinePlugin(env.stringified), | |||
| // Minify the code. | |||
| // new webpack.optimize.UglifyJsPlugin({ | |||
| // compress: { | |||
| // warnings: false, | |||
| // // Disabled because of an issue with Uglify breaking seemingly valid code: | |||
| // // https://github.com/facebookincubator/create-react-app/issues/2376 | |||
| // // Pending further investigation: | |||
| // // https://github.com/mishoo/UglifyJS2/issues/2011 | |||
| // comparisons: false, | |||
| // }, | |||
| // mangle: { | |||
| // safari10: true, | |||
| // }, | |||
| // output: { | |||
| // comments: false, | |||
| // // Turned on because emoji and regex is not minified properly using default | |||
| // // https://github.com/facebookincubator/create-react-app/issues/2488 | |||
| // ascii_only: true, | |||
| // }, | |||
| // sourceMap: shouldUseSourceMap, | |||
| // }), | |||
| //正式版上线后打开去掉debuger和console | |||
| // new ParallelUglifyPlugin({ | |||
| // cacheDir: '.cache/', | |||
| // uglifyJS:{ | |||
| // output: { | |||
| // comments: false | |||
| // }, | |||
| // compress: { | |||
| // drop_debugger: true, | |||
| // drop_console: true | |||
| // } | |||
| // } | |||
| // }), | |||
| // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. | |||
| new ExtractTextPlugin({ | |||
| filename: cssFilename, | |||
| }), | |||
| // Generate a manifest file which contains a mapping of all asset filenames | |||
| // to their corresponding output file so that tools can pick it up without | |||
| // having to parse `index.html`. | |||
| new ManifestPlugin({ | |||
| fileName: 'asset-manifest.json', | |||
| }), | |||
| // Generate a service worker script that will precache, and keep up to date, | |||
| // the HTML & assets that are part of the Webpack build. | |||
| new SWPrecacheWebpackPlugin({ | |||
| // By default, a cache-busting query parameter is appended to requests | |||
| // used to populate the caches, to ensure the responses are fresh. | |||
| // If a URL is already hashed by Webpack, then there is no concern | |||
| // about it being stale, and the cache-busting can be skipped. | |||
| dontCacheBustUrlsMatching: /\.\w{8}\./, | |||
| filename: 'service-worker.js', | |||
| logger(message) { | |||
| if (message.indexOf('Total precache size is') === 0) { | |||
| // This message occurs for every build and is a bit too noisy. | |||
| return; | |||
| } | |||
| if (message.indexOf('Skipping static resource') === 0) { | |||
| // This message obscures real errors so we ignore it. | |||
| // https://github.com/facebookincubator/create-react-app/issues/2612 | |||
| return; | |||
| } | |||
| // console.log(message); | |||
| }, | |||
| minify: true, | |||
| // For unknown URLs, fallback to the index page | |||
| navigateFallback: publicUrl + '/index.html', | |||
| // Ignores URLs starting from /__ (useful for Firebase): | |||
| // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 | |||
| navigateFallbackWhitelist: [/^(?!\/__).*/], | |||
| // Don't precache sourcemaps (they're large) and build asset manifest: | |||
| staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], | |||
| }), | |||
| // Moment.js is an extremely popular library that bundles large locale files | |||
| // by default due to how Webpack interprets its code. This is a practical | |||
| // solution that requires the user to opt into importing specific locales. | |||
| // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack | |||
| // You can remove this if you don't use Moment.js: | |||
| new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | |||
| // new MonacoWebpackPlugin(), | |||
| ], | |||
| // Some libraries import Node modules but don't use them in the browser. | |||
| // Tell Webpack to provide empty mocks for them so importing them works. | |||
| node: { | |||
| dgram: 'empty', | |||
| fs: 'empty', | |||
| net: 'empty', | |||
| tls: 'empty', | |||
| child_process: 'empty', | |||
| }, | |||
| }; | |||
| @@ -1,95 +0,0 @@ | |||
| 'use strict'; | |||
| const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); | |||
| const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); | |||
| const ignoredFiles = require('react-dev-utils/ignoredFiles'); | |||
| const config = require('./webpack.config.dev'); | |||
| const paths = require('./paths'); | |||
| const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; | |||
| const host = process.env.HOST || '0.0.0.0'; | |||
| module.exports = function(proxy, allowedHost) { | |||
| return { | |||
| // WebpackDevServer 2.4.3 introduced a security fix that prevents remote | |||
| // websites from potentially accessing local content through DNS rebinding: | |||
| // https://github.com/webpack/webpack-dev-server/issues/887 | |||
| // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a | |||
| // However, it made several existing use cases such as development in cloud | |||
| // environment or subdomains in development significantly more complicated: | |||
| // https://github.com/facebookincubator/create-react-app/issues/2271 | |||
| // https://github.com/facebookincubator/create-react-app/issues/2233 | |||
| // While we're investigating better solutions, for now we will take a | |||
| // compromise. Since our WDS configuration only serves files in the `public` | |||
| // folder we won't consider accessing them a vulnerability. However, if you | |||
| // use the `proxy` feature, it gets more dangerous because it can expose | |||
| // remote code execution vulnerabilities in backends like Django and Rails. | |||
| // So we will disable the host check normally, but enable it if you have | |||
| // specified the `proxy` setting. Finally, we let you override it if you | |||
| // really know what you're doing with a special environment variable. | |||
| disableHostCheck: | |||
| !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', | |||
| // Enable gzip compression of generated files. | |||
| compress: true, | |||
| // Silence WebpackDevServer's own logs since they're generally not useful. | |||
| // It will still show compile warnings and errors with this setting. | |||
| clientLogLevel: 'none', | |||
| // By default WebpackDevServer serves physical files from current directory | |||
| // in addition to all the virtual build products that it serves from memory. | |||
| // This is confusing because those files won’t automatically be available in | |||
| // production build folder unless we copy them. However, copying the whole | |||
| // project directory is dangerous because we may expose sensitive files. | |||
| // Instead, we establish a convention that only files in `public` directory | |||
| // get served. Our build script will copy `public` into the `build` folder. | |||
| // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: | |||
| // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | |||
| // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. | |||
| // Note that we only recommend to use `public` folder as an escape hatch | |||
| // for files like `favicon.ico`, `manifest.json`, and libraries that are | |||
| // for some reason broken when imported through Webpack. If you just want to | |||
| // use an image, put it in `src` and `import` it from JavaScript instead. | |||
| contentBase: paths.appPublic, | |||
| // By default files from `contentBase` will not trigger a page reload. | |||
| watchContentBase: true, | |||
| // Enable hot reloading server. It will provide /sockjs-node/ endpoint | |||
| // for the WebpackDevServer client so it can learn when the files were | |||
| // updated. The WebpackDevServer client is included as an entry point | |||
| // in the Webpack development configuration. Note that only changes | |||
| // to CSS are currently hot reloaded. JS changes will refresh the browser. | |||
| hot: true, | |||
| // It is important to tell WebpackDevServer to use the same "root" path | |||
| // as we specified in the config. In development, we always serve from /. | |||
| publicPath: config.output.publicPath, | |||
| // WebpackDevServer is noisy by default so we emit custom message instead | |||
| // by listening to the compiler events with `compiler.plugin` calls above. | |||
| quiet: true, | |||
| // Reportedly, this avoids CPU overload on some systems. | |||
| // https://github.com/facebookincubator/create-react-app/issues/293 | |||
| // src/node_modules is not ignored to support absolute imports | |||
| // https://github.com/facebookincubator/create-react-app/issues/1065 | |||
| watchOptions: { | |||
| ignored: ignoredFiles(paths.appSrc), | |||
| }, | |||
| // Enable HTTPS if the HTTPS environment variable is set to 'true' | |||
| https: protocol === 'https', | |||
| host: host, | |||
| overlay: false, | |||
| historyApiFallback: { | |||
| // Paths with dots should still use the history fallback. | |||
| // See https://github.com/facebookincubator/create-react-app/issues/387. | |||
| disableDotRule: true, | |||
| }, | |||
| public: allowedHost, | |||
| proxy, | |||
| before(app) { | |||
| // This lets us open files from the runtime error overlay. | |||
| app.use(errorOverlayMiddleware()); | |||
| // This service worker file is effectively a 'no-op' that will reset any | |||
| // previous service worker registered for the same host:port combination. | |||
| // We do this in development to avoid hitting the production cache if | |||
| // it used the same host and port. | |||
| // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432 | |||
| app.use(noopServiceWorkerMiddleware()); | |||
| }, | |||
| }; | |||
| }; | |||
| @@ -1,46 +0,0 @@ | |||
| import React from 'react'; | |||
| import ReactDOM from 'react-dom'; | |||
| import './index.css'; | |||
| import './indexPlus.css'; | |||
| import App from './App'; | |||
| // 加之前main.js 18.1MB | |||
| // import { message } from 'antd'; | |||
| import message from 'antd/lib/message'; | |||
| import 'antd/lib/message/style/css'; | |||
| import { AppContainer } from 'react-hot-loader'; | |||
| import registerServiceWorker from './registerServiceWorker'; | |||
| import { configureUrlQuery } from 'react-url-query'; | |||
| import history from './history'; | |||
| // link the history used in our app to url-query so it can update the URL with it. | |||
| configureUrlQuery({ history }); | |||
| // ----------------------------------------------------------------------------------- 请求配置 | |||
| window.__useKindEditor = false; | |||
| const render = (Component) => { | |||
| ReactDOM.render( | |||
| <AppContainer {...this.props} {...this.state}> | |||
| <Component {...this.props} {...this.state}/> | |||
| </AppContainer>, | |||
| document.getElementById('root') | |||
| ); | |||
| } | |||
| // ReactDOM.render( | |||
| // , | |||
| // document.getElementById('root')); | |||
| // registerServiceWorker(); | |||
| render(App); | |||
| if (module.hot) { | |||
| module.hot.accept('./App', () => { render(App) }); | |||
| } | |||
| @@ -1,190 +0,0 @@ | |||
| { | |||
| "name": "educoder", | |||
| "version": "0.1.0", | |||
| "private": true, | |||
| "dependencies": { | |||
| "@icedesign/base": "^0.2.5", | |||
| "@monaco-editor/react": "^2.3.0", | |||
| "@novnc/novnc": "^1.1.0", | |||
| "antd": "^3.23.2", | |||
| "array-flatten": "^2.1.2", | |||
| "autoprefixer": "7.1.6", | |||
| "axios": "^0.18.0", | |||
| "babel-core": "6.26.0", | |||
| "babel-eslint": "7.2.3", | |||
| "babel-jest": "20.0.3", | |||
| "babel-loader": "7.1.2", | |||
| "babel-plugin-syntax-dynamic-import": "^6.18.0", | |||
| "babel-preset-react-app": "^3.1.1", | |||
| "babel-runtime": "6.26.0", | |||
| "bizcharts": "^3.5.5", | |||
| "bundle-loader": "^0.5.6", | |||
| "case-sensitive-paths-webpack-plugin": "2.1.1", | |||
| "chalk": "1.1.3", | |||
| "classnames": "^2.2.5", | |||
| "clipboard": "^2.0.4", | |||
| "codemirror": "^5.46.0", | |||
| "connected-react-router": "4.4.1", | |||
| "css-loader": "0.28.7", | |||
| "dotenv": "4.0.0", | |||
| "dotenv-expand": "4.2.0", | |||
| "echarts": "^4.2.0-rc.2", | |||
| "editor.md": "^1.5.0", | |||
| "eslint": "4.10.0", | |||
| "eslint-config-react-app": "^2.1.0", | |||
| "eslint-loader": "1.9.0", | |||
| "eslint-plugin-flowtype": "2.39.1", | |||
| "eslint-plugin-import": "2.8.0", | |||
| "eslint-plugin-jsx-a11y": "5.1.1", | |||
| "eslint-plugin-react": "7.4.0", | |||
| "extract-text-webpack-plugin": "3.0.2", | |||
| "file-loader": "1.1.5", | |||
| "fs-extra": "3.0.1", | |||
| "html-webpack-plugin": "2.29.0", | |||
| "immutability-helper": "^2.6.6", | |||
| "install": "^0.12.2", | |||
| "jest": "20.0.4", | |||
| "js-base64": "^2.5.1", | |||
| "katex": "^0.11.1", | |||
| "lodash": "^4.17.5", | |||
| "loglevel": "^1.6.1", | |||
| "material-ui": "^1.0.0-beta.40", | |||
| "md5": "^2.2.1", | |||
| "moment": "^2.23.0", | |||
| "monaco-editor": "0.13", | |||
| "monaco-editor-webpack-plugin": "^1.8.1", | |||
| "npm": "^6.10.1", | |||
| "numeral": "^2.0.6", | |||
| "object-assign": "4.1.1", | |||
| "postcss-flexbugs-fixes": "3.2.0", | |||
| "postcss-loader": "2.0.8", | |||
| "promise": "8.0.1", | |||
| "prop-types": "^15.6.1", | |||
| "qs": "^6.6.0", | |||
| "quill": "^1.3.7", | |||
| "quill-delta-to-html": "^0.11.0", | |||
| "raf": "3.4.0", | |||
| "rc-form": "^2.1.7", | |||
| "rc-pagination": "^1.16.2", | |||
| "rc-rate": "^2.4.0", | |||
| "rc-select": "^8.0.12", | |||
| "rc-tree": "^1.7.11", | |||
| "rc-upload": "^2.5.1", | |||
| "react": "^16.9.0", | |||
| "react-beautiful-dnd": "^10.0.4", | |||
| "react-codemirror": "^1.0.0", | |||
| "react-codemirror2": "^6.0.0", | |||
| "react-color": "^2.18.0", | |||
| "react-content-loader": "^3.1.1", | |||
| "react-cookies": "^0.1.1", | |||
| "react-dev-utils": "^5.0.0", | |||
| "react-dom": "^16.9.0", | |||
| "react-hot-loader": "^4.0.0", | |||
| "react-infinite-scroller": "^1.2.4", | |||
| "react-loadable": "^5.3.1", | |||
| "react-monaco-editor": "^0.25.1", | |||
| "react-player": "^1.11.1", | |||
| "react-redux": "5.0.7", | |||
| "react-router": "^4.2.0", | |||
| "react-router-dom": "^4.2.2", | |||
| "react-split-pane": "^0.1.89", | |||
| "react-url-query": "^1.4.0", | |||
| "redux": "^4.0.0", | |||
| "redux-thunk": "2.3.0", | |||
| "rsuite": "^4.0.1", | |||
| "sass-loader": "^7.3.1", | |||
| "scroll-into-view": "^1.12.3", | |||
| "source-map-support": "^0.5.16", | |||
| "showdown": "^1.9.1", | |||
| "showdown-katex": "^0.6.0", | |||
| "store": "^2.0.12", | |||
| "style-loader": "0.19.0", | |||
| "styled-components": "^4.1.3", | |||
| "sw-precache-webpack-plugin": "0.11.4", | |||
| "url-loader": "0.6.2", | |||
| "webpack": "3.8.1", | |||
| "webpack-dev-server": "2.9.4", | |||
| "webpack-manifest-plugin": "1.3.2", | |||
| "webpack-parallel-uglify-plugin": "^1.1.0", | |||
| "whatwg-fetch": "2.0.3", | |||
| "wrap-md-editor": "^0.2.20" | |||
| }, | |||
| "scripts": { | |||
| "start": "node --max_old_space_size=20000 scripts/start.js", | |||
| "build": "node --max_old_space_size=15360 scripts/build.js", | |||
| "concat": "node scripts/concat.js", | |||
| "gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json", | |||
| "ana": "webpack-bundle-analyzer ./stats.json", | |||
| "analyze": "npm run build -- --stats && webpack-bundle-analyzer build/bundle-stats.json", | |||
| "analyz": "NODE_ENV=production npm_config_report=true npm run build" | |||
| }, | |||
| "jest": { | |||
| "collectCoverageFrom": [ | |||
| "src/**/*.{js,jsx,mjs}" | |||
| ], | |||
| "setupFiles": [ | |||
| "<rootDir>/config/polyfills.js" | |||
| ], | |||
| "testMatch": [ | |||
| "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}", | |||
| "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}" | |||
| ], | |||
| "testEnvironment": "node", | |||
| "testURL": "http://localhost", | |||
| "transform": { | |||
| "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest", | |||
| "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", | |||
| "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js" | |||
| }, | |||
| "transformIgnorePatterns": [ | |||
| "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$" | |||
| ], | |||
| "moduleNameMapper": { | |||
| "^react-native$": "react-native-web" | |||
| }, | |||
| "moduleFileExtensions": [ | |||
| "web.js", | |||
| "mjs", | |||
| "js", | |||
| "json", | |||
| "web.jsx", | |||
| "jsx", | |||
| "node" | |||
| ] | |||
| }, | |||
| "babel": { | |||
| "presets": [ | |||
| "react", | |||
| "react-app" | |||
| ], | |||
| "plugins": [ | |||
| [ | |||
| "import", | |||
| { | |||
| "libraryName": "antd", | |||
| "libraryDirectory": "lib", | |||
| "style": "css" | |||
| }, | |||
| "ant" | |||
| ], | |||
| "syntax-dynamic-import" | |||
| ] | |||
| }, | |||
| "eslintConfig": { | |||
| "extends": "react-app" | |||
| }, | |||
| "proxy": "http://localhost:3000", | |||
| "port": "3007", | |||
| "devDependencies": { | |||
| "@babel/runtime": "7.0.0-beta.51", | |||
| "babel-plugin-import": "^1.11.0", | |||
| "compression-webpack-plugin": "^1.1.12", | |||
| "concat": "^1.0.3", | |||
| "happypack": "^5.0.1", | |||
| "mockjs": "^1.1.0", | |||
| "node-sass": "^4.12.0", | |||
| "reqwest": "^2.0.5", | |||
| "webpack-bundle-analyzer": "^3.0.3", | |||
| "webpack-parallel-uglify-plugin": "^1.1.0" | |||
| } | |||
| } | |||
| @@ -1,172 +0,0 @@ | |||
| 其他的文档位置: | |||
| /educoder/public/react/public/js/readme.txt 关于js_min_all | |||
| /educoder/educoder/public/react/scripts/readme-cdn.txt 关于CDN | |||
| /educoder/public/react/src/modules/page/readme.txt 关于TPI | |||
| /educoder/public/editormd/lib/readme-marked.txt 关于md编辑器 marked.js | |||
| 1、 安装node v6.9.x;此安装包含了node和npm。 | |||
| 2、 安装cnpm(命令行): npm install -g cnpm --registry=https://registry.npm.taobao.org | |||
| 3、 安装依赖的js库(public/react目录下<即项目package.json所在目录>,开启命令行): cnpm install | |||
| 4、 如果你的ruby服务使用的是3000端口,则需要在package.json中修改"port"参数的值 | |||
| 5、 启动服务(命令行-目录同3): npm start | |||
| 6、 build初始化 npm run build | |||
| 注意: | |||
| 1、cnpm install 之前先需要修改下ruby mine的一个settings,防止ruby mine对node_modules目录里的内容建索引(详情见线上文档-react开发环境搭建) | |||
| 线上文档-react开发环境搭建 地址: https://www.trustie.net/boards/6862/topics/46425 | |||
| 2、package.json中配置: | |||
| "proxy": "http://localhost:3000", | |||
| "port": "3007" | |||
| 目前暂时必须写为和上面的一样,ruby服务端口为3000,node服务端口为3007;当当前端口为3007时,程序会将axios发出的请求转到localhost:3000上,进行跨域请求。 | |||
| 3、静态js加载问题: | |||
| editormd源码改动,注释掉了564行 加载codemirror/codemirror.min的js代码。因为codemirror 已经加载了,codemirror对象会带有插件,重复加载会覆盖全局codemirror对象,使得之前加载的插件失效 | |||
| ---------------------------------------------------------------------------------------------- | |||
| React开发相关知识点 | |||
| 需要了解的ES6的知识 https://www.trustie.net/boards/6862/topics/46427 | |||
| ---------------------------------------------------------------------------------------------- | |||
| 新加入的lib有: axios、material-ui、lodash、classnames、moment、immutability-helper | |||
| rc-tree、rc-form 、rc-rate、rc-pagination、rc-select 、showdown | |||
| 考虑替代删除确认弹出框的组件http://react-component.github.io/tooltip/examples/onVisibleChange.html | |||
| ---------------------------------------------------------------------------------------------- | |||
| TPI State整理 START | |||
| ---------------------------------------------------------------------------------------------- | |||
| TPIContextProvider 详情接口的所有state | |||
| Index.js | |||
| taskListLoading | |||
| challenges | |||
| challengesDrawerOpen | |||
| MainContentContainer.js | |||
| repositoryCode: '', | |||
| currentPath: '', // 当前所选的path,可能是一个只读的path(只读path的话,challenge.athIndex为-1) | |||
| isEditablePath // 是否是可以编辑的path() | |||
| open: false, // 繁忙等级等提示用Dialog, TODO 考虑重构封装到根组件 | |||
| gameBuilding: false, // 评测中标志 | |||
| codeStatus: 2, // 0 已修改 1 保存中 2 已保存 3 保存失败 | |||
| codeLoading: false, // code加载中 | |||
| resetCodeDialogOpen: false, // TODO考虑重构封装到根组件 | |||
| resetPassedCodeDialogOpen: false, // TODO考虑重构封装到根组件 | |||
| LeftViewContainer.js | |||
| tabIndex: 0, 页签index | |||
| dialogOpen: false, | |||
| gameAnswer: '', 答案 | |||
| snackbarOpen: false, | |||
| comments: [], 评论 | |||
| comment_count_without_reply: 0, 评论数量 TODO 和详情接口字段重复 | |||
| // 默认pageSize为10 | |||
| currentPage: 1, 评论分页 | |||
| loadingComments: true, 评论加载中 | |||
| gotNewReply: false, 新的回复 | |||
| CodeRepositoryViewContainer.js | |||
| drawerOpen: false, | |||
| loadingFirstRepoFiles: false, drawer里的loading状态 | |||
| fileTreeData: "", 文件树 | |||
| codeRepositoryViewExpanded: false, 展开状态 | |||
| CodeEvaluateView.js | |||
| testSetsInitedArray: testSetsExpandedArrayInitVal.slice(0), 测试集是否初始化标志 | |||
| evaluateViewExpanded: false, | |||
| tabIndex: 1, 页签index | |||
| ---------------------------------------------------------------------------------------------- | |||
| TPI State整理 END | |||
| ---------------------------------------------------------------------------------------------- | |||
| ---------------------------------------------------------------------------------------------- | |||
| 重要:TPI实现时修改的js库的记录 START | |||
| ---------------------------------------------------------------------------------------------- | |||
| ---------------------------------------------------------------------------------------------- | |||
| 重要:TPI实现时修改的js库的记录 END | |||
| ---------------------------------------------------------------------------------------------- | |||
| create_kindeditor.js __isR 表示是react环境,react环境下采用事件通知react组件来处理 | |||
| if (window['__isR'] === true) { | |||
| $(document).trigger("onReply", { commentContent:tContents, id:id, editor:params.editor } ); | |||
| } else { | |||
| params.form.submit(); | |||
| } | |||
| editormd.min.js 直接注释掉了codemirror.min的加载,应该改成有codeMirror了则不加载 | |||
| // codemirror 已经加载了,codemirror会有插件,重复加载会使得之前加载的插件失效 | |||
| // editormd.loadScript(loadPath + "codemirror/codemirror.min", function() { | |||
| 对应提交项 | |||
| Revision: 73d95ce266d5d7e55a3a88d08d1247b3a08c7caf | |||
| Date: 2018/4/2 16:12:21 | |||
| Message: 切下一题时更新左侧editormd里的内容,更新右侧codemirror内容。 | |||
| js_min_all.js 最后面手动加入了若干js代码,还没做分离、再合并处理 date:180507 | |||
| is_cdn_link tpi_html_show方法 | |||
| ---------------------------------------------------------------------------------------------- | |||
| TPM使用react实现的利弊 START | |||
| ---------------------------------------------------------------------------------------------- | |||
| 1、全部使用react重写 | |||
| 做法:第一屏使用新接口,之前的js脚本还是继续使用,有必要的话(需要局部刷新的),将部分jquery实现改为react实现 | |||
| 利: | |||
| tpi中评论组件、文件树组件方便复用 | |||
| js、css库管理方便 | |||
| 暂时不依赖于react的状态管理 | |||
| 之前的ajax请求还是可以暂时复用 | |||
| 弊: | |||
| 接口评估? | |||
| rails模板要改成jsx语法 | |||
| 头部功能区域、底部静态链接区域会存在重复代码 : react版和非react版 | |||
| codemirror等组件的使用会不会有问题? | |||
| 学习成本 | |||
| !!目前决定,新页面或者评论组件所在页面使用react实现 | |||
| ---------------------------------------------------------------------------------------------- | |||
| TPM使用react实现的利弊 END | |||
| ---------------------------------------------------------------------------------------------- | |||
| 其他方式:comments组件build到新入口后,将代码copy到rails页面 | |||
| ---------------------------------------------------------------------------------------------- | |||
| 不错的库 START | |||
| ---------------------------------------------------------------------------------------------- | |||
| https://livicons.com/icons-original -- 收费 动画icon | |||
| https://github.com/maxwellito/vivus -- 让SVG标签动起来 | |||
| http://ianlunn.github.io/Hover/ -- hover 动画 | |||
| https://github.com/legomushroom/mojs | |||
| https://github.com/juliangarnier/anime --js动画 | |||
| https://codepen.io/juliangarnier/pen/gmOwJX | |||
| https://github.com/daneden/animate.css | |||
| A responsive tour snippet, with a step-by-step guide(onboarding) to help users understand how to use your website. | |||
| https://github.com/sorich87/bootstrap-tour | |||
| https://github.com/linkedin/hopscotch | |||
| https://github.com/Robophil/Product-Tour | |||
| code editor | |||
| https://microsoft.github.io/monaco-editor/ | |||
| @@ -1,251 +0,0 @@ | |||
| 'use strict'; | |||
| // Do this as the first thing so that any code reading it knows the right env. | |||
| process.env.BABEL_ENV = 'production'; | |||
| process.env.NODE_ENV = 'production'; | |||
| // Makes the script crash on unhandled rejections instead of silently | |||
| // ignoring them. In the future, promise rejections that are not handled will | |||
| // terminate the Node.js process with a non-zero exit code. | |||
| process.on('unhandledRejection', err => { | |||
| throw err; | |||
| }); | |||
| // Ensure environment variables are read. | |||
| require('../config/env'); | |||
| const path = require('path'); | |||
| const chalk = require('chalk'); | |||
| const fs = require('fs-extra'); | |||
| const webpack = require('webpack'); | |||
| const config = require('../config/webpack.config.prod'); | |||
| const paths = require('../config/paths'); | |||
| const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); | |||
| const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | |||
| const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); | |||
| const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); | |||
| const printBuildError = require('react-dev-utils/printBuildError'); | |||
| var CombinedStream = require('combined-stream'); | |||
| var fs2 = require('fs'); | |||
| const measureFileSizesBeforeBuild = | |||
| FileSizeReporter.measureFileSizesBeforeBuild; | |||
| const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; | |||
| const useYarn = fs.existsSync(paths.yarnLockFile); | |||
| // These sizes are pretty large. We'll warn for bundles exceeding them. | |||
| const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; | |||
| const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; | |||
| // Warn and crash if required files are missing | |||
| if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { | |||
| process.exit(1); | |||
| } | |||
| function removeExceptGitDir(dir) { | |||
| // readdirSync | |||
| const list = fs2.readdirSync(dir) | |||
| // if (err) return done(err); | |||
| var pending = list.length; | |||
| // if (!pending) return done(null, results); | |||
| list.forEach(function(file) { | |||
| if (file.indexOf('.git') == -1) { | |||
| file = path.resolve(dir, file); | |||
| fs.removeSync(file) | |||
| } | |||
| }); | |||
| } | |||
| // First, read the current file sizes in build directory. | |||
| // This lets us display how much they changed later. | |||
| measureFileSizesBeforeBuild(paths.appBuild) | |||
| .then(previousFileSizes => { | |||
| // Remove all content but keep the directory so that | |||
| // if you're in it, you don't end up in Trash | |||
| // fs.emptyDirSync(paths.appBuild); | |||
| console.log('removeExceptGitDir') | |||
| removeExceptGitDir(paths.appBuild) | |||
| console.log('copyPublicFolder') | |||
| // Merge with the public folder | |||
| copyPublicFolder(); | |||
| // Start the webpack build | |||
| return build(previousFileSizes); | |||
| }) | |||
| .then( | |||
| ({ stats, previousFileSizes, warnings }) => { | |||
| if (warnings.length) { | |||
| console.log(chalk.yellow('Compiled with warnings.\n')); | |||
| console.log(warnings.join('\n\n')); | |||
| console.log( | |||
| '\nSearch for the ' + | |||
| chalk.underline(chalk.yellow('keywords')) + | |||
| ' to learn more about each warning.' | |||
| ); | |||
| console.log( | |||
| 'To ignore, add ' + | |||
| chalk.cyan('// eslint-disable-next-line') + | |||
| ' to the line before.\n' | |||
| ); | |||
| } else { | |||
| console.log(chalk.green('Compiled successfully.\n')); | |||
| } | |||
| console.log('File sizes after gzip:\n'); | |||
| printFileSizesAfterBuild( | |||
| stats, | |||
| previousFileSizes, | |||
| paths.appBuild, | |||
| WARN_AFTER_BUNDLE_GZIP_SIZE, | |||
| WARN_AFTER_CHUNK_GZIP_SIZE | |||
| ); | |||
| console.log(); | |||
| const appPackage = require(paths.appPackageJson); | |||
| const publicUrl = paths.publicUrl; | |||
| const publicPath = config.output.publicPath; | |||
| const buildFolder = path.relative(process.cwd(), paths.appBuild); | |||
| printHostingInstructions( | |||
| appPackage, | |||
| publicUrl, | |||
| publicPath, | |||
| buildFolder, | |||
| useYarn | |||
| ); | |||
| }, | |||
| err => { | |||
| console.log(chalk.red('Failed to compile.\n')); | |||
| printBuildError(err); | |||
| process.exit(1); | |||
| } | |||
| ); | |||
| // Create the production build and print the deployment instructions. | |||
| function build(previousFileSizes) { | |||
| console.log('Creating an optimized production build...'); | |||
| let compiler = webpack(config); | |||
| return new Promise((resolve, reject) => { | |||
| compiler.run((err, stats) => { | |||
| if (err) { | |||
| return reject(err); | |||
| } | |||
| const messages = formatWebpackMessages(stats.toJson({}, true)); | |||
| if (messages.errors.length) { | |||
| // Only keep the first error. Others are often indicative | |||
| // of the same problem, but confuse the reader with noise. | |||
| if (messages.errors.length > 1) { | |||
| messages.errors.length = 1; | |||
| } | |||
| return reject(new Error(messages.errors.join('\n\n'))); | |||
| } | |||
| if ( | |||
| process.env.CI && | |||
| (typeof process.env.CI !== 'string' || | |||
| process.env.CI.toLowerCase() !== 'false') && | |||
| messages.warnings.length | |||
| ) { | |||
| console.log( | |||
| chalk.yellow( | |||
| '\nTreating warnings as errors because process.env.CI = true.\n' + | |||
| 'Most CI servers set it automatically.\n' | |||
| ) | |||
| ); | |||
| return reject(new Error(messages.warnings.join('\n\n'))); | |||
| } | |||
| generateNewIndexJsp(); | |||
| return resolve({ | |||
| stats, | |||
| previousFileSizes, | |||
| warnings: messages.warnings, | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| function copyPublicFolder() { | |||
| fs.copySync(paths.appPublic, paths.appBuild, { | |||
| dereference: true, | |||
| filter: file => file !== paths.appHtml, | |||
| }); | |||
| } | |||
| // 给build脚本增加的方法,对其生成的index.html做一些文本替换,以及cdn处理 | |||
| function generateNewIndexJsp() { | |||
| // var combinedStream = CombinedStream.create(); | |||
| var filePath = paths.appBuild + '/index.html'; | |||
| // var htmlContent = fs2.createReadStream( filePath ) | |||
| // stream没有replace方法 | |||
| // htmlContent = htmlContent.replace('/js/js_min_all.js', '/react/build/js/js_min_all.js') | |||
| // htmlContent = htmlContent.replace('/css/css_min_all.css', '/react/build/css/css_min_all.css') | |||
| // combinedStream.append(htmlContent); | |||
| // combinedStream.pipe(fs2.createWriteStream( filePath )); | |||
| var outputPath = paths.appBuild + '/../../../index.html' | |||
| fs2.readFile(filePath, 'utf8', function (err,data) { | |||
| if (err) { | |||
| return console.log(err); | |||
| } | |||
| const newVersion = '1.1.1' | |||
| let cdnHost = 'https://shixun.educoder.net' | |||
| cdnHost = 'https://ali-cdn.educoder.net' | |||
| cdnHost = '' | |||
| var mainRegex = /<script type="text\/javascript" src="\/forgeplus-react\/build\/.\/static\/js\/main.([a-zA-Z0-9]{8,}).js"><\/script>/ | |||
| var matchResult = data.match(mainRegex) | |||
| var code = ` | |||
| <script> | |||
| (function() { | |||
| var _host = '/forgeplus-react/build/' | |||
| /**/ | |||
| if (window.location.host == 'pre-newweb.educoder.net') { | |||
| _host = 'https://testali-cdn.educoder.net/react/build/' | |||
| } else if (window.location.host == 'www.educoder.net') { | |||
| _host = 'https://ali-cdn.educoder.net/react/build/' | |||
| } | |||
| document.write('<script type="text/javascript" src="' + _host + 'js/js_min_all.js"><\\/script>'); | |||
| document.write('<script type="text/javascript" src="' + _host + 'static/js/main.${matchResult[1]}.js"><\\/script>'); | |||
| })() | |||
| </script> | |||
| ` | |||
| var jsMinAllRegex = /<script type="text\/javascript" src="\/js\/js_min_all.js"><\/script>/ | |||
| // <script type="text/javascript" src="/js/js_min_all.js"></script> | |||
| var result = data | |||
| .replace(jsMinAllRegex, code) | |||
| // .replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) | |||
| // .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) | |||
| // ${cdnHost} 加了cdn后,这个文件里的字体文件加载会有跨域的报错 ../fonts/fontawesome-webfont.eot | |||
| // TODO tpi 评测结果关闭也使用了fontawesome | |||
| .replace('/css/css_min_all.css', `${cdnHost}/react/build/css/css_min_all.css?v=${newVersion}`) | |||
| .replace('/css/iconfont.css', `${cdnHost}/react/build/css/iconfont.css?v=${newVersion}`) | |||
| .replace(/\/js\/create_kindeditor.js/g, `${cdnHost}/react/build/js/create_kindeditor.js?v=${newVersion}`) | |||
| .replace(mainRegex, '') | |||
| // .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`) | |||
| // .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`) | |||
| // .replace(/https:\/\/testeduplus2.educoder.net/g, ''); | |||
| // .replace(/http:\/\/testbdweb.educoder.net/g, ''); | |||
| // .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css'); | |||
| fs2.writeFile(outputPath, result, 'utf8', function (err) { | |||
| if (err) return console.log(err); | |||
| // commitAndPush(); | |||
| }); | |||
| }); | |||
| } | |||
| function commitAndPush() { | |||
| var exec = require('child_process').exec; | |||
| function puts(error, stdout, stderr) { console.log(stdout) } | |||
| var options = {cwd:"./build"}; | |||
| exec("git status && git commit -am 'b' && git push", options, puts); | |||
| } | |||
| @@ -1,98 +0,0 @@ | |||
| var fs = require('fs'); | |||
| var uglify = require("uglify-js"); | |||
| var path = require('path'); | |||
| var concat = require('concat') | |||
| var results = []; | |||
| var walk = function(dir, done) { | |||
| fs.readdir(dir, function(err, list) { | |||
| console.log(list) | |||
| if (err) return done(err); | |||
| var pending = list.length; | |||
| if (!pending) return done(null, results); | |||
| list.forEach(function(file) { | |||
| file = path.resolve(dir, file); | |||
| fs.stat(file, function(err, stat) { | |||
| if (stat && stat.isDirectory()) { | |||
| walk(file, function(err, res) { | |||
| // results = results.concat(res); | |||
| if (!--pending) done(null, results); | |||
| }); | |||
| } else { | |||
| results.push(file); | |||
| if (!--pending) done(null, results); | |||
| } | |||
| }); | |||
| }); | |||
| }); | |||
| }; | |||
| // 需要输出文件名数组时改为true | |||
| var jsDir = './public/js/'; | |||
| var cssDir = './public/css' | |||
| // true && | |||
| false && | |||
| walk(cssDir, function() { | |||
| console.log('results', results.length, results) | |||
| }) | |||
| // return; | |||
| // ----------------------------------------------------------------------------- CSS | |||
| var cssResults = [ | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\edu-common.css', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\edu-public.css', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\taskstyle.css' , | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\font-awesome.css', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\editormd.min.css', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\css\\merge.css', | |||
| ] | |||
| concat(cssResults, './public/css/css_min_all.css') | |||
| return; | |||
| // ----------------------------------------------------------------------------- JS | |||
| var _results = [ | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\jquery-1.8.3.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\underscore.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\marked.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\prettify.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\raphael.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\sequence-diagram.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\flowchart.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\jquery.flowchart.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\editormd\\editormd.min.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\codemirror\\codemirror.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\codemirror\\mode\\javascript.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\diff_match_patch.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\merge.js', | |||
| 'D:\\Code\\trustieplus\\public\\react\\public\\js\\edu_tpi.js', | |||
| ] | |||
| concat(_results, './public/js/js_min_all.js') | |||
| // var uglified = uglify.minify(['./public/js/merge.js']); | |||
| // console.log('uglified', uglified) | |||
| // fs.writeFile('concat.min.js', uglified.code, function (err){ | |||
| // if(err) { | |||
| // console.log(err); | |||
| // } else { | |||
| // console.log("Script generated and saved:", 'concat.min.js'); | |||
| // } | |||
| // }); | |||
| // var uglified = uglify.minify(['file1.js', 'file2.js', 'file3.js']); | |||
| @@ -1,23 +0,0 @@ | |||
| var fs2 = require('fs'); | |||
| function generateNewIndexJsp() { | |||
| var filePath = './build/index.html'; | |||
| var outputPath = filePath | |||
| fs2.readFile(filePath, 'utf8', function (err,data) { | |||
| if (err) { | |||
| return console.log(err); | |||
| } | |||
| var result = data | |||
| .replace('/js/create_kindeditor.js', '/react/build/js/create_kindeditor.js') | |||
| .replace(/https:\/\/testeduplus2.educoder.net/g, ''); | |||
| // .replace(/http:\/\/testbdweb.educoder.net/g, ''); | |||
| .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css'); | |||
| fs2.writeFile(outputPath, result, 'utf8', function (err) { | |||
| if (err) return console.log(err); | |||
| }); | |||
| }); | |||
| } | |||
| @@ -1,16 +0,0 @@ | |||
| 目前是判断域名的方式动态访问对应的cdn资源 | |||
| 静态资源处理在build.js中,如下代码: | |||
| if (window.location.host == 'pre-newweb.educoder.net') { | |||
| _host = 'https://testali-cdn.educoder.net/react/build/' | |||
| } else if (window.location.host == 'www.educoder.net') { | |||
| _host = 'https://ali-cdn.educoder.net/react/build/' | |||
| } | |||
| 只对预上线和正式版做了处理 | |||
| 动态的chunk资源处理在public-path.js中,如下代码: | |||
| if ( window.location.host == 'pre-newweb.educoder.net') { | |||
| __webpack_public_path__ = 'https://testali-cdn.educoder.net/react/build/' | |||
| } else if ( window.location.host == 'www.educoder.net') { | |||
| __webpack_public_path__ = 'https://ali-cdn.educoder.net/react/build/' | |||
| } | |||
| @@ -1,114 +0,0 @@ | |||
| 'use strict'; | |||
| // Do this as the first thing so that any code reading it knows the right env. | |||
| process.env.BABEL_ENV = 'development'; | |||
| process.env.NODE_ENV = 'development'; | |||
| // Makes the script crash on unhandled rejections instead of silently | |||
| // ignoring them. In the future, promise rejections that are not handled will | |||
| // terminate the Node.js process with a non-zero exit code. | |||
| process.on('unhandledRejection', err => { | |||
| throw err; | |||
| }); | |||
| // Ensure environment variables are read. | |||
| require('../config/env'); | |||
| const fs = require('fs'); | |||
| const chalk = require('chalk'); | |||
| const webpack = require('webpack'); | |||
| const WebpackDevServer = require('webpack-dev-server'); | |||
| const clearConsole = require('react-dev-utils/clearConsole'); | |||
| const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); | |||
| const { | |||
| choosePort, | |||
| createCompiler, | |||
| prepareProxy, | |||
| prepareUrls, | |||
| } = require('react-dev-utils/WebpackDevServerUtils'); | |||
| const openBrowser = require('react-dev-utils/openBrowser'); | |||
| const paths = require('../config/paths'); | |||
| const config = require('../config/webpack.config.dev'); | |||
| const createDevServerConfig = require('../config/webpackDevServer.config'); | |||
| const useYarn = fs.existsSync(paths.yarnLockFile); | |||
| const isInteractive = process.stdout.isTTY; | |||
| const portSetting = require(paths.appPackageJson).port | |||
| if ( portSetting ) { | |||
| process.env.port = portSetting | |||
| } | |||
| // Warn and crash if required files are missing | |||
| if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { | |||
| process.exit(1); | |||
| } | |||
| // Tools like Cloud9 rely on this. | |||
| const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3007; | |||
| const HOST = process.env.HOST || '0.0.0.0'; | |||
| if (process.env.HOST) { | |||
| console.log( | |||
| chalk.cyan( | |||
| `Attempting to bind to HOST environment variable: ${chalk.yellow( | |||
| chalk.bold(process.env.HOST) | |||
| )}` | |||
| ) | |||
| ); | |||
| console.log( | |||
| `If this was unintentional, check that you haven't mistakenly set it in your shell.` | |||
| ); | |||
| console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); | |||
| console.log(); | |||
| } | |||
| // We attempt to use the default port but if it is busy, we offer the user to | |||
| // run on a different port. `choosePort()` Promise resolves to the next free port. | |||
| choosePort(HOST, DEFAULT_PORT) | |||
| .then(port => { | |||
| if (port == null) { | |||
| // We have not found a port. | |||
| return; | |||
| } | |||
| const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; | |||
| const appName = require(paths.appPackageJson).name; | |||
| const urls = prepareUrls(protocol, HOST, port); | |||
| // Create a webpack compiler that is configured with custom messages. | |||
| const compiler = createCompiler(webpack, config, appName, urls, useYarn); | |||
| // Load proxy config | |||
| const proxySetting = require(paths.appPackageJson).proxy; | |||
| console.log('-------------------------proxySetting:', proxySetting) | |||
| const proxyConfig = prepareProxy(proxySetting, paths.appPublic); | |||
| // Serve webpack assets generated by the compiler over a web sever. | |||
| const serverConfig = createDevServerConfig( | |||
| proxyConfig, | |||
| urls.lanUrlForConfig | |||
| ); | |||
| const devServer = new WebpackDevServer(compiler, serverConfig); | |||
| // Launch WebpackDevServer. | |||
| devServer.listen(port, HOST, err => { | |||
| if (err) { | |||
| return console.log(err); | |||
| } | |||
| if (isInteractive) { | |||
| clearConsole(); | |||
| } | |||
| console.log(chalk.cyan('Starting the development server...\n')); | |||
| openBrowser(urls.localUrlForBrowser); | |||
| }); | |||
| ['SIGINT', 'SIGTERM'].forEach(function(sig) { | |||
| process.on(sig, function() { | |||
| devServer.close(); | |||
| process.exit(); | |||
| }); | |||
| }); | |||
| }) | |||
| .catch(err => { | |||
| if (err && err.message) { | |||
| console.log(err.message); | |||
| } | |||
| process.exit(1); | |||
| }); | |||
| @@ -1,27 +0,0 @@ | |||
| 'use strict'; | |||
| // Do this as the first thing so that any code reading it knows the right env. | |||
| process.env.BABEL_ENV = 'test'; | |||
| process.env.NODE_ENV = 'test'; | |||
| process.env.PUBLIC_URL = ''; | |||
| // Makes the script crash on unhandled rejections instead of silently | |||
| // ignoring them. In the future, promise rejections that are not handled will | |||
| // terminate the Node.js process with a non-zero exit code. | |||
| process.on('unhandledRejection', err => { | |||
| throw err; | |||
| }); | |||
| // Ensure environment variables are read. | |||
| require('../config/env'); | |||
| const jest = require('jest'); | |||
| const argv = process.argv.slice(2); | |||
| // Watch unless on CI or in coverage mode | |||
| if (!process.env.CI && argv.indexOf('--coverage') < 0) { | |||
| argv.push('--watch'); | |||
| } | |||
| jest.run(argv); | |||
| @@ -1,107 +0,0 @@ | |||
| .App { | |||
| text-align: center; | |||
| } | |||
| .App-logo { | |||
| animation: App-logo-spin infinite 20s linear; | |||
| height: 80px; | |||
| } | |||
| .App-header { | |||
| background-color: #222; | |||
| height: 150px; | |||
| padding: 20px; | |||
| color: white; | |||
| } | |||
| .App-title { | |||
| font-size: 1.5em; | |||
| } | |||
| .App-intro { | |||
| font-size: large; | |||
| } | |||
| @keyframes App-logo-spin { | |||
| from { transform: rotate(0deg); } | |||
| to { transform: rotate(360deg); } | |||
| } | |||
| /* 控制md编辑器列行的宽度 | |||
| 见 codermirror maybeUpdateLineNumberWidth方法 | |||
| */ | |||
| .editormd .CodeMirror-linenumbers { | |||
| padding: 0; | |||
| } | |||
| .editormd-html-preview hr, .editormd-preview-container hr { | |||
| /* 颜色加深 */ | |||
| border-top: 1px solid #ccc; | |||
| } | |||
| /* 重置掉antd的一些样式 */ | |||
| html, body { | |||
| -webkit-font-smoothing: auto !important; | |||
| } | |||
| .ant-progress-textyes { | |||
| color: #52c41a; | |||
| } | |||
| .ant-progress-textno{ | |||
| color: #f5222d; | |||
| } | |||
| /* md多空格 */ | |||
| .markdown-body p { | |||
| white-space: pre-wrap; | |||
| font-size: 16px!important | |||
| } | |||
| .markdown-body > p { | |||
| line-height: 25px; | |||
| } | |||
| /* https://www.educoder.net/courses/2346/group_homeworks/34405/question */ | |||
| .renderAsHtml.markdown-body p { | |||
| white-space: inherit; | |||
| } | |||
| /* resize */ | |||
| .editormd .CodeMirror { | |||
| border-right: none !important; | |||
| } | |||
| .editormd-preview { | |||
| border-left: 1px solid rgb(221, 221, 221); | |||
| /* 某些情况下,被cm盖住了 */ | |||
| z-index: 99; | |||
| } | |||
| /* 图片点击放大的场景,隐藏图片链接 */ | |||
| .editormd-image-click-expand .editormd-image-dialog { | |||
| height: 234px !important; | |||
| } | |||
| .editormd-image-click-expand .editormd-image-dialog .image-link { | |||
| display: none; | |||
| } | |||
| /* 解决鼠标框选时,左边第一列没高亮的问题 */ | |||
| .CodeMirror .CodeMirror-lines pre.CodeMirror-line, .CodeMirror .CodeMirror-lines pre.CodeMirror-line-like { | |||
| padding: 0 12px ; | |||
| } | |||
| /* antd扩展 */ | |||
| .formItemInline.ant-form-item { | |||
| display: flex; | |||
| } | |||
| .formItemInline .ant-form-item-control-wrapper { | |||
| flex: 1; | |||
| } | |||
| /* AutoComplete placeholder 不显示的问题 */ | |||
| .ant-select-auto-complete.ant-select .ant-select-selection__placeholder { | |||
| z-index: 2; | |||
| } | |||
| /* 兼容性 */ | |||
| /* 火狐有滚动条时高度问题 */ | |||
| @-moz-document url-prefix() { | |||
| .newContainers { | |||
| min-height: calc(100% - 60px) !important; | |||
| } | |||
| } | |||
| @@ -1,909 +0,0 @@ | |||
| import React, {Component} from 'react'; | |||
| import './public-path'; | |||
| import logo from './logo.svg'; | |||
| import './App.css'; | |||
| import {ConfigProvider} from 'antd' | |||
| import zhCN from 'antd/lib/locale-provider/zh_CN'; | |||
| import { | |||
| BrowserRouter as Router, | |||
| Route, | |||
| Switch | |||
| } from 'react-router-dom'; | |||
| import axios from 'axios'; | |||
| import '@icedesign/base/dist/ICEDesignBase.css'; | |||
| import '@icedesign/base/index.scss'; | |||
| import LoginDialog from './modules/login/LoginDialog'; | |||
| import Notcompletedysl from './modules/user/Notcompletedysl'; | |||
| import Trialapplicationysl from './modules/login/Trialapplicationysl'; | |||
| import Trialapplicationreview from './modules/user/Trialapplicationreview'; | |||
| import Addcourses from "./modules/courses/coursesPublic/Addcourses"; | |||
| import AccountProfile from "./modules/user/AccountProfile"; | |||
| import Accountnewprofile from './modules/user/Accountnewprofile'; | |||
| import Trialapplication from './modules/login/Trialapplication'; | |||
| import Certifiedprofessional from './modules/modals/Certifiedprofessional'; | |||
| import NotFoundPage from './NotFoundPage' | |||
| import Loading from './Loading' | |||
| import Loadable from 'react-loadable'; | |||
| import moment from 'moment' | |||
| import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles'; | |||
| // import './AppConfig' | |||
| import history from './history'; | |||
| import {SnackbarHOC} from 'educoder' | |||
| import {initAxiosInterceptors} from './AppConfig' | |||
| import { Provider } from 'react-redux'; | |||
| import configureStore from './redux/stores/configureStore'; | |||
| // !!!tpi需要这个来加载css | |||
| import {TPMIndexHOC} from './modules/tpm/TPMIndexHOC'; | |||
| const store = configureStore(); | |||
| const theme = createMuiTheme({ | |||
| palette: { | |||
| primary: { | |||
| main: '#4CACFF', | |||
| contrastText: 'rgba(255, 255, 255, 0.87)' | |||
| }, | |||
| secondary: {main: '#4CACFF'}, // #11cb5f This is just green.A700 as hex. | |||
| }, | |||
| }); | |||
| // | |||
| // const Trialapplication= Loadable({ | |||
| // loader: () =>import('./modules/login/Trialapplication'), | |||
| // loading:Loading, | |||
| // }) | |||
| //登入 | |||
| const EducoderLogin = Loadable({ | |||
| loader: () => import('./modules/login/EducoderLogin'), | |||
| loading: Loading, | |||
| }) | |||
| //微信登录 | |||
| const Otherlogin=Loadable({ | |||
| loader: () => import('./modules/login/Otherlogin'), | |||
| loading: Loading, | |||
| }) | |||
| //微信登录 | |||
| const Loginqq=Loadable({ | |||
| loader: () => import('./modules/login/Loginqq'), | |||
| loading: Loading, | |||
| }) | |||
| const Otherloginstart=Loadable({ | |||
| loader: () => import('./modules/login/Otherloginstart'), | |||
| loading: Loading, | |||
| }) | |||
| const Otherloginsqq=Loadable({ | |||
| loader: () => import('./modules/login/Otherloginqq'), | |||
| loading: Loading, | |||
| }) | |||
| // const TestIndex = Loadable({ | |||
| // loader: () => import('./modules/test'), | |||
| // loading: Loading, | |||
| // }) | |||
| const IndexWrapperComponent = Loadable({ | |||
| loader: () => import('./modules/page/IndexWrapper'), | |||
| loading: Loading, | |||
| }) | |||
| const CommentComponent = Loadable({ | |||
| loader: () => import('./modules/comment/CommentContainer'), | |||
| loading: Loading, | |||
| }) | |||
| // const TestMaterialDesignComponent = Loadable({ | |||
| // loader: () => import('./modules/test/md/TestMaterialDesign'), | |||
| // loading: Loading, | |||
| // }) | |||
| // const TestCodeMirrorComponent = Loadable({ | |||
| // loader: () => import('./modules/test/codemirror/TestCodeMirror'), | |||
| // loading: Loading, | |||
| // }) | |||
| // const TestComponent = Loadable({ | |||
| // loader: () => import('./modules/test/TestRC'), | |||
| // loading: Loading, | |||
| // }) | |||
| // const TestUrlQueryComponent = Loadable({ | |||
| // loader: () => import('./modules/test/urlquery/TestUrlQuery'), | |||
| // loading: Loading, | |||
| // }) | |||
| const TPMIndexComponent = Loadable({ | |||
| loader: () => import('./modules/tpm/TPMIndex'), | |||
| loading: Loading, | |||
| }) | |||
| const TPMShixunsIndexComponent = Loadable({ | |||
| loader: () => import('./modules/tpm/shixuns/ShixunsIndex'), | |||
| loading: Loading, | |||
| }) | |||
| //实训课程(原实训路径) | |||
| const ShixunPaths = Loadable({ | |||
| loader: () => import('./modules/paths/Index'), | |||
| loading: Loading, | |||
| }) | |||
| //在线课堂 | |||
| const CoursesIndex = Loadable({ | |||
| loader: () => import('./modules/courses/Index'), | |||
| loading: Loading, | |||
| }) | |||
| const SearchPage = Loadable({ | |||
| loader: () => import('./search/SearchPage'), | |||
| loading: Loading, | |||
| }) | |||
| // 课堂讨论 | |||
| // const BoardIndex = Loadable({ | |||
| // loader: () => import('./modules/courses/boards/BoardIndex'), | |||
| // loading:Loading, | |||
| // }) | |||
| // //课堂普通作业&分组作业 | |||
| // const CoursesWorkIndex = Loadable({ | |||
| // loader: () => import('./modules/courses/busyWork/Index'), | |||
| // loading:Loading, | |||
| // }) | |||
| // | |||
| // const TPMShixunchildIndexComponent = Loadable({ | |||
| // loader: () => import('./modules/tpm/shixunchild/ShixunChildIndex'), | |||
| // loading: Loading, | |||
| // }) | |||
| // const TPMshixunfork_listIndexComponent = Loadable({ | |||
| // loader: () => import('./modules/tpm/shixunchild/Shixunfork_list'), | |||
| // loading: Loading, | |||
| // }) | |||
| const ForumsIndexComponent = Loadable({ | |||
| loader: () => import('./modules/forums/ForumsIndex'), | |||
| loading: Loading, | |||
| }) | |||
| const ProjectIndex = Loadable({ | |||
| loader: () => import('./forge/Index'), | |||
| loading: Loading, | |||
| }) | |||
| // trustie plus forum | |||
| // const TPForumsIndexComponent = Loadable({ | |||
| // loader: () => import('./modules/tp-forums/TPForumsIndex'), | |||
| // loading: Loading, | |||
| // }) | |||
| // const TestPageComponent = Loadable({ | |||
| // loader: () => import('./modules/page/Index'), | |||
| // loading: Loading, | |||
| // }) | |||
| //新建实训 | |||
| const Newshixuns = Loadable({ | |||
| loader: () => import('./modules/tpm/newshixuns/Newshixuns'), | |||
| loading: Loading, | |||
| }) | |||
| //实训首页 | |||
| const ShixunsHome = Loadable({ | |||
| loader: () => import('./modules/home/shixunsHome'), | |||
| loading: Loading, | |||
| }) | |||
| const CompatibilityPageLoadable = Loadable({ | |||
| loader: () => import('./modules/common/CompatibilityPage'), | |||
| loading: Loading, | |||
| }) | |||
| //403页面 | |||
| const Shixunauthority = Loadable({ | |||
| loader: () => import('./modules/403/Shixunauthority'), | |||
| loading: Loading, | |||
| }) | |||
| //404页面 | |||
| const Shixunnopage = Loadable({ | |||
| loader: () => import('./modules/404/Shixunnopage'), | |||
| loading: Loading, | |||
| }) | |||
| //500页面 | |||
| const http500 = Loadable({ | |||
| loader: () => import('./modules/500/http500'), | |||
| loading: Loading, | |||
| }) | |||
| // 登录注册 | |||
| const LoginRegisterPage = Loadable({ | |||
| loader: () => import('./modules/user/LoginRegisterPage'), | |||
| loading: Loading, | |||
| }) | |||
| const AccountPage = Loadable({ | |||
| loader: () => import('./modules/user/AccountPage'), | |||
| loading: Loading, | |||
| }) | |||
| // 个人主页 | |||
| const UsersInfo = Loadable({ | |||
| loader: () => import('./modules/user/usersInfo/Infos'), | |||
| loading: Loading, | |||
| }) | |||
| const InfosIndex = Loadable({ | |||
| loader: () => import('./modules/user/usersInfo/InfosIndex'), | |||
| loading: Loading, | |||
| }) | |||
| // 题库 | |||
| const BanksIndex = Loadable({ | |||
| loader: () => import('./modules/user/usersInfo/banks/BanksIndex'), | |||
| loading: Loading, | |||
| }) | |||
| // 教学案例 | |||
| const MoopCases = Loadable({ | |||
| loader: () => import('./modules/moop_cases/index'), | |||
| loading: Loading, | |||
| }) | |||
| // 兴趣页面 | |||
| const Interestpage = Loadable({ | |||
| loader: () => import('./modules/login/EducoderInteresse'), | |||
| loading: Loading, | |||
| }) | |||
| //众包创新 | |||
| // const ProjectPackages=Loadable({ | |||
| // loader: () => import('./modules/projectPackages/ProjectPackageIndex'), | |||
| // loading: Loading, | |||
| // }) | |||
| //竞赛 | |||
| const NewCompetitions=Loadable({ | |||
| loader: () => import('./modules/competitions/Competitions'), | |||
| loading: Loading, | |||
| }) | |||
| //黑客松定制竞赛 | |||
| const Osshackathon=Loadable({ | |||
| loader: () => import('./modules/osshackathon/Osshackathon'), | |||
| loading: Loading, | |||
| }) | |||
| const Messagerouting= Loadable({ | |||
| loader: () => import('./modules/message/js/Messagerouting'), | |||
| loading: Loading, | |||
| }) | |||
| const Topicbank= Loadable({ | |||
| loader: () => import('./modules/topic_bank/Topic_bank'), | |||
| loading: Loading, | |||
| }) | |||
| const Help = Loadable({ | |||
| loader: () => import('./modules/help/Help'), | |||
| loading: Loading, | |||
| }) | |||
| const Ecs = Loadable({ | |||
| loader: () => import('./modules/ecs/Ecs'), | |||
| loading: Loading, | |||
| }) | |||
| // 添加开发者社区 | |||
| const Developer = Loadable({ | |||
| loader: () => import('./modules/developer'), | |||
| loading: Loading | |||
| }) | |||
| // 试题库 | |||
| const Headplugselection = Loadable({ | |||
| loader: () => import('./modules/question/Question'), | |||
| loading: Loading | |||
| }) | |||
| //试题库新建 //题库编辑 | |||
| const Questionitem_banks = Loadable({ | |||
| loader: () => import('./modules/question/Questionitem_banks'), | |||
| loading: Loading | |||
| }) | |||
| //试卷库 | |||
| const Testpaperlibrary= Loadable({ | |||
| loader: () => import('./modules/testpaper/Testpaperlibrary'), | |||
| loading: Loading | |||
| }) | |||
| //试卷编辑 | |||
| const Paperlibraryeditid= Loadable({ | |||
| loader: () => import('./modules/testpaper/Paperlibraryeditid'), | |||
| loading: Loading | |||
| }) | |||
| //试卷查看 | |||
| const Paperlibraryseeid= Loadable({ | |||
| loader: () => import('./modules/testpaper/Paperlibraryseeid'), | |||
| loading: Loading | |||
| }) | |||
| //人工组卷 | |||
| const Paperreview= Loadable({ | |||
| loader: () => import('./modules/question/Paperreview'), | |||
| loading: Loading | |||
| }) | |||
| //智能组卷 | |||
| const Integeneration= Loadable({ | |||
| loader: () => import('./modules/testpaper/Intecomponents'), | |||
| loading: Loading | |||
| }) | |||
| // 学院统计 | |||
| const College = Loadable({ | |||
| loader: () => import('./college/College'), | |||
| loading: Loading | |||
| }) | |||
| // 开发者编辑模块 | |||
| const NewOrEditTask = Loadable({ | |||
| loader: () => import('./modules/developer/newOrEditTask'), | |||
| loading: Loading | |||
| }); | |||
| // 学员学习 | |||
| const StudentStudy = Loadable({ | |||
| loader: () => import('./modules/developer/studentStudy'), | |||
| loading: Loading | |||
| }); | |||
| // 提交记录详情 | |||
| const RecordDetail = Loadable({ | |||
| loader: () => import('./modules/developer/recordDetail'), | |||
| loading: Loading | |||
| }); | |||
| // jupyter tpi | |||
| const JupyterTPI = Loadable({ | |||
| loader: () => import('./modules/tpm/jupyter'), | |||
| loading: Loading | |||
| }); | |||
| // 微信代码编辑器 | |||
| // const WXCode = Loadable({ | |||
| // loader: () => import('./modules/wxcode'), | |||
| // loading: Loading | |||
| // }); | |||
| // //个人竞赛报名 | |||
| // const PersonalCompetit = Loadable({ | |||
| // loader: () => import('./modules/competition/personal/PersonalCompetit.js'), | |||
| // loading: Loading, | |||
| // }); | |||
| class App extends Component { | |||
| constructor(props) { | |||
| super(props) | |||
| this.state = { | |||
| Addcoursestype:false, | |||
| Addcoursestypes:false, | |||
| mydisplay:false, | |||
| occupation:0, | |||
| mygetHelmetapi: null, | |||
| } | |||
| } | |||
| HideAddcoursestypess=(i)=>{ | |||
| console.log("调用了"); | |||
| this.setState({ | |||
| Addcoursestype:false, | |||
| Addcoursestypes:false, | |||
| mydisplay:true, | |||
| occupation:i, | |||
| }) | |||
| }; | |||
| hideAddcoursestypes=()=>{ | |||
| this.setState({ | |||
| Addcoursestypes:false | |||
| }) | |||
| }; | |||
| ModalCancelsy=()=>{ | |||
| this.setState({ | |||
| mydisplay:false, | |||
| }) | |||
| window.location.href = "/"; | |||
| }; | |||
| ModalshowCancelsy=()=>{ | |||
| this.setState({ | |||
| mydisplay:true, | |||
| }) | |||
| }; | |||
| disableVideoContextMenu = () => { | |||
| window.$( "body" ).on( "mousedown", "video", function(event) { | |||
| if(event.which === 3) { | |||
| window.$('video').bind('contextmenu',function () { return false; }); | |||
| } else { | |||
| window.$('video').unbind('contextmenu'); | |||
| } | |||
| }); | |||
| } | |||
| componentDidMount() { | |||
| document.title = "loading..."; | |||
| this.disableVideoContextMenu(); | |||
| // force an update if the URL changes | |||
| history.listen(() => { | |||
| this.forceUpdate() | |||
| const $ = window.$ | |||
| // https://www.trustie.net/issues/21919 可能会有问题 | |||
| $("html").animate({ scrollTop: $('html').scrollTop() - 0 }) | |||
| }); | |||
| initAxiosInterceptors(this.props); | |||
| // 顶部和底部的动态设置 | |||
| // this.getAppdata(); | |||
| // | |||
| // axios.interceptors.response.use((response) => { | |||
| // // console.log("response"+response); | |||
| // if(response!=undefined) | |||
| // // console.log("response"+response.data.statu); | |||
| // if (response&&response.data.status === 407) { | |||
| // this.setState({ | |||
| // isRenders: true, | |||
| // }) | |||
| // } | |||
| // return response; | |||
| // }, (error) => { | |||
| // //TODO 这里如果样式变了会出现css不加载的情况 | |||
| // }); | |||
| window.addEventListener('error', (event) => { | |||
| const msg = `${event.type}: ${event.message}`; | |||
| console.log(msg) | |||
| }); | |||
| } | |||
| //修改登录方法 | |||
| Modifyloginvalue=()=>{ | |||
| this.setState({ | |||
| isRender:false, | |||
| }) | |||
| }; | |||
| //获取数据为空的时候 | |||
| gettablogourlnull = () => { | |||
| this.setState({ | |||
| mygetHelmetapi: undefined | |||
| }); | |||
| document.title = "EduCoder"; | |||
| var link = document.createElement('link'), | |||
| oldLink = document.getElementById('dynamic-favicon'); | |||
| link.id = 'dynamic-favicon'; | |||
| link.rel = 'shortcut icon'; | |||
| link.href = "/forgeplus-react/build/./favicon.ico"; | |||
| if (oldLink) { | |||
| document.head.removeChild(oldLink); | |||
| } | |||
| document.head.appendChild(link); | |||
| }; | |||
| //获取数据的时候 | |||
| gettablogourldata = (response) => { | |||
| document.title = response.data.setting.name; | |||
| var link = document.createElement('link'), | |||
| oldLink = document.getElementById('dynamic-favicon'); | |||
| link.id = 'dynamic-favicon'; | |||
| link.rel = 'shortcut icon'; | |||
| link.href = '/' + response.data.setting.tab_logo_url; | |||
| if (oldLink) { | |||
| document.head.removeChild(oldLink); | |||
| } | |||
| document.head.appendChild(link); | |||
| } | |||
| //获取当前定制信息 | |||
| getAppdata=()=>{ | |||
| let url = "/setting.json"; | |||
| axios.get(url).then((response) => { | |||
| // console.log("app.js开始请求/setting.json"); | |||
| // console.log("获取当前定制信息"); | |||
| if(response){ | |||
| if(response.data){ | |||
| this.setState({ | |||
| mygetHelmetapi:response.data.setting | |||
| }); | |||
| //存储配置到游览器 | |||
| localStorage.setItem('chromesetting',JSON.stringify(response.data.setting)); | |||
| localStorage.setItem('chromesettingresponse',JSON.stringify(response)); | |||
| try { | |||
| if (response.data.setting.tab_logo_url) { | |||
| this.gettablogourldata(response); | |||
| } else { | |||
| this.gettablogourlnull(); | |||
| } | |||
| } catch (e) { | |||
| this.gettablogourlnull(); | |||
| } | |||
| } else { | |||
| this.gettablogourlnull(); | |||
| } | |||
| } else { | |||
| this.gettablogourlnull(); | |||
| } | |||
| }).catch((error) => { | |||
| this.gettablogourlnull(); | |||
| }); | |||
| }; | |||
| render() { | |||
| return ( | |||
| <Provider store={store}> | |||
| <ConfigProvider locale={zhCN}> | |||
| <MuiThemeProvider theme={theme}> | |||
| <Accountnewprofile {...this.props}{...this.state}/> | |||
| <LoginDialog {...this.props} {...this.state} Modifyloginvalue={()=>this.Modifyloginvalue()}></LoginDialog> | |||
| <Notcompletedysl {...this.props} {...this.state}></Notcompletedysl> | |||
| <Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl> | |||
| <Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview> | |||
| <Addcourses {...this.props} {...this.state} HideAddcoursestypess={(i)=>this.HideAddcoursestypess(i)}/> | |||
| <AccountProfile {...this.props} {...this.state} /> | |||
| <Certifiedprofessional {...this.props} {...this.state} ModalCancelsy={this.ModalCancelsy} ModalshowCancelsy={this.ModalshowCancelsy}/> | |||
| <Router> | |||
| <Switch> | |||
| {/* 项目 */} | |||
| <Route path="/projects" | |||
| render={ | |||
| (props) => { | |||
| return (<ProjectIndex {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| <Route exact path="/" | |||
| // component={ShixunsHome} | |||
| render={ | |||
| (props)=>(<ProjectIndex {...this.props} {...props} {...this.state}></ProjectIndex>) | |||
| } | |||
| /> | |||
| {/*题库*/} | |||
| <Route path="/topicbank/:username/:topicstype" | |||
| render={ | |||
| (props) => { | |||
| return (<Topicbank {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| {/*题库*/} | |||
| <Route path="/topicbank/:topicstype" | |||
| render={ | |||
| (props) => { | |||
| return (<Topicbank {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| {/*/!*众包创新*!/*/} | |||
| {/*<Route path={"/crowdsourcing"} component={ProjectPackages}/>*/} | |||
| {/*竞赛*/} | |||
| <Route path={"/competitions"} | |||
| render={ | |||
| (props) => { | |||
| return (<NewCompetitions {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| {/*黑客松定制竞赛*/} | |||
| <Route | |||
| path={"/osshackathon"} | |||
| render={ | |||
| (props)=>{ | |||
| return( | |||
| <Osshackathon {...this.props} {...props} {...this.state} /> | |||
| ) | |||
| } | |||
| } | |||
| /> | |||
| {/*认证*/} | |||
| <Route path="/account" component={AccountPage}/> | |||
| {/*403*/} | |||
| <Route path="/403" component={Shixunauthority}/> | |||
| <Route path="/500" component={http500}/> | |||
| {/*404*/} | |||
| <Route path="/nopage" component={Shixunnopage}/> | |||
| <Route path="/compatibility" component={CompatibilityPageLoadable}/> | |||
| <Route | |||
| path="/login" | |||
| render={ | |||
| (props) => { | |||
| return (<EducoderLogin {...this.props} {...props} {...this.state} />) | |||
| } | |||
| } | |||
| /> | |||
| <Route | |||
| path="/register" | |||
| render={ | |||
| (props) => { | |||
| return (<EducoderLogin {...this.props} {...props} {...this.state} />) | |||
| } | |||
| } | |||
| /> | |||
| <Route | |||
| path="/otherloginstart" component={Otherloginstart} | |||
| /> | |||
| <Route | |||
| path={"/otherloginqq"} component={Otherloginsqq} | |||
| /> | |||
| <Route | |||
| path="/otherlogin" component={Otherlogin} | |||
| /> | |||
| <Route | |||
| path="/loginqq" component={Loginqq} | |||
| /> | |||
| <Route path="/users/:username" | |||
| render={ | |||
| (props) => { | |||
| return (<InfosIndex {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| <Route path="/banks" | |||
| render={ | |||
| (props) => { | |||
| return (<BanksIndex {...this.props} {...props} {...this.state} />) | |||
| } | |||
| }></Route> | |||
| {/*<Route*/} | |||
| {/*path="/personalcompetit"*/} | |||
| {/*render={*/} | |||
| {/*(props) => (<PersonalCompetit {...this.props} {...props} {...this.state}></PersonalCompetit>)*/} | |||
| {/*}*/} | |||
| {/*/>*/} | |||
| <Route | |||
| path="/changepassword" | |||
| render={ | |||
| (props) => { | |||
| return (<EducoderLogin {...this.props} {...props} {...this.state} />) | |||
| } | |||
| } | |||
| /> | |||
| {/*<Route*/} | |||
| {/* path="/interesse" component={Interestpage}*/} | |||
| {/*/>*/} | |||
| <Route path="/shixuns/new" component={Newshixuns}> | |||
| </Route> | |||
| <Route path="/colleges/:id/statistics" | |||
| render={ | |||
| (props) => (<College {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| {/* jupyter */} | |||
| <Route path="/tasks/:identifier/jupyter/" | |||
| render={ | |||
| (props) => { | |||
| return (<JupyterTPI {...this.props} {...props} {...this.state}/>) | |||
| } | |||
| } | |||
| /> | |||
| <Route path="/myproblems/record_detail/:id" | |||
| render={ | |||
| (props) => (<RecordDetail {...this.props} {...props} {...this.state} />) | |||
| } | |||
| /> | |||
| <Route | |||
| path="/problems/:id/edit" | |||
| render={ | |||
| (props) => (<NewOrEditTask {...this.props} {...props} {...this.state} />) | |||
| } /> | |||
| <Route path="/Integeneration/:type/:id" | |||
| render={ | |||
| (props) => (<Paperreview {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/paperreview/:type" | |||
| render={ | |||
| (props) => (<Paperreview {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/paperlibrary/edit/:id" | |||
| render={ | |||
| (props) => (<Paperlibraryeditid {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/paperlibrary/see/:id" | |||
| render={ | |||
| (props) => (<Paperlibraryseeid {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/myproblems/:id/:tab?" | |||
| render={ | |||
| (props) => (<StudentStudy {...this.props} {...props} {...this.state} />) | |||
| } /> | |||
| <Route path="/question/edit/:id" | |||
| render={ | |||
| (props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />) | |||
| } /> | |||
| <Route path="/question/newitem" | |||
| render={ | |||
| (props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />) | |||
| } /> | |||
| <Route path="/question/:type" | |||
| render={ | |||
| (props) => (<Headplugselection {...this.props} {...props} {...this.state} />) | |||
| } /> | |||
| <Route path="/paperlibrary" | |||
| render={ | |||
| (props) => (<Testpaperlibrary {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/Integeneration" | |||
| render={ | |||
| (props) => (<Integeneration {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/problems" | |||
| render={ | |||
| (props) => (<Developer {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| <Route path="/question" | |||
| render={ | |||
| (props) => (<Headplugselection {...this.props} {...props} {...this.state} />) | |||
| }/> | |||
| {/*<Route path="/wxcode/:identifier?" component={WXCode}*/} | |||
| {/* render={*/} | |||
| {/* (props)=>(<WXCode {...this.props} {...props} {...this.state}></WXCode>)*/} | |||
| {/* }*/} | |||
| {/*/>*/} | |||
| <Route exact path="/" | |||
| // component={ShixunsHome} | |||
| render={ | |||
| (props)=>(<ShixunsHome {...this.props} {...props} {...this.state}></ShixunsHome>) | |||
| } | |||
| /> | |||
| <Route component={Shixunnopage}/> | |||
| </Switch> | |||
| </Router> | |||
| </MuiThemeProvider> | |||
| </ConfigProvider> | |||
| </Provider> | |||
| ); | |||
| } | |||
| } | |||
| // moment国际化,设置为中文 | |||
| moment.defineLocale('zh-cn', { | |||
| months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), | |||
| monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), | |||
| weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), | |||
| weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'), | |||
| weekdaysMin: '日_一_二_三_四_五_六'.split('_'), | |||
| longDateFormat: { | |||
| LT: 'Ah点mm分', | |||
| LTS: 'Ah点m分s秒', | |||
| L: 'YYYY-MM-DD', | |||
| LL: 'YYYY年MMMD日', | |||
| LLL: 'YYYY年MMMD日Ah点mm分', | |||
| LLLL: 'YYYY年MMMD日ddddAh点mm分', | |||
| l: 'YYYY-MM-DD', | |||
| ll: 'YYYY年MMMD日', | |||
| lll: 'YYYY年MMMD日Ah点mm分', | |||
| llll: 'YYYY年MMMD日ddddAh点mm分' | |||
| }, | |||
| meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, | |||
| meridiemHour: function (hour, meridiem) { | |||
| if (hour === 12) { | |||
| hour = 0; | |||
| } | |||
| if (meridiem === '凌晨' || meridiem === '早上' || | |||
| meridiem === '上午') { | |||
| return hour; | |||
| } else if (meridiem === '下午' || meridiem === '晚上') { | |||
| return hour + 12; | |||
| } else { | |||
| // '中午' | |||
| return hour >= 11 ? hour : hour + 12; | |||
| } | |||
| }, | |||
| meridiem: function (hour, minute, isLower) { | |||
| var hm = hour * 100 + minute; | |||
| if (hm < 600) { | |||
| return '凌晨'; | |||
| } else if (hm < 900) { | |||
| return '早上'; | |||
| } else if (hm < 1130) { | |||
| return '上午'; | |||
| } else if (hm < 1230) { | |||
| return '中午'; | |||
| } else if (hm < 1800) { | |||
| return '下午'; | |||
| } else { | |||
| return '晚上'; | |||
| } | |||
| }, | |||
| calendar: { | |||
| sameDay: function () { | |||
| return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'; | |||
| }, | |||
| nextDay: function () { | |||
| return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'; | |||
| }, | |||
| lastDay: function () { | |||
| return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'; | |||
| }, | |||
| nextWeek: function () { | |||
| var startOfWeek, prefix; | |||
| startOfWeek = moment().startOf('week'); | |||
| prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]'; | |||
| return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; | |||
| }, | |||
| lastWeek: function () { | |||
| var startOfWeek, prefix; | |||
| startOfWeek = moment().startOf('week'); | |||
| prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; | |||
| return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; | |||
| }, | |||
| sameElse: 'LL' | |||
| }, | |||
| ordinalParse: /\d{1,2}(日|月|周)/, | |||
| ordinal: function (number, period) { | |||
| switch (period) { | |||
| case 'd': | |||
| case 'D': | |||
| case 'DDD': | |||
| return number + '日'; | |||
| case 'M': | |||
| return number + '月'; | |||
| case 'w': | |||
| case 'W': | |||
| return number + '周'; | |||
| default: | |||
| return number; | |||
| } | |||
| }, | |||
| relativeTime: { | |||
| future: '%s内', | |||
| past: '%s前', | |||
| s: '几秒', | |||
| m: '1分钟', | |||
| mm: '%d分钟', | |||
| h: '1小时', | |||
| hh: '%d小时', | |||
| d: '1天', | |||
| dd: '%d天', | |||
| M: '1个月', | |||
| MM: '%d个月', | |||
| y: '1年', | |||
| yy: '%d年' | |||
| }, | |||
| week: { | |||
| // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 | |||
| dow: 1, // Monday is the first day of the week. | |||
| doy: 4 // The week that contains Jan 4th is the first week of the year. | |||
| } | |||
| }); | |||
| export default SnackbarHOC()(App) ; | |||
| @@ -1,9 +0,0 @@ | |||
| import React from 'react'; | |||
| import ReactDOM from 'react-dom'; | |||
| import App from './App'; | |||
| it('renders without crashing', () => { | |||
| const div = document.createElement('div'); | |||
| ReactDOM.render(<App />, div); | |||
| ReactDOM.unmountComponentAtNode(div); | |||
| }); | |||
| @@ -1,420 +0,0 @@ | |||
| import React from "react"; | |||
| import axios from 'axios'; | |||
| import md5 from 'md5'; | |||
| import { requestProxy } from "./indexEduplus2RequestProxy"; | |||
| import { broadcastChannelOnmessage ,SetAppModel, isDev, queryString } from 'educoder'; | |||
| import { notification } from 'antd'; | |||
| import cookie from 'react-cookies'; | |||
| import './index.css'; | |||
| const $ = window.$; | |||
| const opens ="79e33abd4b6588941ab7622aed1e67e8"; | |||
| let timestamp; | |||
| let checkSubmitFlg = false; | |||
| let message501=false; | |||
| broadcastChannelOnmessage('refreshPage', () => { | |||
| window.location.reload() | |||
| }) | |||
| function locationurl(list){ | |||
| if (window.location.port === "3007") { | |||
| } else { | |||
| window.location.href=list | |||
| } | |||
| } | |||
| // TODO 开发期多个身份切换 | |||
| let debugType ="" | |||
| if (isDev) { | |||
| const _search = window.location.search; | |||
| let parsed = {}; | |||
| if (_search) { | |||
| parsed = queryString.parse(_search); | |||
| } | |||
| debugType = window.location.search.indexOf('debug=t') != -1 ? 'teacher' : | |||
| window.location.search.indexOf('debug=s') != -1 ? 'student' : | |||
| window.location.search.indexOf('debug=a') != -1 ? 'admin' : parsed.debug || 'admin' | |||
| } | |||
| // 超管 | |||
| // debugType="admin"; | |||
| // 老师 | |||
| //debugType="teacher"; | |||
| // 学生 | |||
| //debugType="student"; | |||
| function clearAllCookie() { | |||
| cookie.remove('_educoder_session', {path: '/'}); | |||
| cookie.remove('autologin_trustie', {path: '/'}); | |||
| setpostcookie() | |||
| } | |||
| clearAllCookie(); | |||
| function setpostcookie() { | |||
| const str =window.location.pathname; | |||
| // console.log(str.indexOf("/wxcode")) | |||
| let newdomain=".educoder.net" | |||
| if(str.indexOf("/wxcode") !== -1){ | |||
| console.log("123") | |||
| cookie.remove('_educoder_session', {path: '/'}); | |||
| cookie.remove('autologin_trustie', {path: '/'}); | |||
| // console.log("开始重写cookis"); | |||
| const _params = window.location.search; | |||
| // console.log("1111"); | |||
| if (_params) { | |||
| // console.log("22222"); | |||
| let _search = _params.split('?')[1]; | |||
| let _educoder_sessions= _search.split('&')[0].split('='); | |||
| cookie.save('_educoder_session',_educoder_sessions[1], { domain:'.educoder.net', path: '/'}); | |||
| let autologin_trusties=_search.split('&')[1].split('='); | |||
| cookie.save('autologin_trustie',autologin_trusties[1], { domain:'.educoder.net', path: '/'}); | |||
| } | |||
| } | |||
| } | |||
| setpostcookie(); | |||
| function railsgettimes(proxy) { | |||
| clearAllCookie() | |||
| if(timestamp&&checkSubmitFlg===false){ | |||
| $.ajax({url:proxy,async:false,success:function(data){ | |||
| if(data.status===0){ | |||
| timestamp=data.message; | |||
| setpostcookie(); | |||
| } | |||
| }}) | |||
| checkSubmitFlg=true | |||
| window.setTimeout(()=>{ | |||
| checkSubmitFlg=false; | |||
| }, 2000); | |||
| }else if(checkSubmitFlg===false){ | |||
| $.ajax({url:proxy,async:false,success:function(data){ | |||
| if(data.status===0){ | |||
| timestamp=data.message; | |||
| setpostcookie(); | |||
| } | |||
| }}) | |||
| checkSubmitFlg=true | |||
| window.setTimeout( ()=>{ | |||
| checkSubmitFlg=false; | |||
| }, 2000); | |||
| } | |||
| } | |||
| window._debugType = debugType; | |||
| export function initAxiosInterceptors(props) { | |||
| initOnlineOfflineListener() | |||
| // TODO 避免重复的请求 https://github.com/axios/axios#cancellation | |||
| // https://github.com/axios/axios/issues/1497 | |||
| // TODO 读取到package.json中的配置? | |||
| var proxy = "http://localhost:3000" | |||
| // proxy = "http://testbdweb.trustie.net" | |||
| // proxy = "http://testbdweb.educoder.net" | |||
| // proxy = "https://testeduplus2.educoder.net" | |||
| //proxy="http://47.96.87.25:48080" | |||
| // proxy="https://pre-newweb.educoder.net" | |||
| // proxy="https://test-newweb.educoder.net" | |||
| // proxy="https://test-jupyterweb.educoder.net" | |||
| // proxy="https://test-newweb.educoder.net" | |||
| // proxy="https://test-jupyterweb.educoder.net" | |||
| //proxy="http://192.168.2.63:3001" | |||
| var //proxy = "http://localhost:3000" | |||
| // proxy="http://123.59.135.93:56666" | |||
| proxy="http://localhost:3000" | |||
| // 在这里使用requestMap控制,避免用户通过双击等操作发出重复的请求; | |||
| // 如果需要支持重复的请求,考虑config里面自定义一个allowRepeat参考来控制 | |||
| const requestMap = {}; | |||
| window.setfalseInRequestMap = function(keyName) { | |||
| requestMap[keyName] = false; | |||
| } | |||
| //响应前的设置 | |||
| axios.interceptors.request.use( | |||
| config => { | |||
| setpostcookie() | |||
| clearAllCookie() | |||
| // config.headers['Content-Type']= 'no-cache' | |||
| // if (token) { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了 | |||
| // config.headers.Authorization = token; | |||
| // } | |||
| // --------------------------------------------- 測試3007连测试服的代码 | |||
| // if (url.indexOf('file_update') != -1 || url.indexOf('game_build') != -1 || url.indexOf('game_status') != -1) { | |||
| // proxy = 'https://testbdweb.trustie.net' | |||
| // } else { | |||
| // proxy = 'http://localhost:3000' | |||
| // } | |||
| // --------------------------------------------- | |||
| // console.log("开始请求了"); | |||
| // console.log(config.url); | |||
| // console.log(window.location.pathname); | |||
| // | |||
| // try { | |||
| // const str =window.location.pathname; | |||
| // if(str.indexOf("/wxcode") !== -1){ | |||
| // // console.log("开始重写cookis"); | |||
| // const _params = window.location.search; | |||
| // // console.log("1111"); | |||
| // if (_params) { | |||
| // // console.log("22222"); | |||
| // let _search = _params.split('?')[1]; | |||
| // var _educoder_sessionmys=""; | |||
| // var autologin_trusties=""; | |||
| // _search.split('&').forEach(item => { | |||
| // const _arr = item.split('='); | |||
| // if(_arr[0]==='_educoder_session'){ | |||
| // cookie.save('_educoder_session',_arr[1], { domain: '.educoder.net', path: '/'}); | |||
| // _educoder_sessionmys=_arr[1]; | |||
| // }else{ | |||
| // cookie.save('autologin_trustie',_arr[1], { domain: '.educoder.net', path: '/'}); | |||
| // autologin_trusties=_arr[1]; | |||
| // } | |||
| // }); | |||
| // try { | |||
| // const autlogins= `_educoder_session=${_educoder_sessionmys}; autologin_trustie=${autologin_trusties} `; | |||
| // config.params = {'Cookie': autlogins} | |||
| // config.headers['Cookie'] =autlogins; | |||
| // // console.log("设置了cookis"); | |||
| // } catch (e) { | |||
| // | |||
| // } | |||
| // try { | |||
| // const autloginysls= `_educoder_session=${_educoder_sessionmys}; autologin_trustie=${autologin_trusties} `; | |||
| // config.params = {'autloginysls': autloginysls} | |||
| // config.headers['Cookie'] =autloginysls; | |||
| // // console.log("设置了cookis"); | |||
| // }catch (e) { | |||
| // | |||
| // } | |||
| // } | |||
| // } | |||
| // }catch (e) { | |||
| // | |||
| // } | |||
| if (config.url.indexOf(proxy) != -1 || config.url.indexOf(':') != -1) { | |||
| return config | |||
| } | |||
| requestProxy(config) | |||
| let url = `/api${config.url}`; | |||
| //qq登录去掉api | |||
| if(config.params&&config.params.redirect_uri!=undefined){ | |||
| if(config.params.redirect_uri.indexOf('otherloginqq')!=-1){ | |||
| url = `${config.url}`; | |||
| } | |||
| } | |||
| if(`${config[0]}`!=`true`){ | |||
| let timestamp = Date.parse(new Date())/1000; | |||
| if (window.location.port === "3007") { | |||
| // let timestamp=railsgettimes(proxy); | |||
| // console.log(timestamp) | |||
| // `https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp` | |||
| railsgettimes( `${proxy}/api/main/first_stamp.json`); | |||
| let newopens=md5(opens+timestamp) | |||
| config.url = `${proxy}${url}`; | |||
| if (config.url.indexOf('?') == -1) { | |||
| config.url = `${config.url}?debug=${debugType}&randomcode=${timestamp}&client_key=${newopens}`; | |||
| } else { | |||
| config.url = `${config.url}&debug=${debugType}&randomcode=${timestamp}&client_key=${newopens}`; | |||
| } | |||
| } else { | |||
| // 加api前缀 | |||
| // railsgettimes(`http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp`); | |||
| railsgettimes( `/api/main/first_stamp.json`); | |||
| let newopens=md5(opens+timestamp) | |||
| config.url = url; | |||
| if (config.url.indexOf('?') == -1) { | |||
| config.url = `${config.url}?randomcode=${timestamp}&client_key=${newopens}`; | |||
| } else { | |||
| config.url = `${config.url}&randomcode=${timestamp}&client_key=${newopens}`; | |||
| } | |||
| } | |||
| setpostcookie(); | |||
| } | |||
| // | |||
| // console.log(config); | |||
| if (config.method === "post") { | |||
| if (requestMap[config.url] === true) { // 避免重复的请求 导致页面f5刷新 也会被阻止 显示这个方法会影响到定制信息 | |||
| // console.log(config); | |||
| // console.log(JSON.parse(config)); | |||
| // console.log(config.url); | |||
| // console.log("被阻止了是重复请求================================="); | |||
| return false; | |||
| } | |||
| } | |||
| // 非file_update请求 | |||
| if (config.url.indexOf('update_file') === -1) { | |||
| requestMap[config.url] = true; | |||
| window.setTimeout("setfalseInRequestMap('"+config.url+"')", 900) | |||
| } | |||
| // setTimeout("setfalseInRequestMap(" + config.url + ")", 1200) | |||
| return config; | |||
| }, | |||
| err => { | |||
| return Promise.reject(err); | |||
| }); | |||
| axios.interceptors.response.use(function (response) { | |||
| // console.log(".............") | |||
| if(response===undefined){ | |||
| return | |||
| } | |||
| const config = response.config | |||
| if (response.data.status === -1) { | |||
| // console.error('error:', response.data.message) | |||
| // throw new Error() | |||
| // https://github.com/axios/axios/issues?utf8=%E2%9C%93&q=cancel+request+in+response+interceptors+ | |||
| // https://github.com/axios/axios/issues/583 | |||
| // message.info(response.data.message || '服务端返回status -1,请联系管理员。'); | |||
| // props.showSnackbar( response.data.message || '服务器异常,请联系管理员。' ) | |||
| if (window.location.pathname.startsWith('/tasks/')) { | |||
| props.showSnackbar( response.data.message || '服务器异常,请联系管理员。' ) | |||
| } else { | |||
| notification.open({ | |||
| message:"提示", | |||
| description: response.data.message || '服务器异常,请联系管理员。', | |||
| style: { | |||
| zIndex: 99999999 | |||
| }, | |||
| }); | |||
| // notification['error']({ | |||
| // message:"提示", | |||
| // description: response.data.message || '服务器异常,请联系管理员。', | |||
| // }); | |||
| } | |||
| throw new axios.Cancel('Operation canceled by the user.'); | |||
| } else { | |||
| // hash跳转 | |||
| // var hash = window.location.hash; | |||
| // if (hash) { | |||
| // hashTimeout && window.clearTimeout(hashTimeout) | |||
| // hashTimeout = setTimeout(() => { | |||
| // var element = document.querySelector(hash); | |||
| // if (element) { | |||
| // element.scrollIntoView(); | |||
| // } | |||
| // }, 400) | |||
| // } | |||
| } | |||
| // if(response.data.status === 401){ | |||
| // console.log("401401401") | |||
| // } | |||
| if (response.data.status === 403||response.data.status === "403") { | |||
| locationurl('/403'); | |||
| } | |||
| if (response.data.status === 404) { | |||
| locationurl('/nopage'); | |||
| } | |||
| if (response.data.status === 500) { | |||
| locationurl('/500'); | |||
| } | |||
| if (response.data.status === 501) { | |||
| if(message501===false){ | |||
| message501=true | |||
| notification.open({ | |||
| message:"提示", | |||
| description:response.data.message || '访问异常,请求不合理', | |||
| style: { | |||
| zIndex: 99999999 | |||
| } | |||
| }) | |||
| } | |||
| window.setTimeout(function () { | |||
| message501=false | |||
| }, 2000); | |||
| } | |||
| // if (response.data.status === 402) { | |||
| // console.log(response.data.status); | |||
| // console.log(response.data); | |||
| // // locationurl(402); | |||
| // } | |||
| // | |||
| // if (response.data.status === 401) { | |||
| // console.log("161"); | |||
| // console.log(config); | |||
| // return config; | |||
| // } | |||
| // if (response.data.status === 407) { | |||
| // 在app js 中解决 Trialapplication | |||
| // // </Trialapplication> | |||
| // ///在appjs | |||
| // notification.open({ | |||
| // message:"提示", | |||
| // description: "账号未认证", | |||
| // }); | |||
| // throw new axios.Cancel('Operation canceled by the user.'); | |||
| // // | |||
| // } | |||
| requestMap[response.config.url] = false; | |||
| setpostcookie(); | |||
| return response; | |||
| }, function (error) { | |||
| return Promise.reject(error); | |||
| }); | |||
| // ----------------------------------------------------------------------------------- | |||
| } | |||
| function initOnlineOfflineListener() { | |||
| const $ = window.$ | |||
| $(window).bind("online", () => { | |||
| notification.destroy() | |||
| notification.success({ | |||
| duration: 2, | |||
| message: '网络恢复正常', | |||
| description: | |||
| '网络恢复正常,感谢使用。', | |||
| }) | |||
| }); | |||
| $(window).bind("offline", () => { | |||
| notification.destroy() | |||
| notification.warning({ | |||
| duration: null, | |||
| message: '网络异常', | |||
| description: | |||
| '网络异常,请检测网络后重试。', | |||
| }) | |||
| }); | |||
| } | |||
| @@ -1,12 +0,0 @@ | |||
| import Loadable from 'react-loadable'; | |||
| import Loading from "./Loading"; | |||
| const CustomLoadable = (loader, loading = Loading) => { | |||
| return Loadable({ | |||
| loader, | |||
| loading | |||
| }) | |||
| } | |||
| export default CustomLoadable | |||
| @@ -1,34 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { BrowserRouter as Router, Route, Link } from "react-router-dom"; | |||
| import { Spin } from 'antd'; | |||
| class Loading extends Component { | |||
| componentDidUpdate(prevProps, prevState) { | |||
| if (!prevProps.error && this.props.error) { | |||
| console.log(this.props.error) | |||
| window.location.reload() | |||
| } | |||
| } | |||
| render() { | |||
| // Loading | |||
| return ( | |||
| <div className="App" style={{minHeight: '800px',width:"100%"}}> | |||
| <style> | |||
| { | |||
| ` | |||
| .margintop{ | |||
| margin-top:20%; | |||
| } | |||
| ` | |||
| } | |||
| </style> | |||
| <Spin size="large" className={"margintop"}/> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default Loading; | |||
| @@ -1,41 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { BrowserRouter as Router, Route, Link } from "react-router-dom"; | |||
| class NotFoundPage extends Component { | |||
| render() { | |||
| return ( | |||
| <div className="App"> | |||
| 404 Page | |||
| <br></br> | |||
| <Link to="/tasks/ixq5euhgrf7y">Index</Link> | |||
| | | |||
| <Link to="/shixuns/uznmbg54/challenges">tpm challenges</Link> | |||
| | | |||
| <Link to="/shixuns/uznmbg54/shixun_discuss">tpm discuss</Link> | |||
| | | |||
| <Link to="/forums/categories/all">forums</Link> | |||
| | | |||
| <Link to="/comment">Comment</Link> | |||
| | | |||
| <Link to="/testMaterial">testMaterial</Link> | |||
| | | |||
| <Link to="/testCodeMirror">testCodeMirror</Link> | |||
| | | |||
| <Link to="/taskList">taskList</Link> | |||
| | | |||
| <Link to="/testRCComponent">testRCComponent</Link> | |||
| | | |||
| <Link to="/tpforums">tpforums</Link> | |||
| | | |||
| <Link to="/testUrlQuery">url-query test</Link> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default NotFoundPage; | |||
| @@ -1,84 +0,0 @@ | |||
| import React, {Component} from "react"; | |||
| import {WordsBtn} from 'educoder'; | |||
| import {Table} from "antd"; | |||
| import {Link,Switch,Route,Redirect} from 'react-router-dom'; | |||
| const echarts = require('echarts'); | |||
| function startechart(data,datanane){ | |||
| var effChart = echarts.init(document.getElementById('shixun_skill_chart')); | |||
| var option = { | |||
| tooltip : { | |||
| trigger: 'item', | |||
| formatter: "{d}% <br/>" | |||
| }, | |||
| legend: { | |||
| // orient: 'vertical', | |||
| // top: 'middle', | |||
| bottom: 50, | |||
| left: 'center', | |||
| data: datanane | |||
| }, | |||
| series : [ | |||
| { | |||
| type: 'pie', | |||
| radius : '65%', | |||
| center: ['50%', '35%'], | |||
| selectedMode: 'single', | |||
| data:data, | |||
| itemStyle: { | |||
| emphasis: { | |||
| shadowBlur: 10, | |||
| shadowOffsetX: 0, | |||
| shadowColor: 'rgba(0, 0, 0, 0.5)' | |||
| } | |||
| } | |||
| } | |||
| ] | |||
| }; | |||
| effChart.setOption(option); | |||
| } | |||
| class Colleagechart extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| } | |||
| } | |||
| componentDidMount() { | |||
| startechart(this.props.data,this.props.datanane) | |||
| } | |||
| componentDidUpdate = (prevProps) => { | |||
| if (prevProps.data!= this.props.data) { | |||
| startechart(this.props.data,this.props.datanane) | |||
| } | |||
| } | |||
| render() { | |||
| let {data}=this.props; | |||
| return ( | |||
| <div> | |||
| <div | |||
| style={{ width:'100%',height:'600px'}} | |||
| id="shixun_skill_chart"> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| } | |||
| export default Colleagechart; | |||
| @@ -1,149 +0,0 @@ | |||
| import React, {Component} from "react"; | |||
| import {WordsBtn} from 'educoder'; | |||
| import {Table} from "antd"; | |||
| import {Link,Switch,Route,Redirect} from 'react-router-dom'; | |||
| const echarts = require('echarts'); | |||
| function startechart(names, values){ | |||
| var effChart = echarts.init(document.getElementById('shixun_skill_charts')); | |||
| var Color = ['#962e66', '#623363', '#CCCCCC', '#9A9A9A', '#FF8080', '#FF80C2', '#B980FF', '#80B9FF', '#6FE9FF', '#4DE8B4', '#F8EF63', '#FFB967']; | |||
| var option = { | |||
| backgroundColor: '#fff', | |||
| grid: { | |||
| left: '3%', | |||
| right: '8%', | |||
| bottom: '15%', | |||
| containLabel: true | |||
| }, | |||
| tooltip: { | |||
| show: "true", | |||
| trigger: 'item', | |||
| formatter: '{c0}', | |||
| backgroundColor: 'rgba(0,0,0,0.7)', // 背景 | |||
| padding: [8, 10], //内边距 | |||
| extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影 | |||
| axisPointer: { // 坐标轴指示器,坐标轴触发有效 | |||
| type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' | |||
| } | |||
| }, | |||
| xAxis: { | |||
| type: 'value', | |||
| axisTick: { | |||
| show: false | |||
| }, | |||
| axisLine: { | |||
| show: true, | |||
| lineStyle: { | |||
| color: '#CCCCCC' | |||
| } | |||
| }, | |||
| splitLine: { | |||
| show: false, | |||
| lineStyle: { | |||
| color: '#CCCCCC' | |||
| } | |||
| }, | |||
| axisLabel: { | |||
| textStyle: { | |||
| color: '#656565', | |||
| fontWeight: 'normal', | |||
| fontSize: '12' | |||
| }, | |||
| formatter: '{value}' | |||
| } | |||
| }, | |||
| yAxis: { | |||
| type: 'category', | |||
| axisLine: { | |||
| lineStyle: { | |||
| color: '#cccccc' | |||
| } | |||
| }, | |||
| splitLine: { | |||
| show: false | |||
| }, | |||
| axisTick: { | |||
| show: false | |||
| }, | |||
| splitArea: { | |||
| show: false | |||
| }, | |||
| axisLabel: { | |||
| inside: false, | |||
| textStyle: { | |||
| color: '#656565', | |||
| fontWeight: 'normal', | |||
| fontSize: '12' | |||
| } | |||
| }, | |||
| data: names | |||
| }, | |||
| series: [{ | |||
| name: '', | |||
| type: 'bar', | |||
| itemStyle: { | |||
| normal: { | |||
| show: true, | |||
| color: function(params) { | |||
| return Color[params.dataIndex] | |||
| }, | |||
| barBorderRadius: 50, | |||
| borderWidth: 0, | |||
| borderColor: '#333' | |||
| } | |||
| }, | |||
| barGap: '0%', | |||
| barCategoryGap: '50%', | |||
| data: values | |||
| } | |||
| ] | |||
| }; | |||
| effChart.setOption(option); | |||
| } | |||
| class Colleagechartzu extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| } | |||
| } | |||
| componentDidMount() { | |||
| startechart(this.props.data,this.props.datavule) | |||
| } | |||
| componentDidUpdate = (prevProps) => { | |||
| if (prevProps.data!= this.props.data) { | |||
| startechart(this.props.data,this.props.datavule) | |||
| } | |||
| } | |||
| render() { | |||
| let {data}=this.props; | |||
| return ( | |||
| <div> | |||
| <div | |||
| style={{ width:'100%',height:'600px'}} | |||
| id="shixun_skill_charts"> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| } | |||
| export default Colleagechartzu; | |||
| @@ -1,213 +0,0 @@ | |||
| .yslstatistic-header { | |||
| width: 100%; | |||
| height: 240px; | |||
| background-image: url('/images/educoder/statistics.jpg'); | |||
| background-size: 100% 100%; | |||
| } | |||
| .yslborder{ | |||
| border: 1px solid; | |||
| } | |||
| .yslstatistic-header-title{ | |||
| flex: 1; | |||
| display: flex; | |||
| align-items: center; | |||
| color: #4CACFF; | |||
| font-size: 32px; | |||
| } | |||
| .yslstatistic-header-content{ | |||
| width: 100%; | |||
| display: flex; | |||
| justify-content: space-around; | |||
| } | |||
| .yslstatistic-header-item{ | |||
| margin-bottom: 22px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| color: #fff; | |||
| } | |||
| .yslstatistic-header-item-label{ | |||
| color: #989898; | |||
| } | |||
| .yslstatistic-base-item-label{ | |||
| width: 217px; | |||
| text-align: center; | |||
| font-size: 16px; | |||
| height: 48px; | |||
| line-height: 48px; | |||
| color: #686868; | |||
| background: #F5F5F5; | |||
| border-top: 1px solid #EBEBEB; | |||
| } | |||
| .yslstatistic-base-item-labels{ | |||
| width: 217px; | |||
| text-align: center; | |||
| height: 100px; | |||
| line-height: 100px; | |||
| background: #ffffff; | |||
| border-top: 1px solid #EBEBEB; | |||
| border-bottom: 1px solid #EBEBEB; | |||
| } | |||
| .yslstatistic-base-item-labelsp{ | |||
| color: #000000; | |||
| font-size: 24px; | |||
| } | |||
| .yslstatistic-base-item-labelsspan{ | |||
| color: #000000; | |||
| margin-left: 5px; | |||
| font-size: 16px; | |||
| } | |||
| .jibenshiyong100{ | |||
| width: 100%; | |||
| } | |||
| .yslstatistic-header-item-content{ | |||
| font-size: 24px; | |||
| } | |||
| /* 中间居中 */ | |||
| .intermediatecenter{ | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| /* 简单居中 */ | |||
| .intermediatecenterysls{ | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .spacearound{ | |||
| display: flex; | |||
| justify-content: space-around; | |||
| } | |||
| .spacebetween{ | |||
| display: flex; | |||
| justify-content: space-between; | |||
| } | |||
| /* 头顶部居中 */ | |||
| .topcenter{ | |||
| display: -webkit-flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| } | |||
| /* x轴正方向排序 */ | |||
| /* 一 二 三 四 五 六 七 八 */ | |||
| .sortinxdirection{ | |||
| display: flex; | |||
| flex-direction:row; | |||
| } | |||
| /* x轴反方向排序 */ | |||
| /* 八 七 六 五 四 三 二 一 */ | |||
| .xaxisreverseorder{ | |||
| display: flex; | |||
| flex-direction:row-reverse; | |||
| } | |||
| /* 垂直布局 正方向*/ | |||
| /* 一 | |||
| 二 | |||
| 三 | |||
| 四 | |||
| 五 | |||
| 六 | |||
| 七 | |||
| 八 */ | |||
| .verticallayout{ | |||
| display: flex; | |||
| flex-direction:column; | |||
| } | |||
| /* 垂直布局 反方向*/ | |||
| .reversedirection{ | |||
| display: flex; | |||
| flex-direction:column-reverse; | |||
| } | |||
| .h4{ | |||
| font-size: 1.5rem; | |||
| font-weight: 500 !important; | |||
| } | |||
| .ysllinjibenshiyong{ | |||
| font-weight: 500; | |||
| line-height: 1.2; | |||
| padding: 2rem 1.25rem; | |||
| border-bottom: unset; | |||
| background:#fff; | |||
| } | |||
| .linjibenshiyong{ | |||
| font-weight: 500; | |||
| line-height: 1.2; | |||
| padding: 2rem 1.25rem; | |||
| border-bottom: unset; | |||
| background:#fff; | |||
| box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1); | |||
| border-radius:2px; | |||
| } | |||
| .yslslinjibenshiyong{ | |||
| font-weight: 500; | |||
| line-height: 1.2; | |||
| border-bottom: unset; | |||
| box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1); | |||
| border-radius:2px; | |||
| } | |||
| .yinyin{ | |||
| background: #fff; | |||
| box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1); | |||
| border-radius:2px; | |||
| } | |||
| .edu-back-eeee{ | |||
| background:#EEEEEE !important; | |||
| } | |||
| .mt-4{ | |||
| margin-top: 1.5rem !important; | |||
| } | |||
| .statistic-label{ | |||
| padding: 2rem 1.25rem; | |||
| font-size: 1.5rem; | |||
| font-weight: 400 !important; | |||
| } | |||
| .mb50{ | |||
| padding-bottom: 50px !important; | |||
| } | |||
| .mt40{ | |||
| margin-top: 40px; | |||
| } | |||
| .mb80{ | |||
| margin-bottom: 80px; | |||
| } | |||
| .task-hide{overflow:hidden; white-space: nowrap; text-overflow:ellipsis;} | |||
| a:hover{ | |||
| color:#0056b3; | |||
| } | |||
| .color-blue{ | |||
| color: #4CACFF; | |||
| } | |||
| .color-huang{ | |||
| color:#ffc107 !important | |||
| } | |||
| .maxnamewidth105{ | |||
| max-width: 105px; | |||
| overflow:hidden; | |||
| text-overflow:ellipsis; | |||
| white-space:nowrap; | |||
| cursor: default; | |||
| } | |||
| .maxnamewidth247{ | |||
| max-width: 247px; | |||
| overflow:hidden; | |||
| text-overflow:ellipsis; | |||
| white-space:nowrap; | |||
| cursor: default; | |||
| } | |||
| .maxnamewidth340{ | |||
| max-width: 340px; | |||
| overflow:hidden; | |||
| text-overflow:ellipsis; | |||
| white-space:nowrap; | |||
| cursor: default; | |||
| } | |||
| @@ -1,5 +0,0 @@ | |||
| import md5 from 'md5'; | |||
| export function setmiyah(logins){ | |||
| const opens ="79e33abd4b6588941ab7622aed1e67e8"; | |||
| return md5(opens+logins); | |||
| } | |||
| @@ -1,19 +0,0 @@ | |||
| /** | |||
| EDU_ADMIN = 1 # 超级管理员 | |||
| EDU_BUSINESS = 2 # 运营人员 | |||
| EDU_SHIXUN_MANAGER = 3 # 实训管理员 | |||
| EDU_SHIXUN_MEMBER = 4 # 实训成员 | |||
| EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师 | |||
| EDU_GAME_MANAGER = 6 # TPI的创建者 | |||
| EDU_TEACHER = 7 # 平台老师,但是未认证 | |||
| EDU_NORMAL = 8 # 普通用户 | |||
| */ | |||
| export const EDU_ADMIN = 1 // 超级管理员 | |||
| export const EDU_BUSINESS = 2 // # 运营人员 | |||
| export const EDU_SHIXUN_MANAGER = 3 // 实训管理员 | |||
| export const EDU_SHIXUN_MEMBER = 4 // 实训成员 | |||
| export const EDU_CERTIFICATION_TEACHER = 5 // 平台认证的老师 | |||
| export const EDU_GAME_MANAGER = 6 // TPI的创建者 | |||
| export const EDU_TEACHER = 7 // 平台老师,但是未认证 | |||
| export const EDU_NORMAL = 8 // 普通用户 | |||
| @@ -1,69 +0,0 @@ | |||
| import moment from "moment"; | |||
| // 处理整点 半点 | |||
| // 取传入时间往后的第一个半点 | |||
| export function handleDateString(dateString) { | |||
| if (!dateString) return dateString; | |||
| const ar = dateString.split(':') | |||
| if (ar[1] == '00' || ar[1] == '30') { | |||
| return dateString | |||
| } | |||
| const miniute = parseInt(ar[1]); | |||
| if (miniute < 30 || miniute == 60) { | |||
| return [ar[0], '30'].join(':') | |||
| } | |||
| if (miniute < 60) { | |||
| // 加一个小时 | |||
| const tempStr = [ar[0], '00'].join(':'); | |||
| const format = "YYYY-MM-DD HH:mm"; | |||
| const _moment = moment(tempStr, format) | |||
| _moment.add(1, 'hours') | |||
| return _moment.format(format) | |||
| } | |||
| return dateString | |||
| } | |||
| // 给moment对象取下一个半点或整点 | |||
| export function getNextHalfHourOfMoment(moment) { | |||
| if (!moment) { | |||
| return moment | |||
| } | |||
| const minutes = moment.minutes() | |||
| if (minutes < 30) { | |||
| moment.minutes(30) | |||
| } else if (minutes < 60) { | |||
| moment.minutes(0).add(1, 'hours') | |||
| } | |||
| return moment | |||
| } | |||
| export function formatDuring(mss){ | |||
| var days = parseInt(mss / (1000 * 60 * 60 * 24)); | |||
| var hours = parseInt((mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | |||
| var minutes = parseInt((mss % (1000 * 60 * 60)) / (1000 * 60)); | |||
| // console.log("formatDuringformatDuring"); | |||
| // console.log(days); | |||
| // console.log(hours); | |||
| // console.log(minutes); | |||
| // console.log(Math.abs(days)); | |||
| // console.log(Math.abs(hours)); | |||
| // console.log(Math.abs(minutes)); | |||
| try { | |||
| days = Math.abs(days); | |||
| } catch (e) { | |||
| } | |||
| try { | |||
| hours = Math.abs(hours); | |||
| } catch (e) { | |||
| } | |||
| try { | |||
| minutes = Math.abs(minutes); | |||
| } catch (e) { | |||
| } | |||
| return days + "天" + hours + "小时" + minutes + "分"; | |||
| } | |||
| @@ -1,8 +0,0 @@ | |||
| export function isDev() { | |||
| return window.location.port === "3007"; | |||
| } | |||
| // const isMobile | |||
| export const isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())); | |||
| // const isWeiXin = (/MicroMessenger/i.test(navigator.userAgent.toLowerCase())); | |||
| @@ -1,70 +0,0 @@ | |||
| const $ = window.$; | |||
| export function trigger(eventName, data) { | |||
| $(window).trigger(eventName, data); | |||
| } | |||
| export function on(eventName, callback) { | |||
| $(window).on(eventName, (event, data)=>{ | |||
| callback && callback(event, data) | |||
| }); | |||
| } | |||
| export function off(eventName) { | |||
| $(window).off(eventName); | |||
| } | |||
| // https://stackoverflow.com/questions/28230845/communication-between-tabs-or-windows | |||
| const broadcastChannelMap = {} | |||
| const localStorageMap = {} | |||
| function postMessageByLocalStorage(eventName, message) { | |||
| console.log('storage event trigger:', eventName) | |||
| localStorage.setItem(eventName, JSON.stringify(message)); | |||
| } | |||
| function onMessageByLocalStorage(eventName, callback) { | |||
| console.log('storage event register:', eventName) | |||
| localStorageMap[eventName] = callback; | |||
| } | |||
| window.addEventListener("storage", function(ev) { | |||
| const cb = localStorageMap[ev.key]; | |||
| // console.log('storage event:', ev) | |||
| if (cb) { | |||
| cb(JSON.parse(ev.newValue)) | |||
| } | |||
| }); | |||
| export function broadcastChannelPostMessage(eventName, message) { | |||
| if (!window.BroadcastChannel) { | |||
| console.error('浏览器不支持BroadcastChannel') | |||
| postMessageByLocalStorage(eventName, message) | |||
| return; | |||
| } | |||
| var bc; | |||
| if (!broadcastChannelMap[eventName]) { | |||
| bc = new window.BroadcastChannel(eventName); | |||
| broadcastChannelMap[eventName] = bc | |||
| } else { | |||
| bc = broadcastChannelMap[eventName] | |||
| } | |||
| bc.postMessage(message); /* send */ | |||
| } | |||
| export function broadcastChannelOnmessage(eventName, callback) { | |||
| if (!window.BroadcastChannel) { | |||
| console.error('浏览器不支持BroadcastChannel') | |||
| onMessageByLocalStorage(eventName, callback) | |||
| return; | |||
| } | |||
| var bc; | |||
| if (!broadcastChannelMap[eventName]) { | |||
| bc = new window.BroadcastChannel(eventName); | |||
| broadcastChannelMap[eventName] = bc | |||
| } else { | |||
| bc = broadcastChannelMap[eventName] | |||
| } | |||
| bc.onmessage = function (ev) { | |||
| console.log(ev); | |||
| callback && callback(ev) | |||
| } | |||
| } | |||
| @@ -1,28 +0,0 @@ | |||
| export function IEVersion(){ | |||
| var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 | |||
| var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器 | |||
| var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器 | |||
| var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1; | |||
| if(isIE) { | |||
| var reIE = new RegExp("MSIE (\\d+\\.\\d+);"); | |||
| reIE.test(userAgent); | |||
| var fIEVersion = parseFloat(RegExp["$1"]); | |||
| if(fIEVersion == 7) { | |||
| return 7; | |||
| } else if(fIEVersion == 8) { | |||
| return 8; | |||
| } else if(fIEVersion == 9) { | |||
| return 9; | |||
| } else if(fIEVersion == 10) { | |||
| return 10; | |||
| } else { | |||
| return 6;//IE版本<=7 | |||
| } | |||
| } else if(isEdge) { | |||
| return 'edge';//edge | |||
| } else if(isIE11) { | |||
| return 11; //IE11 | |||
| }else{ | |||
| return -1;//不是ie浏览器 | |||
| } | |||
| } | |||
| @@ -1,29 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import {Spin} from 'antd'; | |||
| class LoadingSpin extends Component{ | |||
| constructor(props) { | |||
| super(props) | |||
| } | |||
| render(){ | |||
| const { style } = this.props; | |||
| return( | |||
| <div className="edu-tab-con-box clearfix edu-txt-center" style={style}> | |||
| <style> | |||
| {` | |||
| .edu-tab-con-box{ | |||
| padding:100px 0px; | |||
| } | |||
| .ant-modal-body .edu-tab-con-box{ | |||
| padding:0px!important; | |||
| } | |||
| img.edu-nodata-img{ | |||
| margin: 40px auto 20px; | |||
| } | |||
| `} | |||
| </style> | |||
| <Spin tip="正在获取相关数据..."/> | |||
| </div> | |||
| ) | |||
| } | |||
| } | |||
| export default LoadingSpin; | |||
| @@ -1,61 +0,0 @@ | |||
| import moment from 'moment' | |||
| const log = require('loglevel'); | |||
| log.enableAll(); | |||
| // 获取后可以改变日志级别 | |||
| window.getLog = () => { | |||
| return log; | |||
| } | |||
| window._logWithTimeStamp = true; | |||
| const timeStamp = () => { | |||
| if (window._logWithTimeStamp) { | |||
| return `[${moment().format('hh:mm:ss')}] ` | |||
| } | |||
| return '' | |||
| } | |||
| /* | |||
| 带trace的、默认折叠起来的控制台输出 | |||
| 第一个参数最好传入string类型的标识,接着可以跟任意类型任意个数的参数,各个参数都会打印到控制台 | |||
| */ | |||
| export function trace_collapse(content) { | |||
| if (console.groupCollapsed) { | |||
| console.groupCollapsed(typeof content == 'string' ? content : 'trace_collapse'); | |||
| log.trace(arguments); | |||
| console.groupEnd(); | |||
| } else { | |||
| trace(content) | |||
| } | |||
| } | |||
| export function trace(content) { | |||
| log.trace(content); | |||
| } | |||
| export function debug(content) { | |||
| log.debug(content); | |||
| } | |||
| export function info(content) { | |||
| log.info(content); | |||
| } | |||
| export function warn(content) { | |||
| log.warn(content); | |||
| } | |||
| export function error(content) { | |||
| log.error(content); | |||
| } | |||
| export function trace_c(content) { | |||
| log.trace(`${timeStamp()}%c${content}`, 'color:magenta;'); | |||
| } | |||
| export function debug_c(content) { | |||
| log.debug(`${timeStamp()}%c${content}`, 'color:cyan;'); | |||
| } | |||
| export function info_c(content) { | |||
| log.info(`${timeStamp()}%c${content}`, 'color:blue;'); | |||
| } | |||
| export function warn_c(content) { | |||
| log.warn(`${timeStamp()}%c${content}`, 'color:crimson;'); | |||
| } | |||
| export function error_c(content) { | |||
| log.error(`${timeStamp()}%c${content}`, 'color:red;'); | |||
| } | |||
| @@ -1,15 +0,0 @@ | |||
| import { queryString } from 'educoder' | |||
| export function updatePageParams(pageNum, props) { | |||
| const url = props.match.url | |||
| const _search = props.location.search; | |||
| let parsed = {}; | |||
| if (_search) { | |||
| parsed = queryString.parse(_search); | |||
| } | |||
| // 修改page參數 | |||
| parsed.page = pageNum | |||
| props.history.push(`${url}?${queryString.stringify(parsed)}`) | |||
| } | |||
| @@ -1,33 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { SnackbarHOC } from 'educoder'; | |||
| import { TPMIndexHOC } from '../modules/tpm/TPMIndexHOC'; | |||
| import {Spin,Alert} from 'antd'; | |||
| class ShowSpin extends Component{ | |||
| constructor(props) { | |||
| super(props) | |||
| } | |||
| render() { | |||
| let marigin={ | |||
| width: '100%', | |||
| minHeight: '500px', | |||
| } | |||
| return ( | |||
| <Spin style={marigin}> | |||
| <Alert | |||
| style={marigin} | |||
| type="info" | |||
| /> | |||
| </Spin> | |||
| ) | |||
| } | |||
| } | |||
| export default SnackbarHOC() ( TPMIndexHOC(ShowSpin) ); | |||
| @@ -1,91 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import Snackbar from 'material-ui/Snackbar'; | |||
| import Fade from 'material-ui/transitions/Fade'; | |||
| import { notification } from 'antd' | |||
| export function SnackbarHOC(options = {}) { | |||
| return function wrap(WrappedComponent) { | |||
| return class Wrapper extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.showSnackbar = this.showSnackbar.bind(this) | |||
| this.state = { | |||
| snackbarText: '', | |||
| snackbarOpen: false, | |||
| } | |||
| } | |||
| handleSnackbarClose() { | |||
| this.setState({ | |||
| snackbarOpen: false, | |||
| snackbarVertical: '', | |||
| snackbarHorizontal: '', | |||
| }) | |||
| } | |||
| // 全局的snackbar this.props.showSnackbar调用即可 | |||
| // showSnackbar(description, message = "提示",icon) { | |||
| // // this.setState({ | |||
| // // snackbarOpen: true, | |||
| // // snackbarText: text, | |||
| // // snackbarVertical: vertical, | |||
| // // snackbarHorizontal: horizontal, | |||
| // // }) | |||
| // const data = { | |||
| // message, | |||
| // description | |||
| // } | |||
| // if (icon) { | |||
| // data.icon = icon; | |||
| // } | |||
| // notification.open(data); | |||
| // } | |||
| showSnackbar(text, vertical, horizontal) { | |||
| this.setState({ | |||
| snackbarOpen: true, | |||
| snackbarText: text, | |||
| snackbarVertical: vertical, | |||
| snackbarHorizontal: horizontal, | |||
| }) | |||
| } | |||
| //个别情况需要走 | |||
| showNotification = (description, message = "提示", icon) => { | |||
| const data = { | |||
| message, | |||
| description | |||
| } | |||
| if (icon) { | |||
| data.icon = icon; | |||
| } | |||
| notification.open(data); | |||
| } | |||
| render() { | |||
| const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state; | |||
| return ( | |||
| <React.Fragment> | |||
| <Snackbar | |||
| className={"rootSnackbar"} | |||
| style={{zIndex:30000}} | |||
| open={this.state.snackbarOpen} | |||
| autoHideDuration={3000} | |||
| anchorOrigin={{ vertical: this.state.snackbarVertical || 'top' | |||
| , horizontal: this.state.snackbarHorizontal || 'center' }} | |||
| onClose={() => this.handleSnackbarClose()} | |||
| transition={Fade} | |||
| SnackbarContentProps={{ | |||
| 'aria-describedby': 'message-id', | |||
| }} | |||
| resumeHideDuration={2000} | |||
| message={<span id="message-id">{this.state.snackbarText}</span>} | |||
| /> | |||
| <WrappedComponent {...this.props} showSnackbar={ this.showSnackbar } showNotification= { this.showNotification } > | |||
| </WrappedComponent> | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,14 +0,0 @@ | |||
| import store from 'store' | |||
| export function toStore(key, val) { | |||
| let _config = store.get('__ec'); | |||
| if (!_config) _config = {}; | |||
| _config[key] = val; | |||
| store.set('__ec', _config) | |||
| } | |||
| export function fromStore(key, defaultVal) { | |||
| let _config = store.get('__ec'); | |||
| if (!_config) return defaultVal; | |||
| return _config[key] === undefined ? defaultVal : _config[key]; | |||
| } | |||
| @@ -1,79 +0,0 @@ | |||
| import { bytesToSize, getUrl, getUrl2 } from 'educoder'; | |||
| const $ = window.$ | |||
| export function isImageExtension(fileName) { | |||
| return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false | |||
| } | |||
| export function markdownToHTML(oldContent, selector) { | |||
| window.$('#md_div').html('') | |||
| // markdown to html | |||
| if (selector && oldContent && oldContent.startsWith('<p')) { // 普通html处理 | |||
| window.$('#' + selector).addClass('renderAsHtml') | |||
| window.$('#' + selector).html(oldContent) | |||
| } else { | |||
| try { | |||
| $("#"+selector).html('') | |||
| // selector || | |||
| var markdwonParser = window.editormd.markdownToHTML(selector || "md_div", { | |||
| markdown: oldContent, // .replace(/▁/g,"▁▁▁"), | |||
| emoji: true, | |||
| htmlDecode: "style,script,iframe", // you can filter tags decode | |||
| taskList: true, | |||
| tex: true, // 默认不解析 | |||
| flowChart: true, // 默认不解析 | |||
| sequenceDiagram: true // 默认不解析 | |||
| }); | |||
| } catch(e) { | |||
| console.error(e) | |||
| } | |||
| // selector = '.' + selector | |||
| if (selector) { | |||
| return; | |||
| } | |||
| const content = window.$('#md_div').html() | |||
| if (selector) { | |||
| window.$(selector).html(content) | |||
| } | |||
| return content | |||
| } | |||
| } | |||
| function _doDownload(options) { | |||
| $.fileDownload(getUrl() + "/api" + options.url, { | |||
| successCallback: options.successCallback, | |||
| failCallback: options.failCallback | |||
| }); | |||
| } | |||
| export function downloadFile(options) { | |||
| if ($.fileDownload) { | |||
| _doDownload(options) | |||
| } else { | |||
| const _url_origin = getUrl2() | |||
| $.getScript( | |||
| `${_url_origin}/javascripts/download/jquery.fileDownload.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| _doDownload(options) | |||
| }); | |||
| } | |||
| } | |||
| export function appendFileSizeToUploadFile(item) { | |||
| return `${item.title}${uploadNameSizeSeperator}${item.filesize}` | |||
| } | |||
| export function appendFileSizeToUploadFileAll(fileList) { | |||
| return fileList.map(item => { | |||
| if (item.name.indexOf(uploadNameSizeSeperator) == -1) { | |||
| return Object.assign({}, item, {name: `${item.name}${uploadNameSizeSeperator}${bytesToSize(item.size)}`}) | |||
| } | |||
| return item | |||
| }) | |||
| } | |||
| export const uploadNameSizeSeperator = ' ' | |||
| export const sortDirections = ["ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", | |||
| "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", | |||
| "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", | |||
| "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", ] | |||
| @@ -1,6 +0,0 @@ | |||
| export function bytesToSize(bytes) { | |||
| var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | |||
| if (bytes == 0) return '0 Byte'; | |||
| var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); | |||
| return parseFloat(bytes / Math.pow(1024, i), 2).toFixed(1) + ' ' + sizes[i]; | |||
| } | |||
| @@ -1,187 +0,0 @@ | |||
| import React from "react"; | |||
| import md5 from 'md5'; | |||
| import {Input} from "antd"; | |||
| const { Search } = Input; | |||
| const $ = window.$; | |||
| const isDev = window.location.port == 3007; | |||
| export const TEST_HOST = "https://test-newweb.educoder.net" | |||
| export function getImageUrl(path) { | |||
| // https://www.educoder.net | |||
| // https://testbdweb.trustie.net | |||
| // const local = 'http://localhost:3000' | |||
| const local = 'https://test-newweb.educoder.net' | |||
| if (isDev) { | |||
| return `${local}/${path}` | |||
| } | |||
| return `/${path}`; | |||
| } | |||
| export function setImagesUrl(path){ | |||
| const local = 'https://test-newweb.educoder.net' | |||
| let firstStr=path.substr(0,1); | |||
| // console.log(firstStr); | |||
| if(firstStr=="/"){ | |||
| return isDev?`${local}${path}`:`${path}`; | |||
| }else{ | |||
| return isDev?`${local}/${path}`:`/${path}`; | |||
| } | |||
| } | |||
| export function getUrl(path, goTest) { | |||
| // https://www.educoder.net | |||
| // https://testbdweb.trustie.net | |||
| // 如果想所有url定位到测试版,可以反注释掉下面这行 | |||
| //goTest = true | |||
| // testbdweb.educoder.net testbdweb.trustie.net | |||
| // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000' | |||
| // const local = 'https://testeduplus2.educoder.net' | |||
| const local = 'http://localhost:3007' | |||
| if (isDev) { | |||
| return `${local}${path?path:''}` | |||
| } | |||
| return `${path ? path: ''}`; | |||
| } | |||
| export function getUrlmys(path, goTest) { | |||
| // https://www.educoder.net | |||
| // https://testbdweb.trustie.net | |||
| // 如果想所有url定位到测试版,可以反注释掉下面这行 | |||
| //goTest = true | |||
| // testbdweb.educoder.net testbdweb.trustie.net | |||
| // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000' | |||
| // const local = 'https://testeduplus2.educoder.net' | |||
| const local = 'https://test-jupyterweb.educoder.net' | |||
| if (isDev) { | |||
| return `${local}${path?path:''}` | |||
| } | |||
| return `${path ? path: ''}`; | |||
| } | |||
| export function getStaticUrl() { | |||
| const local = TEST_HOST; | |||
| if (isDev) { | |||
| return local | |||
| } | |||
| // todo cdn | |||
| return '' | |||
| } | |||
| export function getUrl2(path, goTest) { | |||
| const local = 'http://localhost:3000' | |||
| if (isDev) { | |||
| return `${local}${path?path:''}` | |||
| } | |||
| return `${path ? path: ''}`; | |||
| } | |||
| const newopens ="79e33abd4b6588941ab7622aed1e67e8"; | |||
| let newtimestamp; | |||
| let checkSubmitFlgs = false; | |||
| function railsgettimess(proxy) { | |||
| if(checkSubmitFlgs===false){ | |||
| $.ajax({url:proxy, | |||
| async:false,success:function(data){ | |||
| if(data.status===0){ | |||
| newtimestamp=data.message; | |||
| checkSubmitFlgs = true; | |||
| } | |||
| }}) | |||
| window.setTimeout(function () { | |||
| checkSubmitFlgs=false; | |||
| }, 2500); | |||
| } | |||
| } | |||
| export function Railsgettimes() { | |||
| railsgettimess(`${getUrl()}/api/main/first_stamp.json`); | |||
| // railsgettimess(`https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp`); | |||
| } | |||
| export function getmyUrl(geturl) { | |||
| return `${getUrl()}${geturl}`; | |||
| } | |||
| export function getUploadActionUrl(path, goTest) { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}`; | |||
| } | |||
| export function getUploadActionUrltwo(id) { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}` | |||
| } | |||
| export function getUploadActionUrlthree() { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| return `${getUrlmys()}/api/jupyters/import_with_tpm.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}` | |||
| } | |||
| export function getUploadActionUrlOfAuth(id) { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}` | |||
| } | |||
| export function getRandomNumber(type) { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| return type===true?`randomcode=${newtimestamp}&client_key=${anewopens}`:`?randomcode=${newtimestamp}&client_key=${anewopens}` | |||
| } | |||
| export function test(path) { | |||
| return `${path}`; | |||
| } | |||
| export function toPath(path) { | |||
| window.open(path, '_blank'); | |||
| } | |||
| export function getTaskUrlById(id) { | |||
| return `/tasks/${id}` | |||
| } | |||
| export function getRandomcode(url) { | |||
| Railsgettimes() | |||
| let anewopens=md5(newopens+newtimestamp); | |||
| if (url.indexOf('?') == -1) { | |||
| return `${url}?randomcode=${newtimestamp}&client_key=${anewopens}` | |||
| }else { | |||
| return `${url}&randomcode=${newtimestamp}&client_key=${anewopens}` | |||
| } | |||
| } | |||
| export function htmlEncode(str) { | |||
| var s = ""; | |||
| if (str.length === 0) { | |||
| return ""; | |||
| } | |||
| s = str.replace(/&/g, "&"); | |||
| s = s.replace(/</g, "<"); | |||
| s = s.replace(/>/g, ">"); | |||
| s = s.replace(/ /g, " "); | |||
| s = s.replace(/\'/g, "'");//IE下不支持实体名称 | |||
| s = s.replace(/\"/g, """); | |||
| return s; | |||
| } | |||
| export function publicSearchs(Placeholder,onSearch,onInputs,onChanges,loadings) { | |||
| return(<Search | |||
| placeholder= { Placeholder || "请输入内容进行搜索" } | |||
| onSearch={onSearch} | |||
| // value={searchValue} | |||
| onInput={onInputs} | |||
| onChange={onChanges} | |||
| loading={loadings||false} | |||
| allowClear={true} | |||
| ></Search>) | |||
| } | |||
| @@ -1,51 +0,0 @@ | |||
| const queryString = { | |||
| stringify: function(params) { | |||
| let paramsUrl = ''; | |||
| for (let key in params) { | |||
| // https://stackoverflow.com/questions/6566456/how-to-serialize-an-object-into-a-list-of-url-query-parameters | |||
| if (params[key] != undefined) { | |||
| if (params[key].constructor === Array) { | |||
| for (let singleArrIndex of params[key]) { | |||
| paramsUrl = paramsUrl + key + '[]=' + singleArrIndex + '&' | |||
| } | |||
| } else { | |||
| paramsUrl += `${key}=${encodeURIComponent(params[key])}&` | |||
| } | |||
| } | |||
| } | |||
| if (paramsUrl == '') { | |||
| return ''; | |||
| } | |||
| paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1); | |||
| return paramsUrl; | |||
| }, | |||
| parse: function(search) { | |||
| // ?a=1&b=2 | |||
| if (!search) { | |||
| return {} | |||
| } | |||
| if (search.startsWith('?')) { | |||
| search = search.substring(1); | |||
| } | |||
| if (!search) { | |||
| return {} | |||
| } | |||
| const keyValArray = search.split('&'); | |||
| const result = {} | |||
| keyValArray.forEach(keyValItem => { | |||
| const keyAndVal = keyValItem.split('='); | |||
| result[keyAndVal[0]] = keyAndVal[1] | |||
| }) | |||
| return result; | |||
| } | |||
| } | |||
| /* | |||
| query-string用不了 | |||
| Failed to minify the code from this file: | |||
| ./node_modules/_query-string@6.1.0@query-string/index.js:8 | |||
| Read more here: http://bit.ly/2tRViJ9 | |||
| */ | |||
| module.exports = queryString | |||
| @@ -1,28 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import { Modal,Input, Tooltip} from "antd"; | |||
| class ConditionToolTip extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| this.state={ | |||
| } | |||
| } | |||
| render(){ | |||
| let { condition } = this.props; | |||
| return( | |||
| <React.Fragment> | |||
| { | |||
| condition ? | |||
| <Tooltip placement="bottom" {...this.props}> | |||
| {this.props.children} | |||
| </Tooltip> : | |||
| <React.Fragment> | |||
| {this.props.children} | |||
| </React.Fragment> | |||
| } | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default ConditionToolTip; | |||
| @@ -1,288 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { getUrl2, isDev } from 'educoder' | |||
| const $ = window.$ | |||
| let _url_origin = getUrl2() | |||
| // let _url_origin = `http://47.96.87.25:48080`; | |||
| function save_avatar(){ | |||
| // if($(img_lg).html().trim() == ""){ | |||
| // $("#avatar-name").html("请先选择图片上传").css("color", 'red'); | |||
| // } else { | |||
| // $("#avatar-name").html("").css("color", '#333'); | |||
| const previewId = this.props.previewId | |||
| var img_lg = document.getElementById(previewId || 'img-preview'); | |||
| // 截图小的显示框内的内容 | |||
| window.html2canvas(img_lg).then(function(canvas) { | |||
| // for test | |||
| // document.getElementById('canvasWrap').appendChild(canvas); | |||
| var dataUrl = canvas.toDataURL("image/jpeg"); | |||
| console.log(dataUrl) | |||
| // TODO upload base64 image data to server | |||
| }); | |||
| return | |||
| // 老版接口: | |||
| // html2canvas(img_lg, { | |||
| // allowTaint: true, | |||
| // taintTest: false, | |||
| // onrendered: function(canvas) { | |||
| // canvas.id = "mycanvas"; | |||
| // //生成base64图片数据 | |||
| // var dataUrl = canvas.toDataURL("image/jpeg"); | |||
| // console.log(dataUrl) | |||
| // var newImg = document.getElementById("showImg"); | |||
| // newImg.src = dataUrl; | |||
| // return; | |||
| // imagesAjax(dataUrl); | |||
| // $(".avatar-save").attr("disabled","true"); | |||
| // } | |||
| // }); | |||
| // } | |||
| } | |||
| /** | |||
| props 说明: | |||
| imageId 源图片标签的id | |||
| previewId crop后预览dom的id | |||
| imageSrc 源图片src | |||
| width 数字格式 | |||
| height 数字格式 | |||
| */ | |||
| class Cropper extends Component { | |||
| state = { | |||
| }; | |||
| handleChange = (info) => { | |||
| } | |||
| componentDidMount() { | |||
| this.options = { | |||
| aspectRatio: 1, | |||
| crop(event) { | |||
| // console.log(event.detail.x); | |||
| // console.log(event.detail.y); | |||
| // console.log(event.detail.width); | |||
| // console.log(event.detail.height); | |||
| // console.log(event.detail.rotate); | |||
| // console.log(event.detail.scaleX); | |||
| // console.log(event.detail.scaleY); | |||
| }, | |||
| preview: this.props.previewId ? `#${this.props.previewId}` : '.img-preview', | |||
| } | |||
| if (!window.Cropper) { | |||
| $.ajaxSetup({ | |||
| cache: true | |||
| }); | |||
| const _isDev = isDev() | |||
| let _path = _isDev ? 'public' : 'build' | |||
| $('head').append($('<link rel="stylesheet" type="text/css" />') | |||
| .attr('href', `${_url_origin}/react/${_path}/js/cropper/cropper.min.css`)); | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/cropper/cropper.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| }); | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/cropper/html2canvas.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| }); | |||
| } | |||
| setTimeout(() => { | |||
| const image = document.getElementById(this.props.imageId || '__image'); | |||
| this.cropper = new window.Cropper(image, this.options); | |||
| }, 1200) | |||
| } | |||
| renew = (image) => { | |||
| this.cropper && this.cropper.destroy(); | |||
| this.cropper = new window.Cropper(image, this.options); | |||
| } | |||
| render() { | |||
| const { width, height, previewId, imageSrc } = this.props; | |||
| return ( | |||
| <div> | |||
| {/* This rule is very important, please do not ignore this! */} | |||
| <style>{` | |||
| .wrapper { | |||
| width: ${ width ? (width+'px') : '500px'}; | |||
| height: ${ height ? (height+'px') : '500px'}; | |||
| border: 1px solid #eee; | |||
| } | |||
| img { | |||
| max-width: 100%; | |||
| } | |||
| .preview-lg { | |||
| overflow: hidden; | |||
| background-color: #fff; | |||
| border-radius: 50%; | |||
| text-align: center; | |||
| } | |||
| `}</style> | |||
| <div className="wrapper"> | |||
| {/* http://localhost:3007/images/footNavLogo.png 图片转了后不对 | |||
| || "/images/testPicture.jpg" | |||
| || "/images/shixun0.jpg" | |||
| */} | |||
| <img id={this.props.imageId || "__image"} src={`${imageSrc }`}></img> | |||
| </div> | |||
| {/* background: 'aquamarine', | |||
| 'border-radius': '128px' | |||
| */} | |||
| {!previewId && <div id="img-preview" className="img-preview preview-lg" style={{width: '128px', height: '128px', }}> | |||
| </div>} | |||
| {/* <img id="showImg" src="http://localhost:3007/images/testPicture.jpg"></img> */} | |||
| {/* <div id="canvasWrap"></div> */} | |||
| {/* <button onClick={save_avatar.bind(this)}>save </button> */} | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default Cropper; | |||
| // function aaa () { | |||
| // function showedit_headphoto() { | |||
| // var html = ` | |||
| // <script src=\"../head/jquery.min.js\"><\/script>\n | |||
| // <link href=\"../head/cropper.min.css\" rel=\"stylesheet\">\n | |||
| // <link href=\"../head/sitelogo.css\" rel=\"stylesheet\">\n | |||
| // <script src=\"../head/bootstrap.min.js\"><\/script>\n | |||
| // <script src=\"../head/cropper.js\"><\/script>\n | |||
| // <script src=\"../head/sitelogo.js\"><\/script>\n | |||
| // <script src=\"../head/html2canvas.min.js\" type=\"text/javascript\" charset=\"utf-8\"><\/script>\n\n | |||
| // <div class=\"task-popup\" style=\"width: 550px;\">\n <div class=\"task-popup-title clearfix task-popup-bggrey\">上传头像<\/div>\n <div class=\"clearfix\">\n | |||
| // <div class=\"modal fade\" style=\"outline: none;\" id=\"avatar-modal\" aria-hidden=\"true\" aria-labelledby=\"avatar-modal-label\" role=\"dialog\" tabindex=\"-1\">\n | |||
| // <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <form class=\"avatar-form\">\n <div class=\"modal-body\">\n | |||
| // <div class=\"padding20\">\n <div class=\"avatar-upload\">\n <input class=\"avatar-src\" name=\"avatar_src\" type=\"hidden\">\n | |||
| // <input class=\"avatar-data\" name=\"avatar_data\" type=\"hidden\">\n\n <span id=\"avatar-name\"><\/span>\n | |||
| // <input class=\"avatar-input\" style=\"display:none;\" id=\"avatarInput\" value=\"avatars/User/116\" name=\"avatar_file\" type=\"file\">\n | |||
| // <input type=\"hidden\" id=\"source_id\" value=\"116\"/>\n <input type=\"hidden\" id=\"source_type\" value=\"User\"/>\n <\/div>\n | |||
| // <div class=\"row clearfix mt20 pl20\">\n <div class=\"task-form-45 fl panel-box-sizing uplaodImg\">\n | |||
| // <div class=\"avatar-wrapper\" id=\"wrapper_image_show\">\n <!--<span style=\"display: block;\">\n 选择你要上传的图片<br/> | |||
| // \n 仅支持JPG、GIF、PNG,且文件小于2M\n <\/span>-->\n <\/div>\n | |||
| // <div class=\"row avatar-btns clearfix\">\n <a href=\"javascript:void(0);\" class=\"fl\" type=\"button\" onClick=\"$(\'input[id=avatarInput]\').click();\">重新上传<\/a>\n | |||
| // <!--<div class=\"btn-group\">\n <a href=\"javascript:void(0);\" class=\"fa fa-repeat fr mt5 color-grey-9\" data-method=\"rotate\" data-option=\"90\" | |||
| // type=\"button\" title=\"Rotate 90 degrees\">\n <\/a>\n <\/div>-->\n <\/div>\n <\/div>\n | |||
| // <div class=\"task-form-50 panel-box-sizing fr color-grey pr20\" style=\"width: 128px;\">\n <div class=\"edu-txt-center\">\n | |||
| // <div class=\"avatar-preview preview-lg radius\" id=\"imageHead\">\n | |||
| // <img alt=\"头像\" height=\"128\" nhname=\"avatar_image\" src=\"/images/avatars/User/116?1556802838\" width=\"128\" />\n <\/div>\n | |||
| // <span>头像预览<\/span>\n <\/div>\n | |||
| // <p class=\"color-grey-9 font-12 mt110 justify\">仅支持JPG、GIF、PNG,且文件小于2M<\/p>\n <\/div>\n <\/div>\n | |||
| // <\/div>\n <div class=\"clearfix edu-txt-center mb20 mt10\">\n | |||
| // <a href=\"javascript:void(0);\" class=\"task-btn mr20\" onclick=\"hideModal()\">取消<\/a>\n | |||
| // <a href=\"javascript:void(0);\" class=\"avatar-save task-btn task-btn-orange\" data-dismiss=\"modal\">确定<\/a>\n | |||
| // <\/div>\n <\/div>\n <\/form>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n<\/div>\n\n<script>\n | |||
| // $(function () {\n new CropAvatar($(\'#crop-avatar\'), 1/1);\n\n //---------------------------头像上传-----------------------------//\n //做个下简易的验证 大小 格式\n $(\'#avatarInput\').on(\'change\', function(e) {\n var filemaxsize = 1024 * 5;//5M\n var target = $(e.target);\n var Size = target[0].files[0].size / 1024;\n if(Size > filemaxsize) {\n alert(\'图片过大,请重新选择!\');\n $(\".avatar-wrapper\").children().remove;\n return false;\n }\n if(!this.files[0].type.match(/image.*/)) {\n alert(\'请选择正确的图片!\')\n } else {\n /*var filename = document.querySelector(\"#avatar-name\");*/\n var texts = document.querySelector(\"#avatarInput\").value;\n var teststr = texts; //你这里的路径写错了\n testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的\n /*filename.innerHTML = testend;\n $(filename).css(\"color\", \'#333\');*/\n $(\".avatar-save\").removeClass(\"task-btn-grey\").addClass(\"task-btn-orange\");\n $(\".avatar-save\").on(\"click\", save_avatar);\n }\n\n });\n });\n\n function save_avatar(){\n var img_lg = document.getElementById(\'imageHead\');\n if($(img_lg).html().trim() == \"\"){\n $(\"#avatar-name\").html(\"请先选择图片上传\").css(\"color\", \'red\');\n } else {\n $(\"#avatar-name\").html(\"\").css(\"color\", \'#333\');\n // 截图小的显示框内的内容\n html2canvas(img_lg, {\n allowTaint: true,\n taintTest: false,\n onrendered: function(canvas) {\n canvas.id = \"mycanvas\";\n //生成base64图片数据\n var dataUrl = canvas.toDataURL(\"image/jpeg\");\n var newImg = document.createElement(\"img\");\n newImg.src = dataUrl;\n imagesAjax(dataUrl);\n $(\".avatar-save\").attr(\"disabled\",\"true\");\n }\n });\n }\n }\n\n function imagesAjax(src) {\n var data = {};\n data.img = src;\n data.source_id = $(\'#source_id\').val();\n data.source_type = $(\'#source_type\').val();\n data.is_direct = 0;\n $.ajax({\n url: \"/upload_avatar\",\n beforeSend: function(xhr) {xhr.setRequestHeader(\'X-CSRF-Token\', $(\'meta[name=\"csrf-token\"]\').attr(\'content\'))},\n data: data,\n type: \"POST\",\n success: function (re) {\n console.log(re);\n console.log(1562050370);\n if(re){\n var o = JSON.parse(re);\n if (o.status !=0 ){\n console.log(o.message);\n } else {\n var imgSpan = $(\"img[nhname=\'avatar_image\']\");\n imgSpan.attr({\"src\": o.url + \'?1562050370\'});\n $(\"#user_code\").html(o.grade);\n notice_box_redirect(\"/users/shitou\", \"上传成功\");\n }\n } else {\n notice_box(\"上传出错\");\n }\n\n },\n error: function (e) {\n alert(e);\n }\n });\n }\n | |||
| // <\/script>`; | |||
| // pop_box_new(html, 550, 510); | |||
| // $("#imageHead img").attr({"src": $("#user_avatar_show").attr("src")}); | |||
| // $("#wrapper_image_show img").attr({"src": $("#user_avatar_show").attr("src")}); | |||
| // } | |||
| // $(function () { | |||
| // new CropAvatar($('#crop-avatar'), 1/1); | |||
| // //---------------------------头像上传-----------------------------// | |||
| // //做个下简易的验证 大小 格式 | |||
| // $('#avatarInput').on('change', function(e) { | |||
| // var filemaxsize = 1024 * 5;//5M | |||
| // var target = $(e.target); | |||
| // var Size = target[0].files[0].size / 1024; | |||
| // if(Size > filemaxsize) { | |||
| // alert('图片过大,请重新选择!'); | |||
| // $(".avatar-wrapper").children().remove; | |||
| // return false; | |||
| // } | |||
| // if(!this.files[0].type.match(/image.*/)) { | |||
| // alert('请选择正确的图片!') | |||
| // } else { | |||
| // /*var filename = document.querySelector("#avatar-name");*/ | |||
| // var texts = document.querySelector("#avatarInput").value; | |||
| // var teststr = texts; //你这里的路径写错了 | |||
| // testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的 | |||
| // /*filename.innerHTML = testend; $(filename).css("color", '#333');*/ | |||
| // $(".avatar-save").removeClass("task-btn-grey").addClass("task-btn-orange"); | |||
| // $(".avatar-save").on("click", save_avatar); | |||
| // } | |||
| // }); | |||
| // }); | |||
| // function save_avatar(){ | |||
| // var img_lg = document.getElementById('imageHead'); | |||
| // if($(img_lg).html().trim() == ""){ | |||
| // $("#avatar-name").html("请先选择图片上传").css("color", 'red'); | |||
| // } else { | |||
| // $("#avatar-name").html("").css("color", '#333'); | |||
| // // 截图小的显示框内的内容 | |||
| // html2canvas(img_lg, { | |||
| // allowTaint: true, | |||
| // taintTest: false, | |||
| // onrendered: function(canvas) { | |||
| // canvas.id = "mycanvas"; | |||
| // //生成base64图片数据 | |||
| // var dataUrl = canvas.toDataURL("image/jpeg"); | |||
| // var newImg = document.createElement("img"); | |||
| // newImg.src = dataUrl; | |||
| // imagesAjax(dataUrl); | |||
| // $(".avatar-save").attr("disabled","true"); | |||
| // } | |||
| // }); | |||
| // } | |||
| // } | |||
| // function imagesAjax(src) { | |||
| // var data = {}; | |||
| // data.img = src; | |||
| // data.source_id = $('#source_id').val(); | |||
| // data.source_type = $('#source_type').val(); | |||
| // data.is_direct = 0; | |||
| // $.ajax({ | |||
| // url: "/upload_avatar", | |||
| // beforeSend: function(xhr) { | |||
| // xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')) | |||
| // }, | |||
| // data: data, | |||
| // type: "POST", | |||
| // success: function (re) { | |||
| // console.log(re); | |||
| // // console.log(1562050370); | |||
| // if(re){ | |||
| // var o = JSON.parse(re); | |||
| // if (o.status !=0 ){ | |||
| // console.log(o.message); | |||
| // } else { | |||
| // var imgSpan = $("img[nhname='avatar_image']"); | |||
| // imgSpan.attr({"src": o.url + '?1562050370'}); | |||
| // $("#user_code").html(o.grade); | |||
| // notice_box_redirect("/users/shitou", "上传成功"); | |||
| // } | |||
| // } else { | |||
| // notice_box("上传出错"); | |||
| // } | |||
| // }, | |||
| // error: function (e) { | |||
| // alert(e); | |||
| // } | |||
| // }); | |||
| // } | |||
| // } | |||
| @@ -1,116 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| const $ = window.jQuery | |||
| const jQuery = $; | |||
| // if () { | |||
| // !$.drag | |||
| // (function($){ | |||
| // $.fn.dragValidator = function(options){ | |||
| // var x, drag = this, isMove = false, defaults = { | |||
| // }; | |||
| // var options = $.extend(defaults, options); | |||
| // //添加背景,文字,滑块 | |||
| // var html = '<div class="drag_bg"></div>'+ | |||
| // '<div class="drag_text" onselectstart="return false;" unselectable="on">拖动滑块验证</div>'+ | |||
| // '<div class="handler handler_bg"></div>'; | |||
| // this.append(html); | |||
| // | |||
| // var handler = drag.find('.handler'); | |||
| // var drag_bg = drag.find('.drag_bg'); | |||
| // var text = drag.find('.drag_text'); | |||
| // var maxWidth = text.width() - handler.width(); //能滑动的最大间距 | |||
| // //鼠标按下时候的x轴的位置 | |||
| // handler.mousedown(function(e){ | |||
| // isMove = true; | |||
| // x = e.pageX - parseInt(handler.css('left'), 10); | |||
| // }); | |||
| // | |||
| // //鼠标指针在上下文移动时,移动距离大于0小于最大间距,滑块x轴位置等于鼠标移动距离 | |||
| // $(document).mousemove(function(e){ | |||
| // var _x = e.pageX - x; | |||
| // var handler_offset = handler.offset(); | |||
| // var lastX = e.clientX -x; | |||
| // lastX = Math.max(0,Math.min(maxWidth,lastX)); | |||
| // if(isMove){ | |||
| // if(_x > 0 && _x <= maxWidth){ | |||
| // handler.css({'left': lastX}); | |||
| // drag_bg.css({'width': lastX}); | |||
| // } | |||
| // else if(lastX > maxWidth - 5 && lastX < maxWidth + 5 ){ //鼠标指针移动距离达到最大时清空事件 | |||
| // dragOk(); | |||
| // | |||
| // } | |||
| // } | |||
| // }); | |||
| // handler.mouseup(function(e){ | |||
| // isMove = false; | |||
| // var _x = e.pageX - x; | |||
| // if(text.text() != '验证通过' && _x < maxWidth){ //鼠标松开时,如果没有达到最大距离位置,滑块就返回初始位置 | |||
| // handler.animate({'left': 0}); | |||
| // drag_bg.animate({'width': 0}); | |||
| // } | |||
| // }); | |||
| // | |||
| // //清空事件 | |||
| // function dragOk(){ | |||
| // options.dragOkCallback && options.dragOkCallback() | |||
| // var kuaiwidth=drag.width() - handler.width() - 2; | |||
| // handler.removeClass('handler_bg').addClass('handler_ok_bg'); | |||
| // handler.css({'left':kuaiwidth+'px'}) | |||
| // text.css({'width':kuaiwidth+'px'}); | |||
| // text.text('验证通过'); | |||
| // drag.css({'color': '#fff'}); | |||
| // drag_bg.css({'width':kuaiwidth+'px'}) | |||
| // handler.unbind('mousedown'); | |||
| // $(document).unbind('mousemove'); | |||
| // $(document).unbind('mouseup'); | |||
| // $("#user_verification_notice").html(""); | |||
| // $('#user_verification_notice').parent().hide(); | |||
| // } | |||
| // }; | |||
| // })(jQuery); | |||
| // } | |||
| class DragValidator extends Component { | |||
| componentDidMount () { | |||
| // if($("#reg-drag").length>0 && IsPC()){ | |||
| // $("#reg-drag").dragValidator({ | |||
| // height: this.props.height, | |||
| // dragOkCallback: () => { | |||
| // this.props.dragOkCallback && this.props.dragOkCallback() | |||
| // } | |||
| // }); | |||
| // }else{ | |||
| // $("#reg-drag").empty(); | |||
| // } | |||
| } | |||
| empty() { | |||
| // $("#reg-drag").empty(); | |||
| } | |||
| render() { | |||
| const height = this.props.height || 45; | |||
| const className = this.props.className | |||
| const successGreenColor = this.props.successGreenColor || '#29bd8b' | |||
| // newMain clearfix | |||
| return ( | |||
| <div id="reg-drag" style={{ width:"287px",}} className={`drag_slider ${className}`}> | |||
| <style>{` | |||
| .drag_slider .handler { | |||
| height: 100%; | |||
| } | |||
| .drag_slider { | |||
| height: ${height}px; | |||
| line-height: ${height}px; | |||
| } | |||
| .drag_slider .drag_bg { | |||
| height: ${height}px; | |||
| background-color: ${successGreenColor}; | |||
| } | |||
| `}</style> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| export default ( DragValidator ); | |||
| @@ -1,31 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| // 登录后才能跳转 | |||
| class LinkAfterLogin extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| } | |||
| checkAuth = () => { | |||
| if (this.props.checkIfLogin()) { | |||
| if(this.props.checkProfileComplete){ | |||
| if(this.props.checkIfProfileCompleted()){ | |||
| this.props.history.push(this.props.to) | |||
| }else{ | |||
| this.props.showProfileCompleteDialog(); | |||
| } | |||
| }else{ | |||
| this.props.history.push(this.props.to) | |||
| } | |||
| } else { | |||
| this.props.showLoginDialog() | |||
| } | |||
| } | |||
| render() { | |||
| return( | |||
| <a {...this.props} onClick={this.checkAuth}>{this.props.children}</a> | |||
| ) | |||
| } | |||
| } | |||
| export default LinkAfterLogin; | |||
| @@ -1,30 +0,0 @@ | |||
| /* | |||
| * @Description: | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-13 10:28:15 | |||
| * @LastEditors: tangjiang | |||
| * @LastEditTime: 2019-12-13 10:37:17 | |||
| */ | |||
| import { Modal } from 'antd'; | |||
| export function ModalConfirm ( | |||
| title, | |||
| content, | |||
| handleOk, | |||
| handleCancel | |||
| ) { | |||
| Modal.confirm({ | |||
| title, | |||
| content, | |||
| okText: '确定', | |||
| cancelText: '取消', | |||
| onOk () { | |||
| handleOk && handleOk(); | |||
| }, | |||
| onCancel () { | |||
| handleCancel && handleCancel(); | |||
| } | |||
| }); | |||
| } | |||
| @@ -1,106 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { Modal } from 'antd'; | |||
| export function ModalHOC(options = {}) { | |||
| return function wrap(WrappedComponent) { | |||
| return class Wrapper extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| titlemessage: '', | |||
| Modallist: false, | |||
| Modallisttype: false, | |||
| singleButton: false | |||
| } | |||
| } | |||
| // 全局的modal this.props.showModal 调用即可 | |||
| showModal = (title, content, okCallback) => { | |||
| this.okCallback = okCallback; | |||
| this.setState({ | |||
| titlemessage: title, | |||
| Modallist: content, | |||
| Modallisttype: true, | |||
| singleButton: false, | |||
| }) | |||
| } | |||
| showSingleButtonModal = (title, content) => { | |||
| this.setState({ | |||
| titlemessage: title, | |||
| Modallist: content, | |||
| Modallisttype: true, | |||
| singleButton: true, | |||
| }) | |||
| } | |||
| onCancel = () => { | |||
| this.setState({ | |||
| Modallisttype:false | |||
| }) | |||
| } | |||
| hidemodeldelete = () => { | |||
| if (this.okCallback) { | |||
| this.okCallback() | |||
| } | |||
| this.onCancel() | |||
| } | |||
| render() { | |||
| const { titlemessage, Modallisttype, Modallist, singleButton } = this.state; | |||
| return ( | |||
| <React.Fragment> | |||
| <Modal | |||
| keyboard={false} | |||
| title={titlemessage} | |||
| // visible={modeldelet===true&&listid===list.id?true:false} | |||
| visible={Modallisttype} | |||
| className={"ecmodeldelet"} | |||
| closable={false} | |||
| footer={null} | |||
| > | |||
| <div className="task-popup-content" > | |||
| <div className="task-popup-text-center font-14">{Modallist}</div> | |||
| </div> | |||
| { singleButton ? <div className="task-popup-submit clearfix" | |||
| style={{ textAlign: 'center' }}> | |||
| <a className="task-btn task-btn-orange" | |||
| onClick={this.onCancel} | |||
| >知道啦</a> | |||
| </div> : <div className="task-popup-submit clearfix"> | |||
| <a onClick={this.onCancel} className="task-btn fl">取消</a> | |||
| <a className="task-btn task-btn-orange fr" | |||
| onClick={this.hidemodeldelete} | |||
| >确定</a> | |||
| </div> } | |||
| </Modal> | |||
| <WrappedComponent {...this.props} | |||
| showModal={ this.showModal } | |||
| showSingleButtonModal={ this.showSingleButtonModal } | |||
| > | |||
| </WrappedComponent> | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| import { ModalHOC } from '../common/ModalHOC' | |||
| export default ModalHOC() (XXXComponent) ; | |||
| this.props.showModal('提示', '确定要删除吗?', () => { | |||
| this.remove(k) | |||
| }) | |||
| */ | |||
| @@ -1,15 +0,0 @@ | |||
| /* | |||
| * @Description: 引入阿里图标库 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-10 09:03:48 | |||
| * @LastEditors: tangjiang | |||
| * @LastEditTime: 2019-12-12 10:53:47 | |||
| */ | |||
| import { Icon } from 'antd'; | |||
| const MyIcon = Icon.createFromIconfontCN({ | |||
| scriptUrl: '//at.alicdn.com/t/font_1535266_i4ilpm93kp.js' | |||
| }); | |||
| export default MyIcon; | |||
| @@ -1,48 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { Modal} from 'antd'; | |||
| import axios from 'axios'; | |||
| import '../../modules/user/common.css'; | |||
| //完善个人资料 | |||
| class Notcompleted extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| } | |||
| modalCancel=()=>{ | |||
| window.location.href = "/"; | |||
| } | |||
| setDownload=()=>{ | |||
| window.location.href ='/account/profile'; | |||
| } | |||
| render() { | |||
| console.log(this.props) | |||
| return( | |||
| <Modal | |||
| keyboard={false} | |||
| closable={false} | |||
| footer={null} | |||
| destroyOnClose={true} | |||
| title="提示" | |||
| centered={true} | |||
| visible={this.props.modalsType===undefined?false:this.props.modalsType} | |||
| width="530px" | |||
| > | |||
| <div className="educouddiv"> | |||
| <div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div> | |||
| <div className={"tabeltext-alignleft mt10"}><p>请在完成资料后,提交试用申请</p></div> | |||
| <div className="clearfix mt30 edu-txt-center"> | |||
| <a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a> | |||
| <a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a> | |||
| </div> | |||
| </div> | |||
| </Modal> | |||
| ) | |||
| } | |||
| } | |||
| export default Notcompleted; | |||
| @@ -1,51 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import { Modal } from 'antd'; | |||
| export function SetAppModel(options={}) { | |||
| return function wrap(WrappedComponent) { | |||
| return class Wrapper extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| this.state = { | |||
| } | |||
| } | |||
| modalCancel=()=>{ | |||
| window.location.href = "/"; | |||
| } | |||
| setDownload=()=>{ | |||
| window.location.href ='/account/profile'; | |||
| } | |||
| componentDidMount(){ | |||
| console.log(this.props) | |||
| } | |||
| render() { | |||
| const { titlemessage, Modallisttype, Modallist, singleButton } = this.state; | |||
| return ( | |||
| <Modal | |||
| keyboard={false} | |||
| closable={false} | |||
| footer={null} | |||
| destroyOnClose={true} | |||
| title="提示" | |||
| centered={true} | |||
| visible={true} | |||
| width="530px" | |||
| > | |||
| <div className="educouddiv"> | |||
| <div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div> | |||
| <div className={"tabeltext-alignleft mt10"}><p>请在完成资料后,提交试用申请</p></div> | |||
| <div className="clearfix mt30 edu-txt-center"> | |||
| <a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a> | |||
| <a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a> | |||
| </div> | |||
| </div> | |||
| </Modal> | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,31 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import { ConditionToolTip,getRandomNumber } from 'educoder' | |||
| class AttachmentsList extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| } | |||
| render(){ | |||
| let { attachments } = this.props; | |||
| return( | |||
| <React.Fragment> | |||
| { | |||
| attachments.map((item,key)=>{ | |||
| return( | |||
| <p key={key} className="clearfix mb3"> | |||
| <a className="color-grey fl"> | |||
| <i className="font-14 color-green iconfont icon-fujian mr8"></i> | |||
| </a> | |||
| <ConditionToolTip title={item.title} condition={item.title && item.title.length > 30 }> | |||
| <a href={item.url+getRandomNumber()} className="mr12 fl task-hide" length="58" target={ item.is_pdf && item.is_pdf == true ? "_blank" : "_self" } style={{"maxWidth":"432px"}}>{item.title}</a> | |||
| </ConditionToolTip> | |||
| <span className="color-grey mt2 color-grey-6 font-12">{item.filesize}</span> | |||
| </p> | |||
| ) | |||
| }) | |||
| } | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default AttachmentsList; | |||
| @@ -1,139 +0,0 @@ | |||
| /* | |||
| * @Description: 评论表单 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-17 17:32:55 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2020-01-06 18:42:09 | |||
| */ | |||
| import './index.scss'; | |||
| import React, { useState } from 'react'; | |||
| import { Form, Button, Input } from 'antd'; | |||
| import QuillForEditor from '../../quillForEditor'; | |||
| // import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html' | |||
| // import {formatDelta} from './util'; | |||
| const FormItem = Form.Item; | |||
| function CommentForm (props) { | |||
| const { | |||
| onCancel, | |||
| onSubmit, | |||
| form, | |||
| type | |||
| } = props; | |||
| const { getFieldDecorator } = form; | |||
| const [ctx, setCtx] = useState(''); | |||
| const [focus, setFocus] = useState(false); | |||
| const options = [ | |||
| // ['bold', 'italic', 'underline'], | |||
| // [{header: [1,2,3,false]}], | |||
| 'code-block', | |||
| 'link', | |||
| 'image', | |||
| 'formula' | |||
| ]; | |||
| // const { form: { getFieldDecorator } } = props; | |||
| const [showQuill, setShowQuill] = useState(false); | |||
| // 点击输入框 | |||
| const handleInputClick = (type) => { | |||
| setShowQuill(true); | |||
| setFocus(true); | |||
| } | |||
| // 取消 | |||
| const handleCancle = () => { | |||
| setShowQuill(false); | |||
| setCtx(''); | |||
| props.form.resetFields(); | |||
| onCancel && onCancel(); | |||
| } | |||
| // 编辑器内容变化时 | |||
| const handleContentChange = (content) => { | |||
| console.log('编辑器内容', content); | |||
| setCtx(content); | |||
| try { | |||
| // const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert(); | |||
| // props.form.setFieldsValue({'comment': _html.replace(/<\/?[^>]*>/g, '')}); | |||
| props.form.setFieldsValue({'comment': content}); | |||
| } catch (error) { | |||
| console.log(error); | |||
| } | |||
| } | |||
| // 发送 | |||
| const handleSubmit = (e) => { | |||
| e.preventDefault(); | |||
| props.form.validateFields((err, values) => { | |||
| if (!err) { | |||
| setShowQuill(false); | |||
| const content = ctx; | |||
| props.form.setFieldsValue({'comment': ''}); | |||
| setCtx(''); | |||
| // const _html = formatDelta(content.ops); | |||
| // console.log('保存的内容=====》》》》', content); | |||
| onSubmit && onSubmit(JSON.stringify(content)); | |||
| } | |||
| }); | |||
| } | |||
| const handleShowImage = (url) => { | |||
| alert(url); | |||
| } | |||
| // const _clazz = type === 'bottom' ? 'comment_form_bottom_area' : 'comment_form_area'; | |||
| let _clazz; | |||
| if (type === 'bottom') { | |||
| _clazz = showQuill ? 'comment_form_bottom_area active' : 'comment_form_bottom_area'; | |||
| } else { | |||
| _clazz = 'comment_form_area'; | |||
| } | |||
| return ( | |||
| <Form className={_clazz}> | |||
| <FormItem> | |||
| { | |||
| getFieldDecorator('comment', { | |||
| rules: [ | |||
| { required: true, message: '评论内容不能为空'} | |||
| ], | |||
| })( | |||
| <Input | |||
| onClick={() => handleInputClick(type)} | |||
| placeholder="说点儿什么~" | |||
| className={showQuill ? '' : 'show_input'} | |||
| style={{ | |||
| height: showQuill ? '0px' : '40px', | |||
| overflow: showQuill ? 'hidden' : 'auto', | |||
| opacity: showQuill ? 0 : 1, | |||
| }} | |||
| /> | |||
| ) | |||
| } | |||
| <QuillForEditor | |||
| imgAttrs={{width: '60px', height: '30px'}} | |||
| wrapStyle={{ | |||
| height: showQuill ? 'auto' : '0px', | |||
| opacity: showQuill ? 1 : 0, | |||
| overflow: showQuill ? 'none' : 'hidden', | |||
| transition: 'all 0.3s' | |||
| }} | |||
| autoFocus={focus} | |||
| style={{ height: '150px' }} | |||
| placeholder="说点儿什么~" | |||
| options={options} | |||
| value={ctx} | |||
| showUploadImage={handleShowImage} | |||
| onContentChange={handleContentChange} | |||
| /> | |||
| </FormItem> | |||
| <FormItem style={{ textAlign: 'right', display: showQuill ? 'block' : 'none' }}> | |||
| <Button onClick={handleCancle}>取消</Button> | |||
| <Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px'}}>发送</Button> | |||
| </FormItem> | |||
| </Form> | |||
| ); | |||
| } | |||
| export default Form.create()(CommentForm); | |||
| @@ -1,42 +0,0 @@ | |||
| /* | |||
| * @Description: | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-18 10:49:46 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-25 10:03:21 | |||
| */ | |||
| import './index.scss'; | |||
| import React from 'react'; | |||
| // import { Icon } from 'antd'; | |||
| // import MyIcon from '../MyIcon'; | |||
| function CommentIcon ({ | |||
| type, // 图标类型 | |||
| count, // 评论数 | |||
| iconClick, | |||
| iconColor, | |||
| theme, | |||
| ...props | |||
| }) { | |||
| // 点击图标 | |||
| const handleSpanClick = () => { | |||
| iconClick && iconClick(); | |||
| } | |||
| const _className = [undefined, null, ''].includes(count) ? 'comment_count_none' : 'comment_count'; | |||
| const _classIcon = `iconfont icon-${type} icon_font_size_14 comment_icon `; | |||
| return ( | |||
| <span | |||
| style={props.style} | |||
| className={`comment_icon_count ${props.className}`} | |||
| onClick={ handleSpanClick } | |||
| > | |||
| {/* <Icon className="comment_icon" type={type} style={{ color: iconColor }} theme={theme}/> */} | |||
| <span className={_classIcon} style={{ color: iconColor }}></span> | |||
| <span className={_className}>{ count }</span> | |||
| </span> | |||
| ) | |||
| } | |||
| export default CommentIcon; | |||
| @@ -1,257 +0,0 @@ | |||
| /* | |||
| * @Description: 评论单列 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-17 17:35:17 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-27 11:05:17 | |||
| */ | |||
| import './index.scss'; | |||
| import 'quill/dist/quill.core.css'; // 核心样式 | |||
| import 'quill/dist/quill.snow.css'; // 有工具栏 | |||
| import 'quill/dist/quill.bubble.css'; // 无工具栏 | |||
| import 'katex/dist/katex.min.css'; // katex 表达式样式 | |||
| import React, { useState } from 'react'; | |||
| import CommentIcon from './CommentIcon'; | |||
| import { getImageUrl, CNotificationHOC } from 'educoder' | |||
| import { Icon } from 'antd'; | |||
| import CommentForm from './CommentForm'; | |||
| import QuillForEditor from '../../quillForEditor'; | |||
| function CommentItem ({ | |||
| isAdmin, | |||
| options, | |||
| confirm, | |||
| comment, | |||
| submitDeleteComment, | |||
| submitChildComment, | |||
| likeComment, | |||
| showOrHideComment | |||
| }) { | |||
| // 显示评论输入框 | |||
| const [showQuill, setShowQuill] = useState(false); | |||
| // 加载更多评论内容 | |||
| // const [showMore, setShowMore] = useState(false); | |||
| // 显示子列数 | |||
| const [showItemCount, setShowItemCount] = useState(5); | |||
| // 箭头方向 | |||
| const [arrow, setArrow] = useState(false); | |||
| // 上传图片的ulr | |||
| const [url, setUrl] = useState(''); | |||
| const { | |||
| author = {}, // 作者 | |||
| id, // 评论id | |||
| content, // 回复内容 | |||
| time, // 回复时间 | |||
| hidden, // 是否隐藏 | |||
| // hack_id, // OJ的ID | |||
| praise_count, // 点赞数 | |||
| user_praise, // 当前用户是否点赞 | |||
| can_delete, | |||
| children = [] // 子回复 | |||
| } = comment; | |||
| // 删除评论 type: parent | child, id | |||
| const deleteComment = (id) => { | |||
| confirm({ | |||
| title: '提示', | |||
| content: ('确定要删除该条回复吗?'), | |||
| onOk () { | |||
| console.log('点击了删除', id); | |||
| submitDeleteComment && submitDeleteComment(id); | |||
| } | |||
| }); | |||
| } | |||
| // 评论头像 | |||
| const commentAvatar = (author) => ( | |||
| <img | |||
| className="item-flex flex-image" | |||
| src={author.image_url ? getImageUrl(`images/${author.image_url}`) : 'https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg'} | |||
| alt="" | |||
| /> | |||
| ); | |||
| // 评论信息 | |||
| const commentInfo = (id, author, time, can_delete) => { | |||
| const _classNames = can_delete ? 'item-close' : 'item-close hide'; | |||
| return ( | |||
| <div className="item-header"> | |||
| <span className="item-name">{author.name || ''}</span> | |||
| <span className="item-time">{time || ''}</span> | |||
| <span className={_classNames}> | |||
| <span className="iconfont icon-shanchu icon_font_size_14" onClick={() => deleteComment(id)}></span> | |||
| </span> | |||
| </div> | |||
| ); | |||
| }; | |||
| const handleShowUploadImage = (url) => { | |||
| // console.log('==============>>>>>>>>>>>>',url); | |||
| setUrl(url); | |||
| } | |||
| // 评论内容 | |||
| const commentCtx = (ctx) => { | |||
| let _ctx = null; | |||
| try { | |||
| _ctx = JSON.parse(ctx); | |||
| } catch (e) { | |||
| _ctx = ctx; | |||
| } | |||
| return ( | |||
| <QuillForEditor | |||
| readOnly={true} | |||
| value={_ctx} | |||
| showUploadImage={handleShowUploadImage} | |||
| /> | |||
| )}; | |||
| // 加载更多 | |||
| const handleOnLoadMore = (len) => { | |||
| setShowItemCount(!arrow ? len : 1); | |||
| setArrow(!arrow); | |||
| }; | |||
| // 评论追加内容 | |||
| const commentAppend = (children = []) => { | |||
| const len = children.length; | |||
| const _moreClass = len > showItemCount ? 'comment_item_loadmore show' : 'comment_item_loadmore' | |||
| const lastTxt = len - showItemCount; | |||
| const renderChild = (children) => { | |||
| return children.map((child, i) => { | |||
| const { | |||
| id, // 评论id | |||
| author = {}, | |||
| time, | |||
| content, | |||
| can_delete | |||
| } = child; | |||
| const showOrHide = i < showItemCount ? 'comment_item_show' : 'comment_item_hide'; | |||
| return ( | |||
| <li | |||
| key={`child_${i}`} | |||
| className={showOrHide} | |||
| > | |||
| <div className="comment_item_area comment_child_item_area"> | |||
| {commentAvatar(author)} | |||
| <div className="item-flex item-desc"> | |||
| {commentInfo(id, author, time, can_delete)} | |||
| {commentCtx(content)} | |||
| </div> | |||
| </div> | |||
| </li> | |||
| ); | |||
| }) | |||
| } | |||
| const _clazz = len > 0 ? 'comment_item_append_list active' : 'comment_item_append_list'; | |||
| return ( | |||
| <ul className={_clazz}> | |||
| {renderChild(children)} | |||
| <li className={_moreClass} onClick={() => handleOnLoadMore(len)}> | |||
| <p className="loadmore-txt">展开其余{lastTxt}条评论</p> | |||
| <p className="loadmore-icon"> | |||
| <Icon type={!arrow ? 'down' : 'up'}/> | |||
| </p> | |||
| </li> | |||
| </ul> | |||
| ); | |||
| }; | |||
| // 点击图标 | |||
| const handleShowOrHide = (id, hidden) => { | |||
| showOrHideComment && showOrHideComment(id, hidden); | |||
| } | |||
| // 点赞 | |||
| const handleClickLick = (id) => { | |||
| likeComment && likeComment(id); | |||
| } | |||
| // 点击评论icon | |||
| const handleClickMessage = () => { | |||
| setShowQuill(true); | |||
| } | |||
| // 点击取消 | |||
| const handleClickCancel = () => { | |||
| setShowQuill(false); | |||
| } | |||
| // 点击保存 | |||
| const handleClickSubmit = (id) => { | |||
| return (ctx) => { | |||
| setShowQuill(false); | |||
| submitChildComment && submitChildComment(id, ctx); | |||
| } | |||
| } | |||
| const handleClose = () => { | |||
| setUrl(''); | |||
| } | |||
| return ( | |||
| <li className="comment_item_area"> | |||
| {commentAvatar(author)} | |||
| <div className="item-flex item-desc"> | |||
| {commentInfo(id, author, time, can_delete)} | |||
| {commentCtx(content)} | |||
| {commentAppend(children)} | |||
| <div className="comment_icon_area"> | |||
| <CommentIcon | |||
| style={{ display: isAdmin ? 'inline-block' : 'none'}} | |||
| className='comment-icon-margin' | |||
| type={!hidden ? "xianshi" : 'yincang1'} | |||
| iconClick={() => handleShowOrHide(id, !hidden ? 1 : 0)} | |||
| /> | |||
| <CommentIcon | |||
| style={{ display: can_delete ? 'inline-block' : 'none'}} | |||
| className='comment-icon-margin' | |||
| type={'shanchu'} | |||
| iconClick={() => deleteComment(id)} | |||
| /> | |||
| {/* 回复 */} | |||
| <CommentIcon | |||
| className='comment-icon-margin' | |||
| type="huifu1" | |||
| count={children.length} | |||
| iconClick={handleClickMessage} | |||
| /> | |||
| {/* 点赞 */} | |||
| <CommentIcon | |||
| iconColor={ user_praise ? '#5091FF' : '' } | |||
| className='comment-icon-margin' | |||
| theme={user_praise ? 'filled' : ''} | |||
| type="dianzan" | |||
| count={praise_count} | |||
| iconClick={() => handleClickLick(id)} | |||
| /> | |||
| </div> | |||
| <div | |||
| style={{ display: showQuill ? 'block' : 'none'}} | |||
| className="comment_item_quill"> | |||
| <CommentForm | |||
| onCancel={handleClickCancel} | |||
| onSubmit={handleClickSubmit(id)} | |||
| /> | |||
| </div> | |||
| {/* 显示上传的图片信息 */} | |||
| <div className="show_upload_image" style={{ display: url ? 'block' : 'none'}}> | |||
| <Icon type="close" className="image_close" onClick={handleClose}/> | |||
| <div className="image_info"> | |||
| <img className="image" src={url} alt=""/> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </li> | |||
| ); | |||
| } | |||
| export default CNotificationHOC() (CommentItem); | |||
| @@ -1,56 +0,0 @@ | |||
| /* | |||
| * @Description: 评论列表页 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-17 17:34:00 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-24 18:08:07 | |||
| */ | |||
| import './index.scss'; | |||
| import React from 'react'; | |||
| import CommentItem from './CommentItem'; | |||
| import { Empty } from 'antd'; | |||
| function CommentList (props) { | |||
| const { | |||
| isAdmin, | |||
| commentLists, // 评论列表 | |||
| submitChildComment, | |||
| submitDeleteComment, | |||
| likeComment, | |||
| showOrHideComment | |||
| } = props; | |||
| const {comments = []} = commentLists; | |||
| const renderLi = () => { | |||
| if (comments.length > 0) { | |||
| return comments.map((item, index) => { | |||
| return ( | |||
| <CommentItem | |||
| isAdmin={isAdmin} | |||
| key={`item_${index}`} | |||
| submitChildComment={submitChildComment} | |||
| submitDeleteComment={submitDeleteComment} | |||
| comment={item} | |||
| likeComment={likeComment} | |||
| showOrHideComment={showOrHideComment} | |||
| /> | |||
| ); | |||
| }); | |||
| } else { | |||
| return ( | |||
| <div className="empty_comment"> | |||
| <Empty /> | |||
| </div> | |||
| ); | |||
| } | |||
| } | |||
| return ( | |||
| <ul className="comment_list_wrapper"> | |||
| {renderLi()} | |||
| </ul> | |||
| ); | |||
| } | |||
| export default CommentList; | |||
| @@ -1,46 +0,0 @@ | |||
| /* | |||
| * @Description: 评论组件 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-17 17:31:33 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-24 18:03:21 | |||
| */ | |||
| import React from 'react'; | |||
| // import CommentForm from './CommentForm'; | |||
| import CommentList from './CommentList'; | |||
| function Comment (props) { | |||
| const { | |||
| commentLists, | |||
| // addComment, | |||
| // cancelComment, | |||
| isAdmin, | |||
| addChildComment, | |||
| likeComment, | |||
| showOrHideComment, | |||
| submitDeleteComment | |||
| } = props; | |||
| // const handleCancelComment = () => { | |||
| // cancelComment && cancelComment(); | |||
| // }; | |||
| return ( | |||
| <React.Fragment> | |||
| {/* <CommentForm | |||
| onCancel={handleCancelComment} | |||
| onSubmit={addComment} | |||
| /> */} | |||
| <CommentList | |||
| isAdmin={isAdmin} | |||
| likeComment={likeComment} | |||
| showOrHideComment={showOrHideComment} | |||
| commentLists={commentLists} | |||
| submitChildComment={addChildComment} | |||
| submitDeleteComment={submitDeleteComment} | |||
| /> | |||
| </React.Fragment> | |||
| ); | |||
| } | |||
| export default Comment; | |||
| @@ -1,230 +0,0 @@ | |||
| $bdColor: rgba(244,244,244,1); | |||
| $bgColor: rgba(250,250,250,1); | |||
| $lh14: 14px; | |||
| $lh22: 22px; | |||
| $fz14: 14px; | |||
| $fz12: 12px; | |||
| $ml: 20px; | |||
| .comment_list_wrapper{ | |||
| box-sizing: border-box; | |||
| // border-top: 1px solid $bdColor; | |||
| .empty_comment{ | |||
| display: flex; | |||
| height: calc(100vh - 200px); | |||
| width: 100%; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| .comment_item_show{ | |||
| display: block; | |||
| } | |||
| .comment_item_hide{ | |||
| display: none; | |||
| } | |||
| .comment_item_area{ | |||
| display: flex; | |||
| padding: 20px 0; | |||
| box-sizing: border-box; | |||
| border-bottom: 1px solid $bdColor; | |||
| .comment_child_item_area:hover{ | |||
| .item-close{ | |||
| display: inline-block; | |||
| } | |||
| } | |||
| .flex-image{ | |||
| width: 48px; | |||
| height: 48px; | |||
| border-radius: 50%; | |||
| } | |||
| .item-desc{ | |||
| flex: 1; | |||
| // margin-left: $ml; | |||
| margin-left: 5px; | |||
| } | |||
| .item-header{ | |||
| font-size: $fz14; | |||
| line-height: $lh14; | |||
| color: #333; | |||
| margin-left: 15px; | |||
| .item-time{ | |||
| font-size: $fz12; | |||
| line-height: $lh14; | |||
| margin-left: $ml; | |||
| } | |||
| .item-close{ | |||
| display: none; | |||
| cursor: pointer; | |||
| float: right; | |||
| } | |||
| .item-close.hide{ | |||
| display: none; | |||
| } | |||
| } | |||
| .item-ctx{ | |||
| position: relative; | |||
| line-height: $lh22; | |||
| font-size: $fz12; | |||
| color: #333; | |||
| margin-top: 10px; | |||
| vertical-align: top; | |||
| } | |||
| .comment_icon_area{ | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| margin-top: 10px; | |||
| .comment-icon-margin{ | |||
| margin-left: 20px; | |||
| } | |||
| .comment-icon-margin-10{ | |||
| margin-left: 10px; | |||
| } | |||
| } | |||
| // .comment_item_quill{ | |||
| // // margin-top: 10px; | |||
| // } | |||
| .show_upload_image{ | |||
| position: fixed; | |||
| left: 0; | |||
| top: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| z-index: 1000; | |||
| &::before{ | |||
| position: absolute; | |||
| height: 100%; | |||
| width:100%; | |||
| content: ''; | |||
| background: #000; | |||
| opacity: .7; | |||
| } | |||
| .image_info{ | |||
| display: flex; | |||
| position: absolute; | |||
| width: 80%; | |||
| height: 80%; | |||
| left: 10%; | |||
| top: 10%; | |||
| justify-content: center; | |||
| align-items: center; | |||
| // background: green; | |||
| .image{ | |||
| display: block; | |||
| width: 100%; | |||
| } | |||
| } | |||
| .image_close{ | |||
| position: absolute; | |||
| right: 20px; | |||
| top: 20px; | |||
| color: #fff; | |||
| cursor: pointer; | |||
| } | |||
| } | |||
| } | |||
| .comment_icon_count{ | |||
| cursor: pointer; | |||
| font-size: 12px; | |||
| line-height: 1.5; | |||
| .comment_icon{ | |||
| color: #333; | |||
| } | |||
| .comment_count{ | |||
| color: #999999; | |||
| margin-left: 10px; | |||
| transition: color .3s; | |||
| } | |||
| .comment_count_none{ | |||
| margin-left: 0; | |||
| } | |||
| &:hover{ | |||
| .comment_icon, | |||
| .comment_count{ | |||
| color: #5091FF; | |||
| } | |||
| } | |||
| } | |||
| .comment_item_append_list{ | |||
| display: none; | |||
| position: relative; | |||
| background-color: $bgColor; | |||
| border-radius: 5px; | |||
| padding: 0 15px 10px; | |||
| margin: 15px 0; | |||
| &.active{ | |||
| display: block; | |||
| } | |||
| &::before { | |||
| position: absolute; | |||
| left: 15px; | |||
| bottom: 100%; | |||
| height: 0; | |||
| width: 0; | |||
| content: ''; | |||
| // border: 5px solid transparent; | |||
| border: 10px solid transparent; | |||
| border-bottom-color: $bgColor; | |||
| } | |||
| .comment_item_loadmore{ | |||
| display: none; | |||
| padding-top: 10px; | |||
| cursor: pointer; | |||
| .loadmore-txt, | |||
| .loadmore-icon{ | |||
| color: #999; | |||
| text-align: center; | |||
| font-size: $fz12; | |||
| } | |||
| &.show{ | |||
| display: block; | |||
| } | |||
| } | |||
| } | |||
| .icon_font_size_14{ | |||
| font-size: 14px !important; | |||
| } | |||
| } | |||
| .comment_form_area, | |||
| .comment_form_bottom_area{ | |||
| width: 100%; | |||
| } | |||
| .comment_form_area{ | |||
| position: relative; | |||
| background: #fff; | |||
| // top: 10px; | |||
| .ant-form-explain{ | |||
| padding-left: 0px; | |||
| } | |||
| .show_input{ | |||
| margin-top: 10px; | |||
| } | |||
| } | |||
| .comment_form_bottom_area{ | |||
| position: relative; | |||
| background: #fff; | |||
| top: 10px; | |||
| &.active{ | |||
| position: absolute; | |||
| background: #fff; | |||
| left: 0px; | |||
| right: 0px; | |||
| top: -230px; | |||
| padding: 0 20px; | |||
| } | |||
| } | |||
| @@ -1,80 +0,0 @@ | |||
| /* | |||
| * @Description: quill delta -> html | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-24 08:51:25 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-26 09:30:11 | |||
| */ | |||
| export const formatDelta = (deltas) => { | |||
| let formatted = []; | |||
| deltas.forEach(element => { | |||
| let text = null; | |||
| // 没有图片时 | |||
| if (!element['insert']['image']) { | |||
| text = element['insert']; // 获取插入的内容 | |||
| // 元素有属性时 | |||
| if (element['attributes']) { | |||
| // 获取所有的key值 | |||
| const keys = Object.keys(element['attributes']); | |||
| keys.forEach(key => { | |||
| text = operate(text, key, element['attributes'][key]); | |||
| }); | |||
| } else if (element['insert']['formula']) { | |||
| text = element['insert']['formula']; | |||
| } | |||
| } else { | |||
| const image = element['insert']['image']; | |||
| const {url, alt} = image; | |||
| if (url && (url.startsWith('http') || url.startsWith('https'))) { | |||
| text = ` | |||
| <img | |||
| src="${url}" | |||
| style="{display: 'inline-block'}" | |||
| width="60px" | |||
| height="30px" | |||
| alt="${alt}" | |||
| /> | |||
| `; | |||
| // text = "<img src="+url+" width='60px' height='30px' onclick='' alt="+alt+"/>"; | |||
| } | |||
| } | |||
| formatted.push(text); | |||
| }); | |||
| console.log(formatted); | |||
| return formatted.join(''); | |||
| } | |||
| /** | |||
| * @param {*} text 文本内容 | |||
| * @param {*} key 属性key | |||
| * @param {*} value 属性key对应的值 | |||
| */ | |||
| export const operate = (text, key, value) => { | |||
| let operatedText = null; | |||
| debugger; | |||
| switch (key) { | |||
| case 'bold': | |||
| operatedText = `<strong>${text}</strong>`; | |||
| break; | |||
| case 'italic': | |||
| operatedText = `<i>${text}</i>`; | |||
| break; | |||
| case 'strike': | |||
| operatedText = `<s>${text}</s>`; | |||
| break; | |||
| case 'underline': | |||
| operatedText = `<u>${text}</u>`; | |||
| break; | |||
| case 'link': | |||
| operatedText = `<a href="${value}" style="color: #5091ff; text-decoration: underline;" target="bland">${text}</a>`; | |||
| break; | |||
| default: | |||
| operatedText = text; | |||
| } | |||
| return operatedText; | |||
| } | |||
| @@ -1,68 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import { getImageUrl } from 'educoder' | |||
| class PopInstruction extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| this.state={ | |||
| visible: false, | |||
| } | |||
| } | |||
| setVisible = (visible) => { | |||
| this.setState({ visible }) | |||
| } | |||
| render(){ | |||
| const { visible } = this.state | |||
| const { width, id } = this.props | |||
| let _width = width || 92 | |||
| return( | |||
| <div style={{position: 'relative', display: 'inline' }}> | |||
| {/* | |||
| width: ${width || 292}px!important; | |||
| */} | |||
| <style>{` | |||
| #repository_url_tip { | |||
| top: 32px!important; | |||
| left: 10px!important; | |||
| z-index: 999; | |||
| } | |||
| .repository_url_tippostion { | |||
| position: absolute; | |||
| } | |||
| #repository_url_tip.popInstruction${id} { | |||
| width: ${_width}px !important; | |||
| } | |||
| .top-black-trangleft { | |||
| display: block; | |||
| border-width: 8px; | |||
| position: absolute; | |||
| top: -16px; | |||
| border-style: dashed solid dashed dashed; | |||
| border-color: transparent transparent rgba(5,16,26,.6); | |||
| font-size: 0; | |||
| line-height: 0; | |||
| } | |||
| `}</style> | |||
| <a className="ml10" onClick={()=>this.setVisible(!visible)}> | |||
| <img src={getImageUrl("images/educoder/problem.png")} | |||
| style={{marginBottom: '2px'}} | |||
| /> | |||
| </a> | |||
| <div className={`invite-tip popInstruction${id} clearfix repository_url_tippostion`} style={{display: visible===true?"block":"none"}} | |||
| id="repository_url_tip" | |||
| > | |||
| <span className="top-black-trangleft"></span> | |||
| <div className="padding20 invitecontent clearfix"> | |||
| {this.props.children} | |||
| </div> | |||
| <p className="inviteTipbtn with100"> | |||
| <a onClick={()=>this.setVisible(false)} > | |||
| 知道了 | |||
| </a> | |||
| </p> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| } | |||
| export default PopInstruction; | |||
| @@ -1,12 +0,0 @@ | |||
| .hideMd { | |||
| display: none !important; | |||
| } | |||
| .content_editorMd_show { | |||
| height: auto; | |||
| min-height: 38px; | |||
| width: 800px; | |||
| border: 1px solid rgba(234,234,234,1); | |||
| margin-top: 2px; | |||
| padding: 4px; | |||
| width: 100%; | |||
| } | |||
| @@ -1,83 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import TPMMDEditor from '../../../modules/tpm/challengesnew/TPMMDEditor' | |||
| import {markdownToHTML} from 'educoder' | |||
| import './DMDEditor.css' | |||
| // 需要父组件通过toShowMode、toMDMode 来控制,一次只能打开一个DMDEditor | |||
| class DMDEditor extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| this.mdRef = React.createRef() | |||
| this.state={ | |||
| mdMode: false, | |||
| // value: this.props.initValue | |||
| } | |||
| } | |||
| componentDidUpdate(prevProps, prevState) { | |||
| } | |||
| componentDidMount() { | |||
| // if(this.props.initValue != this.mdRef.current.getValue()) { | |||
| // this.mdRef.current.setValue(this.props.initValue) | |||
| // } | |||
| } | |||
| toMDMode = () => { | |||
| this.setState({mdMode: true}, () => { | |||
| this.mdRef.current.resize() | |||
| this.mdRef.current.setValue(this.props.initValue) | |||
| }) | |||
| this.props.toMDMode(this) | |||
| } | |||
| toShowMode = () => { | |||
| this.setState({mdMode: false}) | |||
| this.props.toShowMode && this.props.toShowMode(this) | |||
| } | |||
| onCMBlur = () => { | |||
| this.toShowMode() | |||
| } | |||
| onChange = (val) => { | |||
| // this.setState({ value: val }) | |||
| this.props.onChange(val) | |||
| if (this.state.showError == true) { | |||
| this.setState({showError: false}) | |||
| } | |||
| } | |||
| showError = () => { | |||
| this.mdRef.current.showError() | |||
| this.setState({showError: true}) | |||
| } | |||
| render(){ | |||
| const { mdMode, showError } = this.state; | |||
| const { initValue } = this.props; | |||
| let _style = {} | |||
| if (showError) { | |||
| _style.border = '1px solid red' | |||
| } | |||
| _style = Object.assign(_style, {display: mdMode == true ? 'none' : '', color: initValue? '': '#999', alignItems: 'center', wordBreak: 'break-all'}) | |||
| return( | |||
| <React.Fragment> | |||
| <style>{` | |||
| `}</style> | |||
| <div id="content_editorMd_show" className="new_li content_editorMd_show markdown-body" | |||
| style={_style} | |||
| dangerouslySetInnerHTML={{__html: initValue ? markdownToHTML(initValue):this.props.placeholder}} | |||
| onClick={this.toMDMode} | |||
| > | |||
| </div> | |||
| {/* | |||
| onCMBlur={this.onCMBlur} */} | |||
| <TPMMDEditor | |||
| ref={this.mdRef} | |||
| {...this.props} | |||
| initValue={initValue} | |||
| className={`${this.props.className} ${mdMode == true ? '' : 'hideMd'}`} | |||
| onChange={this.onChange} | |||
| ></TPMMDEditor> | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default DMDEditor; | |||
| @@ -1,6 +0,0 @@ | |||
| .markdownToHtml.editormd-html-preview, .markdownToHtml.editormd-preview-container { | |||
| overflow: hidden; | |||
| } | |||
| .markdownToHtml.editormd-html-preview p.editormd-tex, .markdownToHtml.editormd-preview-container p.editormd-tex { | |||
| text-align: left; | |||
| } | |||
| @@ -1,40 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import { markdownToHTML } from 'educoder' | |||
| import './MarkdownToHtml.css' | |||
| /** | |||
| selector 需要传入唯一的selector作为id,不然会引起冲突 | |||
| */ | |||
| class MarkdownToHtml extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| this.state={ | |||
| } | |||
| } | |||
| _markdownToHTML = (content, selector) => { | |||
| markdownToHTML(content, selector) | |||
| } | |||
| componentDidUpdate = (prevProps) => { | |||
| if (this.props.content) { | |||
| if ( prevProps.content != this.props.content ) { | |||
| this._markdownToHTML(this.props.content, `markdown_to_html_${this.props.selector || ''}`) | |||
| } | |||
| } | |||
| } | |||
| componentDidMount () { | |||
| this.props.content && this._markdownToHTML(this.props.content, `markdown_to_html_${this.props.selector || ''}`) | |||
| } | |||
| render(){ | |||
| const { style, className } = this.props | |||
| let _selector = `markdown_to_html_${this.props.selector || ''}` | |||
| return( | |||
| <div id={_selector } className={`markdownToHtml new_li markdown-body ${className} ${_selector}`} | |||
| // dangerouslySetInnerHTML={{__html: markdownToHTML(this.props.content)}} | |||
| style={style} | |||
| > | |||
| </div> | |||
| ) | |||
| } | |||
| } | |||
| export default MarkdownToHtml; | |||
| @@ -1,237 +0,0 @@ | |||
| import React, { useState, useEffect, memo } from 'react'; | |||
| import { getUrl2, isDev } from 'educoder' | |||
| import axios from 'axios' | |||
| const $ = window.$ | |||
| let _url_origin = getUrl2() | |||
| let _path = isDev() ? 'public' : 'build' | |||
| let uploader | |||
| let _testHost = '' ; '192.168.2.63:3001/api' | |||
| const login = 'innov' | |||
| function createUploader () { | |||
| uploader = new window.AliyunUpload.Vod({ | |||
| timeout: $('#timeout').val() || 60000, | |||
| partSize: $('#partSize').val() || 1048576, | |||
| parallel: $('#parallel').val() || 5, | |||
| retryCount: $('#retryCount').val() || 3, | |||
| retryDuration: $('#retryDuration').val() || 2, | |||
| region: $('#region').val() || 'ap-southeast-1', | |||
| userId: $('#userId').val() || 1829848226361863, //, // 1303984639806000, | |||
| // 添加文件成功 | |||
| addFileSuccess: function (uploadInfo) { | |||
| console.log('addFileSuccess') | |||
| $('#authUpload').attr('disabled', false) | |||
| $('#resumeUpload').attr('disabled', false) | |||
| $('#status').text('添加文件成功, 等待上传...') | |||
| console.log("addFileSuccess: " + uploadInfo.file.name) | |||
| $('#pauseUpload').attr('disabled', false) | |||
| uploader.startUpload() | |||
| }, | |||
| // 开始上传 | |||
| onUploadstarted: function (uploadInfo) { | |||
| // 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法 | |||
| // 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress | |||
| // 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口 | |||
| // 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi | |||
| // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html) | |||
| // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html) | |||
| if (!uploadInfo.videoId) { | |||
| // var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6' | |||
| // $.get(createUrl, function (data) { | |||
| // var uploadAuth = data.UploadAuth | |||
| // var uploadAddress = data.UploadAddress | |||
| // var videoId = data.VideoId | |||
| // uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId) | |||
| // }, 'json') | |||
| var createUrl = `${_testHost}/users/${login}/video_auths.json?debug=true` | |||
| axios.post(createUrl, { | |||
| title: 'testvod1', | |||
| file_name: 'aa.mp4' | |||
| }).then((response) => { | |||
| // if (response.data.status == ) | |||
| const data = response.data.data | |||
| var uploadAuth = data.UploadAuth | |||
| var uploadAddress = data.UploadAddress | |||
| var videoId = data.VideoId | |||
| uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId) | |||
| }).catch((error) => { | |||
| console.log(error) | |||
| }) | |||
| $('#status').text('文件开始上传...') | |||
| console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) | |||
| } else { | |||
| // 如果videoId有值,根据videoId刷新上传凭证 | |||
| // https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY | |||
| // var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId | |||
| // $.get(refreshUrl, function (data) { | |||
| // var uploadAuth = data.UploadAuth | |||
| // var uploadAddress = data.UploadAddress | |||
| // var videoId = data.VideoId | |||
| // uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId) | |||
| // }, 'json') | |||
| var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true` | |||
| axios.put(refreshUrl, { | |||
| video_id: uploadInfo.videoId, | |||
| }).then((response) => { | |||
| const data = response.data.data | |||
| var uploadAuth = data.UploadAuth | |||
| var uploadAddress = data.UploadAddress | |||
| var videoId = data.VideoId | |||
| uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId) | |||
| }).catch((error) => { | |||
| console.log(error) | |||
| }) | |||
| } | |||
| }, | |||
| // 文件上传成功 | |||
| onUploadSucceed: function (uploadInfo) { | |||
| console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) | |||
| $('#status').text('文件上传成功!') | |||
| }, | |||
| // 文件上传失败 | |||
| onUploadFailed: function (uploadInfo, code, message) { | |||
| console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message) | |||
| $('#status').text('文件上传失败!') | |||
| }, | |||
| // 取消文件上传 | |||
| onUploadCanceled: function (uploadInfo, code, message) { | |||
| console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message) | |||
| $('#status').text('文件上传已暂停!') | |||
| }, | |||
| // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上 | |||
| onUploadProgress: function (uploadInfo, totalSize, progress) { | |||
| console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%") | |||
| var progressPercent = Math.ceil(progress * 100) | |||
| $('#auth-progress').text(progressPercent) | |||
| $('#status').text('文件上传中...') | |||
| }, | |||
| // 上传凭证超时 | |||
| onUploadTokenExpired: function (uploadInfo) { | |||
| // 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时 | |||
| // 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth | |||
| // 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth | |||
| $('#status').text('文件上传超时!') | |||
| // let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId | |||
| // $.get(refreshUrl, function (data) { | |||
| // var uploadAuth = data.UploadAuth | |||
| // uploader.resumeUploadWithAuth(uploadAuth) | |||
| // console.log('upload expired and resume upload with uploadauth ' + uploadAuth) | |||
| // }, 'json') | |||
| var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true` | |||
| axios.put(refreshUrl, { | |||
| video_id: uploadInfo.videoId, | |||
| }).then((response) => { | |||
| const data = response.data.data | |||
| var uploadAuth = data.UploadAuth | |||
| uploader.resumeUploadWithAuth(uploadAuth) | |||
| }).catch((error) => { | |||
| console.log(error) | |||
| }) | |||
| }, | |||
| // 全部文件上传结束 | |||
| onUploadEnd: function (uploadInfo) { | |||
| $('#status').text('文件上传完毕!') | |||
| console.log("onUploadEnd: uploaded all the files") | |||
| } | |||
| }) | |||
| return uploader | |||
| } | |||
| function AliyunUploader(props) { | |||
| useEffect(() => { | |||
| if (window.AliyunUpload && window.AliyunUpload.Vod) { | |||
| } else { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| $('#fileUpload').on('change', function (e) { | |||
| var file = e.target.files[0] | |||
| if (!file) { | |||
| alert("请先选择需要上传的文件!") | |||
| return | |||
| } | |||
| var Title = file.name | |||
| var userData = '{"Vod":{}}' | |||
| if (uploader) { | |||
| uploader.stopUpload() | |||
| $('#auth-progress').text('0') | |||
| $('#status').text("") | |||
| } | |||
| if (!uploader) { | |||
| uploader = createUploader() | |||
| } | |||
| // 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData) | |||
| console.log(uploader) | |||
| let result = uploader.addFile(file, null, null, null, userData) | |||
| $('#authUpload').attr('disabled', false) | |||
| // $('#pauseUpload').attr('disabled', true) | |||
| // $('#resumeUpload').attr('disabled', true) | |||
| }) | |||
| return () => { | |||
| $('#fileUpload').off('change') | |||
| } | |||
| }, []) | |||
| let { source, id, className, type } = props; | |||
| function onStop() { | |||
| $('#resumeUpload').attr('disabled', false) | |||
| // $('#pauseUpload').attr('disabled', true) | |||
| uploader.stopUpload() | |||
| } | |||
| function onResume() { | |||
| // $('#resumeUpload').attr('disabled', true) | |||
| // $('#pauseUpload').attr('disabled', false) | |||
| uploader.startUpload() | |||
| } | |||
| return( | |||
| <React.Fragment> | |||
| <div> | |||
| <input type="file" id="fileUpload"></input> | |||
| <label class="status">上传状态: <span id="status"></span></label> | |||
| </div> | |||
| <div class="upload-type"> | |||
| 上传方式一, 使用 UploadAuth 上传: | |||
| {/* <button id="authUpload" disabled="true">开始上传</button> | |||
| <button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button> | |||
| <button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */} | |||
| <button id="authUpload" >开始上传</button> | |||
| <button id="pauseUpload" onClick={onStop}>暂停</button> | |||
| <button id="resumeUpload" onClick={onResume}>恢复上传</button> | |||
| <span class="progress">上传进度: <i id="auth-progress">0</i> %</span> | |||
| <span></span> | |||
| </div> | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| export default AliyunUploader | |||
| @@ -1,188 +0,0 @@ | |||
| import React, { useState, useEffect, memo } from 'react'; | |||
| import { getUrl2, isDev } from 'educoder' | |||
| const $ = window.$ | |||
| let _url_origin = getUrl2() | |||
| let _path = _isDev ? 'public' : 'build' | |||
| let uploader | |||
| function createUploader () { | |||
| uploader = new AliyunUpload.Vod({ | |||
| timeout: $('#timeout').val() || 60000, | |||
| partSize: $('#partSize').val() || 1048576, | |||
| parallel: $('#parallel').val() || 5, | |||
| retryCount: $('#retryCount').val() || 3, | |||
| retryDuration: $('#retryDuration').val() || 2, | |||
| region: $('#region').val() || 'ap-southeast-1', | |||
| userId: $('#userId').val() || 1202060945918292, // 1303984639806000, | |||
| // 添加文件成功 | |||
| addFileSuccess: function (uploadInfo) { | |||
| console.log('addFileSuccess') | |||
| $('#authUpload').attr('disabled', false) | |||
| $('#resumeUpload').attr('disabled', false) | |||
| $('#status').text('添加文件成功, 等待上传...') | |||
| console.log("addFileSuccess: " + uploadInfo.file.name) | |||
| $('#pauseUpload').attr('disabled', false) | |||
| uploader.startUpload() | |||
| }, | |||
| // 开始上传 | |||
| onUploadstarted: function (uploadInfo) { | |||
| // 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法 | |||
| // 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress | |||
| // 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口 | |||
| // 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi | |||
| // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html) | |||
| // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html) | |||
| if (!uploadInfo.videoId) { | |||
| var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6' | |||
| $.get(createUrl, function (data) { | |||
| var uploadAuth = data.UploadAuth | |||
| var uploadAddress = data.UploadAddress | |||
| var videoId = data.VideoId | |||
| uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId) | |||
| }, 'json') | |||
| $('#status').text('文件开始上传...') | |||
| console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) | |||
| } else { | |||
| // 如果videoId有值,根据videoId刷新上传凭证 | |||
| // https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY | |||
| var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId | |||
| $.get(refreshUrl, function (data) { | |||
| var uploadAuth = data.UploadAuth | |||
| var uploadAddress = data.UploadAddress | |||
| var videoId = data.VideoId | |||
| uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId) | |||
| }, 'json') | |||
| } | |||
| }, | |||
| // 文件上传成功 | |||
| onUploadSucceed: function (uploadInfo) { | |||
| console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) | |||
| $('#status').text('文件上传成功!') | |||
| }, | |||
| // 文件上传失败 | |||
| onUploadFailed: function (uploadInfo, code, message) { | |||
| console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message) | |||
| $('#status').text('文件上传失败!') | |||
| }, | |||
| // 取消文件上传 | |||
| onUploadCanceled: function (uploadInfo, code, message) { | |||
| console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message) | |||
| $('#status').text('文件上传已暂停!') | |||
| }, | |||
| // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上 | |||
| onUploadProgress: function (uploadInfo, totalSize, progress) { | |||
| console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%") | |||
| var progressPercent = Math.ceil(progress * 100) | |||
| $('#auth-progress').text(progressPercent) | |||
| $('#status').text('文件上传中...') | |||
| }, | |||
| // 上传凭证超时 | |||
| onUploadTokenExpired: function (uploadInfo) { | |||
| // 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时 | |||
| // 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth | |||
| // 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth | |||
| $('#status').text('文件上传超时!') | |||
| let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId | |||
| $.get(refreshUrl, function (data) { | |||
| var uploadAuth = data.UploadAuth | |||
| uploader.resumeUploadWithAuth(uploadAuth) | |||
| console.log('upload expired and resume upload with uploadauth ' + uploadAuth) | |||
| }, 'json') | |||
| }, | |||
| // 全部文件上传结束 | |||
| onUploadEnd: function (uploadInfo) { | |||
| $('#status').text('文件上传完毕!') | |||
| console.log("onUploadEnd: uploaded all the files") | |||
| } | |||
| }) | |||
| return uploader | |||
| } | |||
| function AliyunUploaderDemo(props) { | |||
| useEffect(() => { | |||
| if (window.AliyunUpload && window.AliyunUpload.Vod) { | |||
| } else { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| $.getScript( | |||
| `${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| }); | |||
| }); | |||
| }); | |||
| } | |||
| $('#fileUpload').on('change', function (e) { | |||
| var file = e.target.files[0] | |||
| if (!file) { | |||
| alert("请先选择需要上传的文件!") | |||
| return | |||
| } | |||
| var Title = file.name | |||
| var userData = '{"Vod":{}}' | |||
| if (uploader) { | |||
| uploader.stopUpload() | |||
| $('#auth-progress').text('0') | |||
| $('#status').text("") | |||
| } | |||
| if (!uploader) { | |||
| uploader = createUploader() | |||
| } | |||
| // 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData) | |||
| console.log(uploader) | |||
| uploader.addFile(file, null, null, null, userData) | |||
| $('#authUpload').attr('disabled', false) | |||
| // $('#pauseUpload').attr('disabled', true) | |||
| // $('#resumeUpload').attr('disabled', true) | |||
| }) | |||
| // return () => { | |||
| // } | |||
| }, []) | |||
| let { source, id, className, type } = props; | |||
| function onStop() { | |||
| $('#resumeUpload').attr('disabled', false) | |||
| // $('#pauseUpload').attr('disabled', true) | |||
| uploader.stopUpload() | |||
| } | |||
| function onResume() { | |||
| // $('#resumeUpload').attr('disabled', true) | |||
| // $('#pauseUpload').attr('disabled', false) | |||
| uploader.startUpload() | |||
| } | |||
| return( | |||
| <React.Fragment> | |||
| <div> | |||
| <input type="file" id="fileUpload"></input> | |||
| <label class="status">上传状态: <span id="status"></span></label> | |||
| </div> | |||
| <div class="upload-type"> | |||
| 上传方式一, 使用 UploadAuth 上传: | |||
| {/* <button id="authUpload" disabled="true">开始上传</button> | |||
| <button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button> | |||
| <button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */} | |||
| <button id="authUpload" >开始上传</button> | |||
| <button id="pauseUpload" onClick={onStop}>暂停</button> | |||
| <button id="resumeUpload" onClick={onResume}>恢复上传</button> | |||
| <span class="progress">上传进度: <i id="auth-progress">0</i> %</span> | |||
| <span></span> | |||
| </div> | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| export default AliyunUploaderDemo | |||
| @@ -1,110 +0,0 @@ | |||
| import React,{ Component } from "react"; | |||
| import { getUrl2 } from "educoder"; | |||
| import ReactPlayer from 'react-player' | |||
| const $ = window.$ | |||
| let _url_origin = getUrl2() | |||
| class Clappr extends Component{ | |||
| constructor(props){ | |||
| super(props); | |||
| this.state={ | |||
| } | |||
| } | |||
| componentWillUnmount() { | |||
| this['player'+this.props.id] && this['player'+this.props.id].destroy() | |||
| } | |||
| componentDidMount() { | |||
| return; | |||
| const source = this.props.source || "http://your.video/here.mp4" | |||
| const { id, type } = this.props | |||
| const _id = `#_player${id}` | |||
| if (!window['Clappr'] && window['ClapprLoading'] == true) { | |||
| setTimeout(() => { | |||
| this.componentDidMount() | |||
| }, 300) | |||
| return; | |||
| } | |||
| // && window['clappr-playback-rate-plugin'] | |||
| if (window['Clappr'] ) { | |||
| // https://github.com/clappr/clappr/issues/1839 | |||
| // http://clappr.github.io/classes/Player.html#method_mute | |||
| this['player'+id] = new window.Clappr.Player({ | |||
| source: source, parentId: _id, | |||
| height: type == 'mp3' ? 60 : 360, | |||
| hideMediaControl: type == 'mp3' ? false : true, | |||
| // plugins: { | |||
| // 'core': [window.Clappr.MediaControl, window['clappr-playback-rate-plugin'].default] | |||
| // } | |||
| }); | |||
| } else { | |||
| window['ClapprLoading'] = true; | |||
| $.getScript( | |||
| `${_url_origin}/javascripts/media/clappr.min.js`, | |||
| (data, textStatus, jqxhr) => { | |||
| window.clappr = window.Clappr | |||
| // $.getScript( | |||
| // `${_url_origin}/javascripts/media/clappr-playback-rate-plugin.min.js`, | |||
| // (data, textStatus, jqxhr) => { | |||
| this['player'+id] = new window.Clappr.Player({ | |||
| source: source, parentId: _id, | |||
| height: type == 'mp3' ? 60 : 360, | |||
| hideMediaControl: type == 'mp3' ? false : true, | |||
| // plugins: { | |||
| // 'core': [window.Clappr.MediaControl, window['clappr-playback-rate-plugin'].default] | |||
| // } | |||
| }); | |||
| // }) | |||
| }); | |||
| // | |||
| // $.when( | |||
| // $.getScript( `${_url_origin}/javascripts/media/clappr.min.js` ), | |||
| // // $.getScript( `${_url_origin}/javascripts/media/clappr-thumbnails-plugin.js` ), | |||
| // $.getScript( `${_url_origin}/javascripts/media/clappr-playback-rate-plugin.min.js` ), | |||
| // $.Deferred(function( deferred ){ | |||
| // $( deferred.resolve ); | |||
| // }) | |||
| // ).done(function(){ | |||
| // //place your code here, the scripts are all loaded | |||
| // const player = new window.Clappr.Player({ | |||
| // source: source, parentId: _id, | |||
| // plugins: { | |||
| // 'core': [window.Clappr.MediaControl, window.Clappr.Playback] | |||
| // } | |||
| // }); | |||
| // }); | |||
| } | |||
| } | |||
| render(){ | |||
| let { source, id, className, type } = this.props; | |||
| const _id = `_player${id}` | |||
| return( | |||
| <React.Fragment> | |||
| {/* https://github.com/CookPete/react-player/issues/686 */} | |||
| <ReactPlayer url={source} playing={false} controls={true} width={400} height={ type == 'mp3' ? 55 : 290}/> | |||
| {/* <style>{` | |||
| .playback_rate { | |||
| margin-right: 16px; | |||
| } | |||
| `}</style> | |||
| <div id={_id} className={className + ' ' + type}></div> */} | |||
| {/* 原生 */} | |||
| {/* { type == 'mp3' ? <audio src={source} preload controls></audio> | |||
| : <video src={source} controls="controls"> | |||
| 您的浏览器不支持 video 标签。 | |||
| </video>} */} | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default Clappr; | |||
| @@ -1,20 +0,0 @@ | |||
| import React from 'react' | |||
| export const themes = { | |||
| light: { | |||
| foreground: '#000000', | |||
| background: '#eeeeee', | |||
| foreground_select: '#4CACFF', | |||
| foreground_orange1: '#FF6800', | |||
| foreground_tip: '#333', | |||
| }, | |||
| dark: { | |||
| foreground: '#ffffff', | |||
| background: '#222222', | |||
| }, | |||
| }; | |||
| export const ThemeContext = React.createContext( | |||
| themes.light // default value | |||
| ); | |||
| @@ -1,29 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import {Link} from 'react-router-dom' | |||
| const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack",'greyLine':"greyLine",'orangeLine':"orangeLine", | |||
| 'colorBlue': 'colorBlue', // 蓝字白底 | |||
| } | |||
| class ActionBtn extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| } | |||
| render() { | |||
| let{to, children, style, className, ...others}=this.props | |||
| return( | |||
| <React.Fragment> | |||
| { | |||
| to==undefined ? | |||
| <a href="javascript:void(0)" onClick={this.props.onClick} | |||
| {...others} | |||
| className={"Actionbtn "+`${map[style || 'blue']} ${this.props.className}`} | |||
| >{children}</a> | |||
| : | |||
| <Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`} {...others}>{this.props.children}</Link> | |||
| } | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default ActionBtn; | |||
| @@ -1,31 +0,0 @@ | |||
| import React, { Component } from 'react'; | |||
| import {Link} from 'react-router-dom' | |||
| const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange",'colorgrey9':'color-grey-9'} | |||
| class WordsBtn extends Component { | |||
| constructor(props) { | |||
| super(props); | |||
| } | |||
| render() { | |||
| let{to, href,targets, style2, style, className, ...others }=this.props | |||
| return( | |||
| <React.Fragment> | |||
| { | |||
| to==undefined&&targets==undefined ? | |||
| <a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`} | |||
| style={style2} {...others} | |||
| >{this.props.children}</a>: | |||
| targets!=undefined? <a href={to} target="_blank" className={"btn "+`${map[this.props.style]} ${this.props.className}`} | |||
| style={style2} {...others} | |||
| >{this.props.children}</a> | |||
| : | |||
| <Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`} | |||
| style={style2} {...others} | |||
| >{this.props.children}</Link> | |||
| } | |||
| </React.Fragment> | |||
| ) | |||
| } | |||
| } | |||
| export default WordsBtn; | |||
| @@ -1,77 +0,0 @@ | |||
| //import { from } from '_array-flatten@2.1.2@array-flatten'; | |||
| // export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil'; | |||
| export { getImageUrl as getImageUrl,getmyUrl as getmyUrl, getRandomNumber as getRandomNumber,getUrl as getUrl, publicSearchs as publicSearchs,getRandomcode as getRandomcode,getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl | |||
| , getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo ,getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth | |||
| , getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode ,getupload_git_file as getupload_git_file} from './UrlTool'; | |||
| export {setmiyah as setmiyah} from './Component'; | |||
| export { default as queryString } from './UrlTool2'; | |||
| export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC'; | |||
| export { trigger as trigger, on as on, off as off | |||
| , broadcastChannelPostMessage, broadcastChannelOnmessage } from './EventUtil'; | |||
| export { updatePageParams as updatePageParams } from './RouterUtil'; | |||
| export { bytesToSize as bytesToSize } from './UnitUtil'; | |||
| export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension, | |||
| downloadFile, sortDirections } from './TextUtil' | |||
| export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil' | |||
| export { configShareForIndex, configShareForPaths, configShareForShixuns, configShareForCourses, configShareForCustom } from './util/ShareUtil' | |||
| export { isDev as isDev, isMobile } from './Env' | |||
| export { toStore as toStore, fromStore as fromStore } from './Store' | |||
| export { trace_collapse, trace, debug, info, warn, error, trace_c, debug_c, info_c, warn_c, error_c } from './LogUtil' | |||
| export { EDU_ADMIN, EDU_BUSINESS, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER | |||
| , EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from './Const' | |||
| export { default as AttachmentList } from './components/attachment/AttachmentList' | |||
| export { themes, ThemeContext } from './context/ThemeContext' | |||
| export { ModalHOC } from './components/ModalHOC' | |||
| export { SetAppModel } from './components/SetAppModel' | |||
| export { default as LinkAfterLogin } from './components/LinkAfterLogin' | |||
| export { default as Cropper } from './components/Cropper' | |||
| export { default as ConditionToolTip } from './components/ConditionToolTip' | |||
| // export { default as DragValidator } from './components/DragValidator' | |||
| export { default as PopInstruction } from './components/instruction/PopInstruction' | |||
| export { default as City } from './components/form/City' | |||
| // course | |||
| export { default as WordsBtn } from './course/WordsBtn' | |||
| export { default as ActionBtn } from './course/ActionBtn' | |||
| export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml' | |||
| export { default as DMDEditor } from './components/markdown/DMDEditor' | |||
| export { default as Clappr } from './components/media/Clappr' | |||
| export { default as AliyunUploader } from './components/media/AliyunUploader' | |||
| export { default as ImageLayer2 } from './hooks/ImageLayer2' | |||
| // 外部 | |||
| export { default as CBreadcrumb } from '../modules/courses/common/CBreadcrumb' | |||
| export { CNotificationHOC as CNotificationHOC } from '../modules/courses/common/CNotificationHOC' | |||
| export { default as ModalWrapper } from '../modules/courses/common/ModalWrapper' | |||
| export { default as NoneData } from '../modules/courses/coursesPublic/NoneData' | |||
| export {default as WordNumberTextarea} from '../modules/modals/WordNumberTextarea' | |||
| @@ -1,48 +0,0 @@ | |||
| import React, { useState, useEffect, memo } from 'react'; | |||
| import ImageLayer from '../../modules/page/layers/ImageLayer'; | |||
| import { isImageExtension } from 'educoder'; | |||
| const $ = window.$; | |||
| function ImageLayer2(props) { | |||
| const [showImage, setShowImage] = useState(false) | |||
| const [imageSrc, setImageSrc] = useState('') | |||
| const { parentSel, childSel, watchPropsArray } = props | |||
| const onImageLayerClose = () => { | |||
| setShowImage(false) | |||
| setImageSrc('') | |||
| } | |||
| const onDelegateClick = (event) => { | |||
| const imageSrc = event.target.src || event.target.getAttribute('src') || event.target.getAttribute('href') | |||
| // 判断imageSrc是否是图片 | |||
| const fileName = event.target.innerHTML.trim() | |||
| if (isImageExtension(imageSrc.trim()) || isImageExtension(fileName) || event.target.tagName == 'IMG' || imageSrc.indexOf('base64,') != -1) { | |||
| // 非回复里的头像图片; 非emoticons | |||
| if (imageSrc.indexOf('/images/avatars/User') === -1 && | |||
| imageSrc.indexOf('kindeditor/plugins/emoticons') === -1 ) { | |||
| setShowImage(true) | |||
| setImageSrc(imageSrc) | |||
| } | |||
| event.stopPropagation() | |||
| event.preventDefault && event.preventDefault() | |||
| event.originalEvent.preventDefault() | |||
| // event.originalEvent.stopPropagation() | |||
| // event.originalEvent.cancelBubble = true | |||
| return false; | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| $(parentSel) | |||
| .delegate(childSel, "click", onDelegateClick); | |||
| return () => { | |||
| $(parentSel).undelegate(childSel, "click", onDelegateClick ) | |||
| } | |||
| }) | |||
| return ( | |||
| <ImageLayer showImage={showImage} imageSrc={imageSrc} onImageLayerClose={onImageLayerClose}></ImageLayer> | |||
| ) | |||
| } | |||
| export default memo(ImageLayer2) | |||
| @@ -1,35 +0,0 @@ | |||
| /* | |||
| * @Description: 填空 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2020-01-06 09:02:29 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2020-02-05 10:44:01 | |||
| */ | |||
| import Quill from 'quill'; | |||
| let Inline = Quill.import('blots/inline'); | |||
| // const BlockEmbed = Quill.import('blots/embed'); | |||
| class FillBlot extends Inline { | |||
| static create (value) { | |||
| const node = super.cerate(value); | |||
| // node.classList.add('icon icon-bianji2'); | |||
| // node.setAttribute('data-fill', 'fill'); | |||
| console.log('编辑器值===》》》》》', value); | |||
| node.setAttribute('data_index', value.data_index); | |||
| node.nodeValue = value.text; | |||
| return node; | |||
| } | |||
| static value (node) { | |||
| return { | |||
| // dataSet: node.getAttribute('data-fill'), | |||
| data_index: node.getAttribute('data_index') | |||
| } | |||
| } | |||
| } | |||
| FillBlot.blotName = "fill"; | |||
| FillBlot.tagName = "span"; | |||
| export default FillBlot; | |||
| @@ -1,70 +0,0 @@ | |||
| /* | |||
| * @Description: 重写图片 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-16 15:50:45 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2019-12-31 13:59:02 | |||
| */ | |||
| import Quill from "quill"; | |||
| const BlockEmbed = Quill.import('blots/block/embed'); | |||
| export default class ImageBlot extends BlockEmbed { | |||
| static create(value) { | |||
| const node = super.create(); | |||
| node.setAttribute('alt', value.alt); | |||
| node.setAttribute('src', value.url); | |||
| // console.log('~~~~~~~~~~~', node, value); | |||
| node.addEventListener('click', function () { | |||
| value.onclick(value.url); | |||
| }, false); | |||
| if (value.width) { | |||
| node.setAttribute('width', value.width); | |||
| } | |||
| if (value.height) { | |||
| node.setAttribute('height', value.height); | |||
| } | |||
| if (value.id) { | |||
| node.setAttribute('id', value.id); | |||
| } | |||
| // 宽度和高度都不存在时, | |||
| if (!value.width && !value.height) { | |||
| // node.setAttribute('display', 'block'); | |||
| node.setAttribute('width', '100%'); | |||
| } | |||
| // node.setAttribute('style', { cursor: 'pointer' }); | |||
| // if (node.onclick) { | |||
| // console.log('image 有图片点击事件======》》》》》》'); | |||
| // // node.setAttribute('onclick', node.onCLick); | |||
| // } | |||
| // 给图片添加点击事件 | |||
| // node.onclick = () => { | |||
| // value.onClick && value.onClick(value.url); | |||
| // } | |||
| return node; | |||
| } | |||
| // 获取节点值 | |||
| static value (node) { | |||
| return { | |||
| alt: node.getAttribute('alt'), | |||
| url: node.getAttribute('src'), | |||
| onclick: node.onclick, | |||
| width: node.width, | |||
| height: node.height, | |||
| display: node.getAttribute('display'), | |||
| id: node.id, | |||
| // style: node.style | |||
| }; | |||
| } | |||
| } | |||
| ImageBlot.blotName = 'image'; | |||
| ImageBlot.tagName = 'img'; | |||
| @@ -1,95 +0,0 @@ | |||
| <!-- | |||
| * @Description: | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2020-01-06 16:20:03 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2020-01-09 09:45:29 | |||
| --> | |||
| ## QuillForEditor 使用 [https://quilljs.com/] | |||
| ### 导入 | |||
| - 目录 src/common/quillForEditor (默认加载当前文件夹下的 index.js 文件) | |||
| ### 参数 | |||
| | 字段 | 描述 | | |||
| | ----- | ----- | | |||
| | placeholder | 提示信息 | | |||
| | readOnly | 只读(只读取模式时,没有 工具栏且内容不可编辑,通常用于展示quill内容) | | |||
| | autoFocus | 自动获得焦点 | | |||
| | options | 配置参数, 指定工具栏内容 | | |||
| | value | 文本编辑器内容 | | |||
| | imgAttrs | 指定上传图片的尺寸 { width: 'xxpx}, height: 'xxpx'| | |||
| | style | 指定quill容器样式 | | |||
| | wrapStyle | 指定包裹quill容器的样式| | |||
| | onContentChange | 当编辑器内容变化时调用此回调函数(注: 此时返回的内容为对象,提交到后台时需要格式成 JSON 字符串: JSON.stringify(xx)) | | |||
| | showUploadImage | 点击放大上传成功后的图片, 返回上传成功后的图片 url, (评论时点击图片这么大)| | |||
| ### 添加工具栏 | |||
| - 默认所有的 | |||
| ``` | |||
| const options = [ | |||
| 'bold', // 加粗 | |||
| 'italic', // 斜体 | |||
| 'underline', // 下划线 | |||
| {size: ['12px', '14px', '16px', '18px', '20px']}, // 字体大小 | |||
| {align: []}, // 对齐方式 | |||
| {list: 'ordered'}, // 有序列表 | |||
| {list: 'bullet'}, // 无序列表 | |||
| {script: 'sub'}, // 下标 x2 | |||
| {script: 'super'}, // 上标 平方 (x2) | |||
| { 'color': [] }, // 字体颜色 | |||
| { 'background': [] }, // 背景色 | |||
| {header: [1,2,3,4,5,false]}, // H1,H2 ... | |||
| 'blockquote', // 文件左边加一个边框样式 | |||
| 'code-block', // 块内容 | |||
| 'link', // 链接 | |||
| 'image', // 图片 | |||
| 'video', // 视频 | |||
| 'formula', // 数学公式 | |||
| 'clean' // 清除 | |||
| ] | |||
| ``` | |||
| ### 使用 | |||
| ```` | |||
| 编辑模式是放不大图片的 | |||
| import QuillForEditor from 'xxx'; | |||
| // 指定需要显示的工具栏信息, 不指定加载全部 | |||
| const options = [ | |||
| ]; | |||
| /** | |||
| * @description 获取编辑器返回的内容 | |||
| * @params [Object] value 编辑器内容 | |||
| */ | |||
| const handleCtxChange = (value) => { | |||
| // 编辑器内容非空判断 | |||
| const _text = quill.getText(); | |||
| const reg = /^[\s\S]*.*[^\s][\s\S]*$/; | |||
| if (!reg.test(_text)) { | |||
| // 处理编辑器内容为空 | |||
| } else { | |||
| // 提交到后台的内容需要处理一下; | |||
| value = JSON.stringify(value) | |||
| } | |||
| } | |||
| <QuillForEditor | |||
| options={options} | |||
| onContentChange={handleCtxChange} | |||
| > | |||
| </QuillForEditor> | |||
| ```` | |||
| @@ -1,47 +0,0 @@ | |||
| function deepEqual (prev, current) { | |||
| if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined | |||
| return true; | |||
| } | |||
| if ((!prev && current) | |||
| || (prev && !current) | |||
| || (!prev && !current) | |||
| ) { | |||
| return false; | |||
| } | |||
| if (Array.isArray(prev)) { | |||
| if (!Array.isArray(current)) return false; | |||
| if (prev.length !== current.length) return false; | |||
| for (let i = 0; i < prev.length; i++) { | |||
| if (!deepEqual(current[i], prev[i])) { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| if (typeof current === 'object') { | |||
| if (typeof prev !== 'object') return false; | |||
| const prevKeys = Object.keys(prev); | |||
| const curKeys = Object.keys(current); | |||
| if (prevKeys.length !== curKeys.length) return false; | |||
| prevKeys.sort(); | |||
| curKeys.sort(); | |||
| for (let i = 0; i < prevKeys.length; i++) { | |||
| if (prevKeys[i] !== curKeys[i]) return false; | |||
| const key = prevKeys[i]; | |||
| if (!deepEqual(prev[key], current[key])) return false; | |||
| } | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| export default deepEqual; | |||
| @@ -1,280 +0,0 @@ | |||
| /* | |||
| * @Description: quill 编辑器 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-18 08:49:30 | |||
| * @LastEditors : tangjiang | |||
| * @LastEditTime : 2020-02-05 11:23:03 | |||
| */ | |||
| import './index.scss'; | |||
| import 'quill/dist/quill.core.css'; // 核心样式 | |||
| import 'quill/dist/quill.snow.css'; // 有工具栏 | |||
| import 'quill/dist/quill.bubble.css'; // 无工具栏 | |||
| import 'katex/dist/katex.min.css'; // katex 表达式样式 | |||
| import React, { useState, useRef, useEffect } from 'react'; | |||
| import Quill from 'quill'; | |||
| import katex from 'katex'; | |||
| import deepEqual from './deepEqual.js' | |||
| import { fetchUploadImage } from '../../services/ojService.js'; | |||
| import { getImageUrl } from 'educoder' | |||
| import ImageBlot from './ImageBlot'; | |||
| import FillBlot from './FillBlot'; | |||
| const Size = Quill.import('attributors/style/size'); | |||
| const Font = Quill.import('formats/font'); | |||
| // const Color = Quill.import('attributes/style/color'); | |||
| Size.whitelist = ['12px', '14px', '16px', '18px', '20px', false]; | |||
| Font.whitelist = ['SimSun', 'SimHei','Microsoft-YaHei','KaiTi','FangSong','Arial','Times-New-Roman','sans-serif']; | |||
| window.Quill = Quill; | |||
| window.katex = katex; | |||
| Quill.register(ImageBlot); | |||
| Quill.register(Size); | |||
| Quill.register(Font, true); | |||
| // Quill.register({'modules/toolbar': Toolbar}); | |||
| Quill.register({ | |||
| 'formats/fill': FillBlot | |||
| }); | |||
| // Quill.register(Color); | |||
| function QuillForEditor ({ | |||
| placeholder, | |||
| readOnly, | |||
| autoFocus = false, | |||
| options, | |||
| value, | |||
| imgAttrs = {}, // 指定图片的宽高 | |||
| style = {}, | |||
| wrapStyle = {}, | |||
| showUploadImage, | |||
| onContentChange, | |||
| addFill, // 点击填空成功的回调 | |||
| deleteFill // 删除填空,返回删除的下标 | |||
| // getQuillContent | |||
| }) { | |||
| // toolbar 默认值 | |||
| const defaultConfig = [ | |||
| 'bold', 'italic', 'underline', | |||
| {size: ['12px', '14px', '16px', '18px', '20px']}, | |||
| {align: []}, {list: 'ordered'}, {list: 'bullet'}, // 列表 | |||
| {script: 'sub'}, {script: 'super'}, | |||
| { 'color': [] }, { 'background': [] }, | |||
| {header: [1,2,3,4,5,false]}, | |||
| 'blockquote', 'code-block', | |||
| 'link', 'image', 'video', | |||
| 'formula', | |||
| 'clean' | |||
| ]; | |||
| const editorRef = useRef(null); | |||
| // quill 实例 | |||
| const [quill, setQuill] = useState(null); | |||
| const [selection, setSelection] = useState(null); | |||
| const [fillCount, setFillCount] = useState(0); | |||
| const [quillCtx, setQuillCtx] = useState({}); | |||
| // 文本内容变化时 | |||
| const handleOnChange = content => { | |||
| // getQuillContent && getQuillContent(quill); | |||
| onContentChange && onContentChange(content, quill); | |||
| }; | |||
| const renderOptions = options || defaultConfig; | |||
| const bindings = { | |||
| tab: { | |||
| key: 9, | |||
| handler: function () { | |||
| console.log('调用了tab=====>>>>'); | |||
| } | |||
| }, | |||
| backspace: { | |||
| key: 'Backspace', | |||
| /** | |||
| * @param {*} range | |||
| * { index, // 删除元素的位置 | |||
| * length // 删除元素的个数, 当删除一个时, length=0, 其它等于删除的元素的个数 | |||
| * } | |||
| * @param {*} context 上下文 | |||
| */ | |||
| handler: function (range, context) { | |||
| /** | |||
| * index: 删除元素的位置 | |||
| * length: 删除元素的个数 | |||
| */ | |||
| const {index, length} = range; | |||
| const _start = length === 0 ? index - 1 : index; | |||
| const _length = length || 1; | |||
| let delCtx = this.quill.getText(_start, _length); // 删除的元素 | |||
| // aa | |||
| const reg = /▁/g; | |||
| const delArrs = delCtx.match(reg); | |||
| if (delArrs) { | |||
| const r = window.confirm('确定要删除吗?'); | |||
| if (r) { | |||
| let leaveCtx; // 获取删除元素之前的内容 | |||
| if (length === 0) { | |||
| leaveCtx = this.quill.getText(0, index - 1); | |||
| } else { | |||
| leaveCtx = this.quill.getText(0, index); | |||
| } | |||
| const leaveArrs = leaveCtx.match(reg); | |||
| const leaveLen = (leaveArrs || []).length; | |||
| let delIndexs = []; | |||
| // 获取删除元素的下标 | |||
| delArrs.forEach((item, i) => { | |||
| leaveLen === 0 ? delIndexs.push(i) : delIndexs.push(leaveLen + i); | |||
| }); | |||
| deleteFill && deleteFill(delIndexs); // 调用删除回调, 返回删除的元素下标[] | |||
| return true | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| }; | |||
| // quill 配置信息 | |||
| const quillOption = { | |||
| modules: { | |||
| toolbar: renderOptions, | |||
| keyboard: { | |||
| bindings: bindings | |||
| } | |||
| // toolbar: { | |||
| // container: renderOptions | |||
| // } | |||
| }, | |||
| readOnly, | |||
| placeholder, | |||
| theme: readOnly ? 'bubble' : 'snow', | |||
| }; | |||
| useEffect(() => { | |||
| const quillNode = document.createElement('div'); | |||
| editorRef.current.appendChild(quillNode); | |||
| const _quill = new Quill(editorRef.current, quillOption); | |||
| setQuill(_quill); | |||
| // 处理图片上传功能 | |||
| _quill.getModule('toolbar').addHandler('image', (e) => { | |||
| const input = document.createElement('input'); | |||
| input.setAttribute('type', 'file'); | |||
| input.setAttribute('accept', 'image/*'); | |||
| input.click(); | |||
| input.onchange = async (e) => { | |||
| const file = input.files[0]; // 获取文件信息 | |||
| const formData = new FormData(); | |||
| formData.append('file', file); | |||
| const range = _quill.getSelection(true); | |||
| let fileUrl = ''; // 保存上传成功后图片的url | |||
| // 上传文件 | |||
| const result = await fetchUploadImage(formData); | |||
| // 获取上传图片的url | |||
| if (result.data && result.data.id) { | |||
| fileUrl = getImageUrl(`api/attachments/${result.data.id}`); | |||
| } | |||
| // 根据id获取文件路径 | |||
| const { width, height } = imgAttrs; | |||
| // console.log('上传图片的url:', fileUrl); | |||
| if (fileUrl) { | |||
| _quill.insertEmbed(range.index, 'image', { | |||
| url: fileUrl, | |||
| alt: '图片信息', | |||
| onClick: showUploadImage, | |||
| width, | |||
| height | |||
| }); | |||
| } | |||
| } | |||
| }); | |||
| // 处理填空 | |||
| _quill.getModule('toolbar').addHandler('fill', (e) => { | |||
| // alert(1111); | |||
| setFillCount(fillCount + 1); | |||
| const range = _quill.getSelection(true); | |||
| _quill.insertText(range.index, '▁'); | |||
| addFill && addFill(); // 调用添加回调 | |||
| }); | |||
| }, []); | |||
| // 设置值 | |||
| useEffect(() => { | |||
| if (!quill) return | |||
| const previous = quill.getContents() | |||
| if (value && value.hasOwnProperty('ops')) { | |||
| // console.log(value.ops); | |||
| const ops = value.ops || []; | |||
| ops.forEach((item, i) => { | |||
| if (item.insert['image']) { | |||
| item.insert['image'] = Object.assign({}, item.insert['image'], {style: { cursor: 'pointer' }, onclick: (url) => showUploadImage(url)}); | |||
| } | |||
| }); | |||
| } | |||
| const current = value | |||
| if (!deepEqual(previous, current)) { | |||
| setSelection(quill.getSelection()) | |||
| if (typeof value === 'string' && value) { | |||
| // debugger | |||
| quill.clipboard.dangerouslyPasteHTML(value, 'api'); | |||
| if (autoFocus) { | |||
| quill.focus(); | |||
| } else { | |||
| quill.blur(); | |||
| } | |||
| } else { | |||
| quill.setContents(value) | |||
| if (autoFocus) quill.focus(); | |||
| } | |||
| } | |||
| }, [quill, value, setQuill, autoFocus]); | |||
| // 清除选择区域 | |||
| useEffect(() => { | |||
| if (quill && selection) { | |||
| quill.setSelection(selection) | |||
| setSelection(null) | |||
| } | |||
| }, [quill, selection, setSelection]); | |||
| // 设置placeholder值 | |||
| useEffect(() => { | |||
| if (!quill || !quill.root) return; | |||
| quill.root.dataset.placeholder = placeholder; | |||
| }, [quill, placeholder]); | |||
| // 处理内容变化 | |||
| useEffect(() => { | |||
| if (!quill) return; | |||
| if (typeof handleOnChange !== 'function') return; | |||
| let handler; | |||
| quill.on( | |||
| 'text-change', | |||
| (handler = (delta, oldDelta, source) => { | |||
| const _ctx = quill.getContents(); | |||
| setQuillCtx(_ctx); | |||
| handleOnChange(quill.getContents()); // getContents: 检索编辑器内容 | |||
| }) | |||
| ); | |||
| return () => { | |||
| quill.off('text-change', handler); | |||
| } | |||
| }, [quill, handleOnChange]); | |||
| // 返回结果 | |||
| return ( | |||
| <div className='quill_editor_for_react_area' style={wrapStyle}> | |||
| <div ref={editorRef} style={style}></div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default QuillForEditor; | |||
| @@ -1,132 +0,0 @@ | |||
| .quill_editor_for_react_area{ | |||
| // background: #fff; | |||
| // margin: 0 15px; | |||
| .ql-editing{ | |||
| left: 0 !important; | |||
| } | |||
| .ql-editor{ | |||
| img{ | |||
| cursor: pointer; | |||
| } | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before { | |||
| content: '12px'; | |||
| font-size: 12px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before { | |||
| content: '14px'; | |||
| font-size: 14px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before { | |||
| content: '16px'; | |||
| font-size: 16px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before { | |||
| content: '18px'; | |||
| font-size: 18px; | |||
| } | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before { | |||
| content: '20px'; | |||
| font-size: 20px; | |||
| } | |||
| //默认的样式 | |||
| .ql-snow .ql-picker.ql-size .ql-picker-label::before, | |||
| .ql-snow .ql-picker.ql-size .ql-picker-item::before { | |||
| content: '14px'; | |||
| font-size: 14px; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before { | |||
| content: "宋体"; | |||
| font-family: "SimSun"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before { | |||
| content: "黑体"; | |||
| font-family: "SimHei"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before { | |||
| content: "微软雅黑"; | |||
| font-family: "Microsoft YaHei"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before { | |||
| content: "楷体"; | |||
| font-family: "KaiTi"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before { | |||
| content: "仿宋"; | |||
| font-family: "FangSong"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before { | |||
| content: "Arial"; | |||
| font-family: "Arial"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before { | |||
| content: "Times New Roman"; | |||
| font-family: "Times New Roman"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before { | |||
| content: "sans-serif"; | |||
| font-family: "sans-serif"; | |||
| } | |||
| .ql-font-SimSun { | |||
| font-family: "SimSun"; | |||
| } | |||
| .ql-font-SimHei { | |||
| font-family: "SimHei"; | |||
| } | |||
| .ql-font-Microsoft-YaHei { | |||
| font-family: "Microsoft YaHei"; | |||
| } | |||
| .ql-font-KaiTi { | |||
| font-family: "KaiTi"; | |||
| } | |||
| .ql-font-FangSong { | |||
| font-family: "FangSong"; | |||
| } | |||
| .ql-font-Arial { | |||
| font-family: "Arial"; | |||
| } | |||
| .ql-font-Times-New-Roman { | |||
| font-family: "Times New Roman"; | |||
| } | |||
| .ql-font-sans-serif { | |||
| font-family: "sans-serif"; | |||
| } | |||
| .ql-snow .ql-picker.ql-font .ql-picker-label::before, | |||
| .ql-snow .ql-picker.ql-font .ql-picker-item::before { | |||
| content: "微软雅黑"; | |||
| font-family: "Microsoft YaHei"; | |||
| } | |||
| // 填空图标 | |||
| .ql-snow .ql-fill{ | |||
| display: inline-block; | |||
| position: relative; | |||
| color: #05101A; | |||
| // font-size: 18px; | |||
| vertical-align: top; | |||
| &::before{ | |||
| position: absolute; | |||
| left: 50%; | |||
| top: -1px; | |||
| content: '\e709'; | |||
| font-family: 'iconfont'; | |||
| margin-left: -7px; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,54 +0,0 @@ | |||
| /* | |||
| * @Description: 重写图片 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-16 15:50:45 | |||
| * @LastEditors: tangjiang | |||
| * @LastEditTime: 2019-12-17 16:44:48 | |||
| */ | |||
| import Quill from "quill"; | |||
| const BlockEmbed = Quill.import('blots/block/embed'); | |||
| export default class ImageBlot extends BlockEmbed { | |||
| static create(value) { | |||
| const node = super.create(); | |||
| node.setAttribute('alt', value.alt); | |||
| node.setAttribute('src', value.url); | |||
| if (value.width) { | |||
| node.setAttribute('width', value.width); | |||
| } | |||
| if (value.height) { | |||
| node.setAttribute('height', value.height); | |||
| } | |||
| // 宽度和高度都不存在时, | |||
| if (!value.width && !value.height) { | |||
| node.setAttribute('display', 'block'); | |||
| node.setAttribute('width', '100%'); | |||
| } | |||
| // 给图片添加点击事件 | |||
| node.onclick = () => { | |||
| value.onClick && value.onClick(value.url); | |||
| } | |||
| return node; | |||
| } | |||
| static value (node) { | |||
| return { | |||
| alt: node.getAttribute('alt'), | |||
| url: node.getAttribute('src'), | |||
| onclick: node.onclick, | |||
| // width: node.width, | |||
| // height: node.height, | |||
| display: node.getAttribute('display') | |||
| }; | |||
| } | |||
| } | |||
| ImageBlot.blotName = 'image'; | |||
| ImageBlot.tagName = 'img'; | |||
| @@ -1,45 +0,0 @@ | |||
| /* | |||
| * @Description: | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-09 09:09:42 | |||
| * @LastEditors: tangjiang | |||
| * @LastEditTime: 2019-12-18 08:46:20 | |||
| */ | |||
| import 'quill/dist/quill.core.css'; // 核心样式 | |||
| import 'quill/dist/quill.snow.css'; // 有工具栏 | |||
| import 'quill/dist/quill.bubble.css'; // 无工具栏 | |||
| import 'katex/dist/katex.min.css'; // katex 表达式样式 | |||
| import React, { useState, useReducer, useEffect } from 'react'; | |||
| import useQuill from './useQuill'; | |||
| function ReactQuill ({ | |||
| disallowColors, // 不可见时颜色 | |||
| placeholder, // 提示信息 | |||
| uploadImage, // 图片上传 | |||
| onChange, // 内容变化时 | |||
| options, // 配置信息 | |||
| value, // 显示的内容 | |||
| style, | |||
| showUploadImage // 显示上传图片 | |||
| }) { | |||
| const [element, setElement] = useState(); // quill 渲染节点 | |||
| useQuill({ | |||
| disallowColors, | |||
| placeholder, | |||
| uploadImage, | |||
| onChange, | |||
| options, | |||
| value, | |||
| showUploadImage, | |||
| element | |||
| }); | |||
| return ( | |||
| <div className='react_quill_area' ref={setElement} style={style}/> | |||
| ); | |||
| } | |||
| export default ReactQuill; | |||
| @@ -1,47 +0,0 @@ | |||
| function deepEqual (prev, current) { | |||
| if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined | |||
| return true; | |||
| } | |||
| if ((!prev && current) | |||
| || (prev && !current) | |||
| || (!prev && !current) | |||
| ) { | |||
| return false; | |||
| } | |||
| if (Array.isArray(prev)) { | |||
| if (!Array.isArray(current)) return false; | |||
| if (prev.length !== current.length) return false; | |||
| for (let i = 0; i < prev.length; i++) { | |||
| if (!deepEqual(current[i], prev[i])) { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| if (typeof current === 'object') { | |||
| if (typeof prev !== 'object') return false; | |||
| const prevKeys = Object.keys(prev); | |||
| const curKeys = Object.keys(current); | |||
| if (prevKeys.length !== curKeys.length) return false; | |||
| prevKeys.sort(); | |||
| curKeys.sort(); | |||
| for (let i = 0; i < prevKeys.length; i++) { | |||
| if (prevKeys[i] !== curKeys[i]) return false; | |||
| const key = prevKeys[i]; | |||
| if (!deepEqual(prev[key], current[key])) return false; | |||
| } | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| export default deepEqual; | |||
| @@ -1,26 +0,0 @@ | |||
| /* | |||
| * @Description: 将多维数组转变成一维数组 | |||
| * @Author: tangjiang | |||
| * @Github: | |||
| * @Date: 2019-12-09 09:35:01 | |||
| * @LastEditors: tangjiang | |||
| * @LastEditTime: 2019-12-16 11:36:22 | |||
| */ | |||
| function flatten (array) { | |||
| return flatten.rec(array, []); | |||
| } | |||
| flatten.rec = function flatten (array, result) { | |||
| for (let item of array) { | |||
| if (Array.isArray(item)) { | |||
| flatten(item, result); | |||
| } else { | |||
| result.push(item); | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| export default flatten; | |||