Merge branch 'develop' into dev_cxt
This commit is contained in:
commit
c49191b70f
2
Gemfile
2
Gemfile
|
|
@ -75,7 +75,7 @@ group :development, :test do
|
|||
gem 'pry-byebug'
|
||||
gem "test-unit", "~>3.0"
|
||||
end
|
||||
gem 'rspec-rails', '~> 3.0'
|
||||
# gem 'rspec-rails', '~> 3.0'
|
||||
gem 'factory_girl_rails'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -268,6 +268,8 @@ class AttachmentsController < ApplicationController
|
|||
|
||||
|
||||
def upload
|
||||
|
||||
|
||||
# Make sure that API users get used to set this content type
|
||||
# as it won't trigger Rails' automatic parsing of the request body for parameters
|
||||
unless request.content_type == 'application/octet-stream'
|
||||
|
|
@ -295,6 +297,26 @@ class AttachmentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def upload_with_markdown
|
||||
logger.debug "upload_with_markdown"
|
||||
|
||||
|
||||
@attachment = Attachment.new(:file => params["editormd-image-file"])
|
||||
@attachment.author = User.current
|
||||
if !params[:project].nil?
|
||||
@attachment.container_type = 'Project'
|
||||
@attachment.container_id = params[:project].split("?")[0]
|
||||
end
|
||||
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
|
||||
saved = @attachment.save
|
||||
|
||||
render json: {
|
||||
success: saved ? 1 : 0, # | 1, // 0 表示上传失败,1 表示上传成功
|
||||
message: "上传失败:#{ @attachment.errors.full_messages.join(',') }",
|
||||
url: download_attachment_path(@attachment.id) # 上传成功时才返回
|
||||
}
|
||||
end
|
||||
|
||||
def upload_attachment_version
|
||||
@flag = false
|
||||
Attachment.transaction do
|
||||
|
|
|
|||
|
|
@ -24,34 +24,49 @@ class ChallengesController < ApplicationController
|
|||
@challenge.position = challenge_count + 1
|
||||
@challenge.shixun = @shixun
|
||||
@challenge.user = User.current
|
||||
if @challenge.save
|
||||
if params[:sample][:input].length > 0
|
||||
params[:sample][:input].each_with_index do |value, index|
|
||||
unless (value == params[:sample][:output][index] && value.blank?)
|
||||
ChallengeSample.create(:challenge_id => @challenge.id, :input => value, :output => params[:sample][:output][index])
|
||||
ActiveRecord::Base.transaction do
|
||||
begin
|
||||
@challenge.save!
|
||||
if params[:test_set][:hidden].length > 0
|
||||
params[:test_set][:input].each_with_index do |input, index|
|
||||
params[:test_set][:hidden][index].to_i == 0 ? open = 1 : open = 0
|
||||
unless (input[index] == params[:test_set][:output][index] && input[index].nil?)
|
||||
TestSet.create(:challenge_id => @challenge.id, :input => input, :output => params[:test_set][:output][index], :is_public => open, :position => (index + 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if params[:program][:output].length > 0
|
||||
params[:program][:output].each do |output|
|
||||
TestSet.create(:challenge_id => @challenge.id, :input => nil, :output => output)
|
||||
unless params[:knowledge].blank?
|
||||
params[:knowledge][:input].each do |input|
|
||||
ChallengeTag.create(:name => input, :challenge_id => @challenge.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
unless params[:knowledge].blank?
|
||||
params[:knowledge][:input].each do |input|
|
||||
ChallengeTag.create(:name => input, :challenge_id => @challenge.id)
|
||||
# 向jenkins端发送请求,构建pipeline片段,实时返回结果
|
||||
jenkins_shixuns = Redmine::Configuration['jenkins_shixuns']
|
||||
params = {:challengeStage => "#{@challenge.position}", :challengeType => "#{@challenge.evaluation_way.to_i}", :challengeProgramName => "#{@challenge.path}", :trainingID => "#{@shixun.id}"}
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/game/generatePipelineScriptForChallenge")
|
||||
res = uri_exec uri, {challengeInfo: params.to_json}
|
||||
if res['code'] == 0
|
||||
@challenge.update_attribute(:pipeline_script, res['msg'])
|
||||
respond_to do |format|
|
||||
format.html {redirect_to shixun_challenge_path(@challenge, :shixun_id => @shixun)}
|
||||
end
|
||||
else
|
||||
logger.error("res['code'] is #{res['code']}, res['msg'] is #{res['code']}")
|
||||
raise("脚本构建失败")
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html {redirect_to shixun_challenge_path(@challenge, :shixun_id => @shixun), notice: '创建成功!'}
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new' }
|
||||
rescue Exception => e
|
||||
flash[:error] = "#{e.message}"
|
||||
redirect_to new_shixun_challenge_path(:shixun_id => @shixun.id)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 处理新建challenge返回的pipeline片段
|
||||
def fragment_from_jenkins
|
||||
|
||||
end
|
||||
|
||||
def index
|
||||
# 顶部导航
|
||||
@challenges = @shixun.challenges.order("position desc")
|
||||
|
|
@ -85,21 +100,16 @@ class ChallengesController < ApplicationController
|
|||
respond_to do |format|
|
||||
if @challenge.update_attributes(params[:challenge])
|
||||
ActiveRecord::Base.transaction do
|
||||
@challenge_samples.delete_all unless @challenge_samples.blank?
|
||||
@test_sets.delete_all unless @test_sets.blank?
|
||||
@challenge_tags.delete_all unless @challenge_tags.blank?
|
||||
if params[:sample][:input].length > 0
|
||||
params[:sample][:input].each_with_index do |value, index|
|
||||
unless (value == params[:sample][:output][index] && value.blank?)
|
||||
ChallengeSample.create(:challenge_id => @challenge.id, :input => value, :output => params[:sample][:output][index])
|
||||
@challenge_tags.delete_all unless @challenge_tags.blank?
|
||||
if params[:test_set][:hidden].length > 0
|
||||
params[:test_set][:input].each_with_index do |input, index|
|
||||
unless (input[index] == params[:test_set][:output][index] && input[index].nil?)
|
||||
params[:test_set][:hidden][index].to_i == 0 ? open = 1 : open = 0
|
||||
TestSet.create(:challenge_id => @challenge.id, :input => input, :output => params[:test_set][:output][index], :is_public => open, :position => (index + 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
if params[:program][:output].length > 0
|
||||
params[:program][:output].each do |output|
|
||||
TestSet.create(:challenge_id => @challenge.id, :input => nil, :output => output)
|
||||
end
|
||||
end
|
||||
unless params[:knowledge].blank?
|
||||
params[:knowledge][:input].each do |input|
|
||||
ChallengeTag.create(:name => input, :challenge_id => @challenge.id)
|
||||
|
|
@ -181,7 +191,6 @@ class ChallengesController < ApplicationController
|
|||
end
|
||||
|
||||
def query_challeges
|
||||
@challenge_samples = @challenge.challenge_samples
|
||||
@test_sets = @challenge.test_sets
|
||||
@challenge_tags = @challenge.challenge_tags
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,8 +22,12 @@ class GamesController < ApplicationController
|
|||
# mushixun的版本库必须创建时就创建
|
||||
# 首次进入版本库自动打开文件
|
||||
# path:"" && path: @game.path
|
||||
# @praise 判断是否点赞
|
||||
# @praise 判断是否踩
|
||||
def show
|
||||
@game_challenge = @game.challenge
|
||||
@praise = PraiseTread.where("praise_tread_object_id=? and praise_tread_object_type=? and user_id=?", @game_challenge.id, @game_challenge.class.to_s, User.current.id).first
|
||||
@tread = PraiseTread.where("praise_tread_object_id=? and praise_tread_object_type=? and user_id=?", @game_challenge.id, "ChallengeTread", User.current.id).first
|
||||
logger.info("test ................#{@game_challenge.id}")
|
||||
game_path = (@game_challenge.path.blank? ? @path : @game_challenge.path.strip)
|
||||
logger.info("test game_path ................#{game_path}")
|
||||
|
|
@ -46,19 +50,14 @@ class GamesController < ApplicationController
|
|||
logger.info("7777************#{@entries} ")
|
||||
end
|
||||
logger.info("88888************#{@entries} ")
|
||||
@latest_output = @game.latest_output.try(:out_put).to_s
|
||||
outputs = @game.outputs
|
||||
(@myshixun.games.count == @game_challenge.position && @game.status ==2) ? @had_done = 1 : @had_done = 0
|
||||
if outputs.count == 0
|
||||
@results = []
|
||||
else
|
||||
@results = outputs.map{|result| [result.code, result.id]}
|
||||
@test_sets = @game_challenge.test_sets
|
||||
@test_sets.each_with_index do |test_set, index|
|
||||
@test_set_error_position = (index + 1) unless test_set.result
|
||||
end
|
||||
logger.info("##################languae is #{@language}")
|
||||
logger.info("##################latest_output is #{@latest_output}")
|
||||
logger.info("##################had_done is #{@had_done}")
|
||||
logger.info("##################status is #{@game.status}")
|
||||
logger.info("##################result is #{@results}")
|
||||
@test_sets_count = @test_sets.count
|
||||
(@myshixun.games.count == @game_challenge.position && @game.status ==2) ? @had_done = 1 : @had_done = 0
|
||||
@test_sets_hidden_count = @test_sets.where(:is_public => false).count
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
|
|
@ -110,18 +109,10 @@ class GamesController < ApplicationController
|
|||
jobName = "myshixun_#{@myshixun.id}"
|
||||
ActiveRecord::Base.transaction do
|
||||
@game.update_attributes!(:status => 1)
|
||||
testCode = {}
|
||||
test_sets = game_challenge.test_sets
|
||||
unless test_sets.blank?
|
||||
test_sets.each_with_index do |test_set, index|
|
||||
testCode.store("testCode_#{index}",test_set.try(:output))
|
||||
end
|
||||
end
|
||||
testCode = testCode.to_json
|
||||
jenkins_shixuns = Redmine::Configuration['jenkins_shixuns']
|
||||
step = game_challenge.try(:position)
|
||||
params = {:jobName => "#{jobName}", :taskId => "#{taskId}", :step => "#{step}", :gitUrl => "#{gitUrl}", :testCode => "#{testCode}"}
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/api/buildJob")
|
||||
params = {:jobNameForInstance => "#{jobName}", :buildID => "#{taskId}", :step => "#{step}", :instanceGitURL => "#{gitUrl}", :instanceChallenge => "#{step}"}
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/api/buildJobForInstance")
|
||||
res = uri_exec uri, params
|
||||
if (res && res['code'] != 0)
|
||||
raise("Build job failed")
|
||||
|
|
@ -167,7 +158,7 @@ class GamesController < ApplicationController
|
|||
def next_step
|
||||
render_403 if @game.status != 2
|
||||
next_game = @game.next_game
|
||||
next_game.update_attribute(:status, 0) if next_game.status == 3
|
||||
next_game.update_attributes(:status => 0, :open_time => Time.now) if next_game.status == 3
|
||||
|
||||
respond_to do |format|
|
||||
format.js{redirect_to myshixun_game_path(next_game, :myshixun_id => @myshixun.id)}
|
||||
|
|
|
|||
|
|
@ -5,28 +5,33 @@ class MyshixunsController < ApplicationController
|
|||
before_filter :find_myshixun, :only => [:show]
|
||||
|
||||
# taskId 即返回的game id
|
||||
# params [:stauts] 0 表示成功,其它则失败
|
||||
# 返回结果:params [:stauts] 0 表示成功,其它则失败
|
||||
# msg 错误信息
|
||||
# output 为测试用户编译输出结果
|
||||
# myshixun:status 1为完成实训
|
||||
# @jenkins: caseId对应test_set的position,passed: 1表示成功,0表示失败
|
||||
def training_task_status
|
||||
status = params[:status].to_i
|
||||
task_id = params[:taskId]
|
||||
outPut = Base64.decode64(params[:outPut]) unless params[:outPut].blank?
|
||||
message = Base64.decode64(params[:msg]) unless params[:msg].blank?
|
||||
game = Game.find(task_id)
|
||||
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
|
||||
status = jsonTestDetails['status']
|
||||
game_id = jsonTestDetails['buildID']
|
||||
outPut = Base64.decode64(jsonTestDetails['outPut']) unless params[:outPut].blank?
|
||||
jenkins_testsets = jsonTestDetails['msg']
|
||||
# message = Base64.decode64(params[:msg]) unless params[:msg].blank?
|
||||
game = Game.find(game_id)
|
||||
challenge = game.challenge
|
||||
if status == 0
|
||||
myshixun = game.myshixun
|
||||
games_count = myshixun.games.count
|
||||
if challenge.position == games_count
|
||||
myshixun.update_attribute(:status, 1)
|
||||
test_sets = challenge.test_sets
|
||||
game_outputs = Output.create(:code => status, :game_id => game_id, :out_put => outPut)
|
||||
unless jenkins_testsets.blank?
|
||||
jenkins_testsets.each do |j_test_set|
|
||||
test_set = TestSet.where(:position => j_test_set['caseId'], :challenge_id => challenge).first
|
||||
test_set.update_attributes(:actual_output => j_test_set['expectedOutput'], :result => j_test_set['passed'].to_i)
|
||||
end
|
||||
game_outputs = Output.create(:code => status, :msg => message, :game_id => task_id, :out_put => outPut)
|
||||
end
|
||||
if status == 0
|
||||
game.update_attribute(:status, 2)
|
||||
game.update_attribute(:final_score, challenge.score)
|
||||
else
|
||||
game_outputs = Output.create(:code => status, :msg => message, :game_id => task_id, :out_put => outPut)
|
||||
|
||||
game.update_attribute(:status, 0)
|
||||
end
|
||||
render :json => {:data => "success"}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,19 @@ class PraiseTreadController < ApplicationController
|
|||
end
|
||||
# @is_in_list = nil
|
||||
@obj = find_object_by_type_and_id(@obj_type,@obj_id)
|
||||
pts = PraiseTread.where("praise_tread_object_id=? and praise_tread_object_type=? and user_id=?",@obj_id,@obj_type.to_s,User.current.id)
|
||||
unless pts.empty?
|
||||
pts = PraiseTread.where("praise_tread_object_id=? and praise_tread_object_type=? and user_id=?",@obj_id,@obj_type.to_s,User.current.id).first
|
||||
unless pts.blank?
|
||||
if(params[:game_praise] == "true")
|
||||
pts.delete
|
||||
render :json => {praise: false}
|
||||
return
|
||||
end
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@horizontal = params[:horizontal].downcase == "false" ? false:true if params[:horizontal]
|
||||
# if @obj.respond_to?("author_id")
|
||||
# author_id = @obj.author_id
|
||||
|
|
@ -33,6 +39,10 @@ class PraiseTreadController < ApplicationController
|
|||
# unless author_id == User.current.id
|
||||
praise_tread_plus(@obj_type,@obj_id,1)
|
||||
# end
|
||||
if params[:game_praise] == "true"
|
||||
render :json => {praise: true}
|
||||
return
|
||||
end
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
|
|
@ -137,6 +147,8 @@ class PraiseTreadController < ApplicationController
|
|||
@obj = Syllabus.find_by_id(id)
|
||||
when 'Work'
|
||||
@obj = Work.find_by_id(id)
|
||||
when 'Challenge' || 'ChallengeTread'
|
||||
@obj = Challenge.find_by_id(id)
|
||||
else
|
||||
@obj = nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,28 +5,75 @@ class ShixunsController < ApplicationController
|
|||
before_filter :require_login
|
||||
before_filter :find_shixun, :except => [ :index, :new, :create]
|
||||
before_filter :shixun_view_allow, :only => [:show]
|
||||
before_filter :require_manager, :only => [ :settings, :add_script]
|
||||
before_filter :require_manager, :only => [ :settings, :add_script, :publish]
|
||||
|
||||
include ApplicationHelper
|
||||
|
||||
def shixun_monitor
|
||||
monitor_filter
|
||||
if @had_exec
|
||||
@tpm = Myshixun.where(:user_id => User.current, :shixun_id => @shixun).first
|
||||
end
|
||||
# monitor_filter
|
||||
# if @had_exec
|
||||
# @tpm = Myshixun.where(:user_id => User.current, :shixun_id => @shixun).first
|
||||
# end
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.js
|
||||
# end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
# jenkins回调脚本
|
||||
# gameID 及实训ID
|
||||
def publish
|
||||
jenkins_shixuns = Redmine::Configuration['jenkins_shixuns']
|
||||
fragments = []
|
||||
testSet = []
|
||||
testCases = []
|
||||
gameInfo = []
|
||||
begin
|
||||
challenges = @shixun.challenges.includes(:test_sets)
|
||||
challenges.each do |challenge|
|
||||
pipeline_script = challenge.try(:pipeline_script)
|
||||
challenge_script = {:challengeStage => "#{challenge.position}", :challengePipeline => "#{pipeline_script}"}
|
||||
fragments << challenge_script
|
||||
challenge.test_sets.each do |test_set|
|
||||
test_cases = {:caseId => test_set.id.to_s, :input => test_set.input, :output => test_set.output}
|
||||
testSet << test_cases
|
||||
end
|
||||
test_sets_list = {:challengeStage => challenge.position.to_s, :challengeTestCases => testSet}
|
||||
testCases << test_sets_list
|
||||
end
|
||||
gameInfo = {:trainingID => @shixun.id.to_s, :operationEnvironment => @shixun.language, :challengePipelines => fragments, :testCases => testCases}
|
||||
fragments = fragments.to_json
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/game/publishGame")
|
||||
params = {gameInfo:gameInfo.to_json}
|
||||
res = uri_exec uri, params
|
||||
if res['code'] == 0
|
||||
@shixun.update_attribute(:status, 1)
|
||||
@shixun.update_attribute(:script, res["msg"])
|
||||
else
|
||||
logger.error("res['code'] is #{res['code']}, res['msg'] is #{res['code']}")
|
||||
@jenkin_error = true
|
||||
raise("服务器异常,请联系系统管理员")
|
||||
end
|
||||
rescue
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# copy_shixun:复制一个新的实训模块包括版本库
|
||||
# copy_myshixun自动创建系列game,game中只包含状态等信息,公共信息从Challeges中读取
|
||||
#
|
||||
# 开启过实训的则直接跳入my实训页面
|
||||
def shixun_exec
|
||||
if has_exec_cur_shixun(@shixun) || User.current.id == @shixun.user_id
|
||||
unless allow_shixun_exec(@shixun)
|
||||
render_403
|
||||
end
|
||||
myshixun = Myshixun.where(:user_id => User.current.id, :shixun_id => @shixun.id).first
|
||||
unless myshixun.blank?
|
||||
logger.info("current task id is #{myshixun.current_task}")
|
||||
redirect_to myshixun_game_path(myshixun.current_task, :myshixun_id => myshixun)
|
||||
return
|
||||
end
|
||||
repository = @shixun.repository
|
||||
ActiveRecord::Base.transaction do
|
||||
begin
|
||||
|
|
@ -42,17 +89,17 @@ class ShixunsController < ApplicationController
|
|||
# 之所以增加user_id是为了方便统计查询性能
|
||||
challenges.each_with_index do |challenge, index|
|
||||
status = (index == 0 ? 0 : 3)
|
||||
Game.create!(:challenge_id => challenge.id, :myshixun_id => myshixun.id, :status => status, :user_id => myshixun.user_id)
|
||||
Game.create!(:challenge_id => challenge.id, :myshixun_id => myshixun.id, :status => status, :user_id => myshixun.user_id, :open_time => Time.now)
|
||||
end
|
||||
else
|
||||
raise("网络异常,请稍后重试")
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html{myshixun_game_url(myshixun.current_task, :myshixun_id => myshixun)}
|
||||
end
|
||||
redirect_to myshixun_game_path(myshixun.current_task, :myshixun_id => myshixun)
|
||||
rescue Exception => e
|
||||
flash[:error] = l(:notice_shixun_failed_exec)+ " : " + e.message
|
||||
flash[:error] = e.message
|
||||
logger.info("failed to exec shixun: current task id is #{e}")
|
||||
@g.delete_project(gshixun.id) if !gshixun.id.nil?
|
||||
redirect_to shixun_challenges_path(@shixun)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
|
@ -92,7 +139,6 @@ class ShixunsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
@ -186,13 +232,14 @@ class ShixunsController < ApplicationController
|
|||
jenkins_shixuns = Redmine::Configuration['jenkins_shixuns']
|
||||
if myshixun.save!
|
||||
MyshixunMember.create!(:myshixun_id => myshixun.id, :user_id => User.current.id, :role => 1)
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/api/createJob")
|
||||
pipeLine = "#{Base64.encode64(shixun.script)}"
|
||||
params = {jobName: "myshixun_#{myshixun.id}", pipeLine: pipeLine}
|
||||
res = uri_exec uri, params
|
||||
rep = Repository.new(:myshixun_id => myshixun.id, :identifier => gshixun.name,:project_id => -1, :shixun_id => -2)
|
||||
rep.type = "Repository::Gitlab"
|
||||
rep.save!
|
||||
rep_url = git_repository_url(myshixun, "Myshixun")
|
||||
uri = URI("#{jenkins_shixuns}/jenkins-exec/game/openGameInstance")
|
||||
pipeLine = "#{shixun.script}"
|
||||
params = {jobNameForInstance: "myshixun_#{myshixun.id}", gamePipelineScript: pipeLine, instanceGitURL:rep_url}
|
||||
res = uri_exec uri, params
|
||||
if (res && res['code'] != 0)
|
||||
raise("Job 创建失败")
|
||||
end
|
||||
|
|
@ -219,7 +266,7 @@ class ShixunsController < ApplicationController
|
|||
|
||||
# 实训管理员或者超级管理员
|
||||
def require_manager
|
||||
render_403 unless User.current.manager_of_shixun?(@shixun)
|
||||
render_403 unless (User.current.manager_of_shixun?(@shixun) || User.current.admin?)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -514,8 +514,19 @@ class WordsController < ApplicationController
|
|||
if params[:text].size>0 && @user
|
||||
# @user.add_jour(me, params[:text])
|
||||
#私信
|
||||
if params[:shixun_name]
|
||||
game_id = params[:game_id]
|
||||
message = params[:text]
|
||||
admin_id = User.where(:admin => 1).first.try(:id)
|
||||
user_ids = [admin_id, params[:shixun_author_id].to_i]
|
||||
user_ids.each do |user_id|
|
||||
JournalsForMessage.create(:jour_id => game_id, :jour_type => "Game", :user_id => user_id, :notes => message, :reply_id => 0, :status => true, :is_readed => false, :private => 1)
|
||||
end
|
||||
else
|
||||
message = "<span style='color:red;'>【未收到激活邮件的用户反馈,用户邮箱:"+me.mail+"】</span><br>"+params[:text]
|
||||
@user.journals_for_messages << JournalsForMessage.new(:user_id => me.id, :notes => message, :reply_id => 0, :status => true, :is_readed => false, :private => 1)
|
||||
end
|
||||
|
||||
else
|
||||
status = 0
|
||||
end
|
||||
|
|
@ -524,6 +535,7 @@ class WordsController < ApplicationController
|
|||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
# 定义实训相关方法
|
||||
def sum_final_score
|
||||
Game.find_by_sql("SELECT sum(final_score) as total_score FROM `games` where user_id=#{User.current.id}").first.try(:total_score)
|
||||
end
|
||||
|
||||
# myshixun 最高分
|
||||
def top_score shixun, position
|
||||
Game.find_by_sql("SELECT max(final_score) as top_score FROM `games` g, `challenges` c where g.challenge_id = c.id and c.position=#{position} and g.myshixun_id in (SELECT id FROM `myshixuns` ms where ms.shixun_id=#{shixun.id})").first.try(:top_score)
|
||||
|
|
@ -118,7 +122,7 @@ module ApplicationHelper
|
|||
time = "#{hour}小时 : #{min}分"
|
||||
end
|
||||
else
|
||||
time = "#{day}天 : #{hour}小时 : #{min}分"
|
||||
time = "#{day}天 #{hour}小时 #{min}分"
|
||||
end
|
||||
return time
|
||||
end
|
||||
|
|
@ -181,7 +185,23 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def uri_exec uri, params
|
||||
res = Net::HTTP.post_form(uri, params).body
|
||||
begin
|
||||
logger.error("@parmas is #{params}, url is #{uri}")
|
||||
res = Net::HTTP.post_form(uri, params).body
|
||||
res = JSON.parse(res)
|
||||
rescue => e
|
||||
logger.error("failed to create challenge by 'generatePipelineScriptForChallenge'! #{e}")
|
||||
raise("Jenkins服务器异常")
|
||||
end
|
||||
end
|
||||
|
||||
def uri_json_exec uri, params
|
||||
Net::HTTP.start(uri.hostname, uri.port) {|http|
|
||||
http.post(uri.path, params.to_json)
|
||||
}
|
||||
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
res = Net::HTTP.post(uri, params.to_json).body
|
||||
res = JSON.parse(res)
|
||||
end
|
||||
|
||||
|
|
@ -276,7 +296,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def allow_shixun_exec shixun
|
||||
(User.current.logged? && User.current.id != shixun.user_id && shixun.status == 1)
|
||||
(User.current.logged? && shixun.challenges.count > 0 && !shixun.repository.blank?)
|
||||
end
|
||||
|
||||
# 通过系统外部邮箱查找用户,如果用户不存在则用邮箱替换
|
||||
|
|
|
|||
|
|
@ -1,2 +1,14 @@
|
|||
# encoding: utf-8
|
||||
module ChallengesHelper
|
||||
|
||||
def difficulty_type difficulty
|
||||
case difficulty
|
||||
when 1
|
||||
"简单"
|
||||
when 2
|
||||
"中等"
|
||||
when 3
|
||||
"复杂"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class Game < ActiveRecord::Base
|
||||
# stauts 0: can exe 1:doing 2:successed 3:locked
|
||||
default_scope :order => 'created_at desc'
|
||||
attr_accessible :myshixun_id, :user_id, :status, :final_score, :challenge_id
|
||||
attr_accessible :myshixun_id, :user_id, :status, :final_score, :challenge_id, :open_time
|
||||
belongs_to :myshixun,:touch=> true
|
||||
belongs_to :user
|
||||
belongs_to :challenge
|
||||
|
|
@ -9,6 +9,14 @@ class Game < ActiveRecord::Base
|
|||
has_many :test_sets, :dependent => :destroy
|
||||
has_many :challenge_samples, :dependent => :destroy
|
||||
|
||||
def last_game
|
||||
challenge = self.challenge
|
||||
last_challenge_id = challenge.last_challenge
|
||||
game = Game.where(:myshixun_id => self.myshixun_id, :challenge_id => last_challenge_id).first
|
||||
render_404 if game.nil?
|
||||
return game
|
||||
end
|
||||
|
||||
def next_game
|
||||
challenge = self.challenge
|
||||
next_challenge_id = challenge.next_challenge
|
||||
|
|
|
|||
|
|
@ -322,6 +322,9 @@ class JournalsForMessage < ActiveRecord::Base
|
|||
receivers.each do |r|
|
||||
self.user_feedback_messages << UserFeedbackMessage.new(:user_id => r.id, :journals_for_message_id => self.id, :journals_for_message_type => "Principal", :viewed => false)
|
||||
end
|
||||
elsif self.jour_type == 'Game'
|
||||
# game不存在回复
|
||||
self.user_feedback_messages << UserFeedbackMessage.new(:user_id => self.user_id, :journals_for_message_id => self.id, :journals_for_message_type => "Game", :viewed => false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
class TestSet < ActiveRecord::Base
|
||||
attr_accessible :challenge_id, :input, :output, :game_id
|
||||
default_scope :order => 'id asc'
|
||||
attr_accessible :challenge_id, :input, :output, :game_id, :actual_output, :is_public, :result, :position
|
||||
belongs_to :challenge
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,20 +4,60 @@
|
|||
|
||||
<%= stylesheet_link_tag '/editormd/css/editormd','/editormd/css/editormd.min' %>
|
||||
<%= javascript_include_tag '/editormd/editormd.min.js','/editormd/examples/js/jquery.min.js' %>
|
||||
|
||||
<style>
|
||||
.CodeMirror-scroll{
|
||||
overflow: auto !important;
|
||||
margin-bottom: -30px;
|
||||
margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
editormd("challenge_ready_konewlege", {
|
||||
width : "80%",
|
||||
height : 300,
|
||||
syncScrolling : "single",
|
||||
//你的lib目录的路径,我这边用JSP做测试的
|
||||
path : "/editormd/lib/",
|
||||
//这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。
|
||||
saveHTMLToTextarea : true
|
||||
// 用于增加自定义工具栏的功能,可以直接插入HTML标签,不使用默认的元素创建图标
|
||||
|
||||
challenge_editormd = editormd("challenge_ready_konewlege", {
|
||||
width : "89.6%",
|
||||
height : 300,
|
||||
syncScrolling : "single",
|
||||
//你的lib目录的路径,我这边用JSP做测试的
|
||||
path : "/editormd/lib/",
|
||||
toolbarIcons : function() {
|
||||
// Or return editormd.toolbarModes[name]; // full, simple, mini
|
||||
// Using "||" set icons align right.
|
||||
return ["bold", "italic", "|", "h1", "h2", "h3", "|", "list-ul", "list-ol", "|", "image", "code", "code-block", "|", "watch", "clear"]
|
||||
},
|
||||
//这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。
|
||||
saveHTMLToTextarea : true,
|
||||
// 用于增加自定义工具栏的功能,可以直接插入HTML标签,不使用默认的元素创建图标
|
||||
dialogMaskOpacity : 0.6,
|
||||
placeholder: "请输入完成当前任务依赖的知识点或者其它相关信息",
|
||||
imageUpload : true,
|
||||
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
|
||||
imageUploadURL : "<%= upload_with_markdown_path %>" //url
|
||||
});
|
||||
taskpass_editormd = editormd("challenge_task_pass", {
|
||||
width : "89.6%",
|
||||
height : 300,
|
||||
syncScrolling : "single",
|
||||
//你的lib目录的路径,我这边用JSP做测试的
|
||||
path : "/editormd/lib/",
|
||||
toolbarIcons : function() {
|
||||
// Or return editormd.toolbarModes[name]; // full, simple, mini
|
||||
// Using "||" set icons align right.
|
||||
return ["bold", "italic", "|", "h1", "h2", "h3", "|", "list-ul", "list-ol", "|", "image", "code", "code-block", "|", "watch", "clear"]
|
||||
},
|
||||
//这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。
|
||||
saveHTMLToTextarea : true,
|
||||
// 用于增加自定义工具栏的功能,可以直接插入HTML标签,不使用默认的元素创建图标
|
||||
dialogMaskOpacity : 0.6,
|
||||
placeholder: "请输入完成当前任务依赖的知识点或者其它相关信息",
|
||||
imageUpload : true,
|
||||
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
|
||||
imageUploadURL : "<%= upload_with_markdown_path %>"//url
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -25,24 +65,16 @@
|
|||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="sample_inputs_label"></span>
|
||||
<input type="checkbox" name="test_set[lock][]" class="fl ml5 mr5 mt8" value=""><span class="fl mr5">锁定</span>
|
||||
<input type="hidden" name="test_set[hidden][]"/>
|
||||
<a href="javascript:void(0)" title="增加" class="sample_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<a href="javascript:void(0)" title="删除" class="sample_icon_remove"><i class="fa fa-times-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
</p>
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing mb10" name="sample[input][]" id="textarea_input_test" placeholder="样例输入" />
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing mb10" name="sample[output][]" id="textarea_output_test" placeholder="样例输出" />
|
||||
</li>
|
||||
</script>
|
||||
<script id="t:test-answer-list" type="text/html">
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="inputs_label"></span>
|
||||
<a href="javascript:void(0);" title="增加" class="test_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<a href="javascript:void(0);" title="删除" class="test_icon_remove"><i class="fa fa-times-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
</p>
|
||||
<!-- <input type="text" class="panel-form-width-670 panel-form-height-30 mb10" name="program[input][]" placeholder="测试输入" />-->
|
||||
<textarea class="panel-form-width-100 panel-box-sizing" id="textarea_output_test" name="program[output][]" placeholder="输入测试集内容" ></textarea>
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_input_test" name="test_set[input][]" placeholder="输入" ></textarea>
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_output_test" name="test_set[output][]" placeholder="希望输出" ></textarea>
|
||||
</li>
|
||||
</script>
|
||||
|
||||
<div id="shixun_form">
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>名称:</label>
|
||||
|
|
@ -57,88 +89,70 @@
|
|||
<label class="panel-form-label fl">预备知识:</label>
|
||||
<div id="challenge_ready_konewlege">
|
||||
<%#= f.text_area :ready_knowledge, :placeholder => "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务" %>
|
||||
<textarea name="challenge[ready_knowledge]" placeholder = "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务"></textarea>
|
||||
<textarea name="challenge[ready_knowledge]" placeholder = "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务"><%= @challenge.ready_knowledge %></textarea>
|
||||
</div>
|
||||
<%#= f.kindeditor :ready_knowledge, :placeholder => "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务", :width=>'87.5%', :height => 159, :resizeType => 0, :no_label => true %>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>过关任务:</label>
|
||||
<%#= f.kindeditor :task_pass, :editor_id => "challenge_task_pass", :width=>'87.5%', :height => 159, :resizeType => 0, :no_label => true %>
|
||||
<%= f.text_area :task_pass, :class => "panel-form-width-690 panel-box-sizing", :no_label => true %>
|
||||
<%#= f.text_area :task_pass, :class => "panel-form-width-690 panel-box-sizing", :no_label => true %>
|
||||
<div id="challenge_task_pass">
|
||||
<%#= f.text_area :ready_knowledge, :placeholder => "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务" %>
|
||||
<textarea name="challenge[task_pass]" placeholder = "请输入完成当前任务依赖的知识点或者其它相关信息,指导学员完成任务"><%= @challenge.task_pass %></textarea>
|
||||
</div>
|
||||
<span style="display: none" class="c_red ml90" id="new_shixun_pass">过关任务不能为空</span>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<% if params[:action] == "edit" && @challenge.challenge_samples.count > 0 %>
|
||||
<label class="panel-form-label fl">样例设置:</label>
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>评测方式:</label>
|
||||
<%= select_tag :evaluation_way, options_for_select([["输出输出", 1]]), :name => 'challenge[evaluation_way]', :class => " fl", :style => "height: 40px; width:200px;" %>
|
||||
<span class="fl ml15 mt8">请选择自动检测学员是否完成本任务的评测标准</span>
|
||||
</li>
|
||||
|
||||
<li class="clearfix">
|
||||
<% if params[:action] == "edit" && @challenge.test_sets.count > 0 %>
|
||||
<label class="panel-form-label fl">测试集设置:</label>
|
||||
<ul class="fl task-bg-grey panel-box-sizing" >
|
||||
<% @challenge.challenge_samples.each_with_index do |sample, index| %>
|
||||
<% @challenge.test_sets.each_with_index do |test_set, index| %>
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="sample_inputs_label">样例<%= index + 1 %></span>
|
||||
<span class="color-green fb fl" name="sample_inputs_label">组<%= index + 1 %></span>
|
||||
<% if test_set.is_public? %>
|
||||
<input type="checkbox" name="test_set[lock][]" class="fl ml5 mr5 mt8"><span class="fl mr5">锁定</span>
|
||||
<% else %>
|
||||
<input type="checkbox" name="test_set[lock][]" class="fl ml5 mr5 mt8" checked="checked"><span class="fl mr5">锁定</span>
|
||||
<% end %>
|
||||
<input type="hidden" name="test_set[hidden][]"/>
|
||||
<% if index == 0 %>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:输入样例供学员参考。</span>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:选中“锁定”,则对学员隐藏该条测试集,但在“提交评测”时也将被自动检测。</span>
|
||||
<% end %>
|
||||
<a href="javascript:void(0)" title="增加" class="sample_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<% if index > 0 %>
|
||||
<a href="javascript:void(0)" title="删除" class="sample_icon_remove"><i class="fa fa-times-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<% end %>
|
||||
</p>
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing mb10" name="sample[input][]" id="textarea_sample_input_test" placeholder="样例输入" value="<%= sample.input %>" />
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing" name="sample[output][]" id="textarea_sample_output_test" placeholder="样例输出" value="<%= sample.output %>" />
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_input_test" name="test_set[input][]" placeholder="输入"><%= test_set.input %></textarea>
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_output_test" name="test_set[output][]" placeholder="希望输出"><%= test_set.output %></textarea>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<label class="panel-form-label fl">样例设置:</label>
|
||||
<label class="panel-form-label fl">测试集设置:</label>
|
||||
<ul class="fl task-bg-grey panel-box-sizing">
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="sample_inputs_label">样例1</span>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:输入样例供学员参考。</span>
|
||||
<span class="color-green fb fl" name="sample_inputs_label">组1</span>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:选中“锁定”,则对学员隐藏该条测试集,但在“提交评测”时也将被自动检测。</span>
|
||||
<input type="checkbox" name="test_set[lock][]" class="fl ml5 mr5 mt8" value=""><span class="fl mr5">锁定</span>
|
||||
<input type="hidden" name="test_set[hidden][]"/>
|
||||
<a href="javascript:void(0)" title="增加" class="sample_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
</p>
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing mb10" name="sample[input][]" id="textarea_sample_input_test" placeholder="样例输入" />
|
||||
<input type="text" class="panel-form-width-100 panel-box-sizing mb10" name="sample[output][]" id="textarea_sample_output_test" placeholder="样例输出" />
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_input_test" name="test_set[input][]" placeholder="输入" ></textarea>
|
||||
<textarea class="panel-form-width-40 panel-box-sizing" id="textarea_sample_output_test" name="test_set[output][]" placeholder="希望输出" ></textarea>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<% if params[:action] == "edit" && @challenge.test_sets.count > 0 %>
|
||||
<label class="panel-form-label fl">测试集设置:</label>
|
||||
<ul class="fl task-bg-grey panel-box-sizing" >
|
||||
<% @challenge.test_sets.each_with_index do |test, index| %>
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="inputs_label">测试集<%= index + 1 %></span>
|
||||
<% if index == 0%>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:在学员"提交评测"时进行自动检测。</span>
|
||||
<% end %>
|
||||
<a href="javascript:void(0);" title="增加" class="test_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<% if index > 0 %>
|
||||
<a href="javascript:void(0);" title="删除" class="test_icon_remove"><i class="fa fa-times-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
<% end %>
|
||||
</p>
|
||||
<!--<input type="text" class="panel-form-width-670 panel-form-height-30 mb10" name="program[input][]" id="textarea_input_test" value="<%#= test.input %>"/>-->
|
||||
<textarea class="panel-form-width-100 panel-box-sizing" id="textarea_output_test" name="program[output][]" placeholder="输入测试集内容" ><%= test.output %></textarea>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<label class="panel-form-label fl">测试集设置:</label>
|
||||
<ul class="fl task-bg-grey panel-box-sizing">
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb fl" name="inputs_label">测试集1</span>
|
||||
<span class="color-grey fr" style="font-size:12px;">温馨提示:在学员"提交评测"时进行自动检测。</span>
|
||||
<a href="javascript:void(0);" title="增加" class="test_icon_add"><i class="fa fa-plus-circle color-grey font-16 ml10 fl mt7"></i></a>
|
||||
</p>
|
||||
<!-- <input type="text" class="panel-form-width-670 panel-form-height-30 mb10" name="program[input][]" id="textarea_input_test" placeholder="测试输入" />-->
|
||||
<textarea class="panel-form-width-100 panel-box-sizing " id="textarea_output_test" name="program[output][]" placeholder="输入用例内容" ></textarea>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class=" panel-form-label fl"> 知识/技能点:</label>
|
||||
<div class="fl task-bd-grey">
|
||||
|
|
@ -161,19 +175,28 @@
|
|||
<label class=" panel-form-label fl"> 参考答案:</label>
|
||||
<%= f.text_area :answer, :class => "panel-form-width-690 panel-form-height-150 fl task-textarea-pd", :no_label => true, :style => "line-height:1.9;" %>
|
||||
</li>
|
||||
|
||||
<li class="clearfix">
|
||||
<label class=" panel-form-label fl"><span class="c_red mr5">*</span>分值设定:</label>
|
||||
<%= f.text_field :score, :class => "panel-form-height-30 fl", :no_label => true, :style => "padding:5px;", :placeholder => "请输入分值(只能是数字)" %>
|
||||
<span class="fl ml5" style="line-height: 40px;">分</span>
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>难易度:</label>
|
||||
<span class="mt10 fl"><%= radio_button_tag("challenge[difficulty]", 1, checked = (@challenge.difficulty ==1 ? true : false)) %></span><span class="ml5 fl mt8 mr15">简单</span>
|
||||
<span class="mt10 fl"><%= radio_button_tag("challenge[difficulty]", 2, checked = (@challenge.difficulty ==2 ? true : false)) %></span><span class="ml5 fl mt8 mr15">中等</span>
|
||||
<span class="mt10 fl"><%= radio_button_tag("challenge[difficulty]", 3, checked = (@challenge.difficulty ==3 ? true : false)) %></span><span class="ml5 fl mt8 mr15">困难 </span>
|
||||
<!-- <span class="fl color-orange ml30 mt8"><i class="fa fa-exclamation-circle mr5"></i>请选择难易度并设定通过本关后,学员得到的经验值</span>-->
|
||||
</li>
|
||||
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>奖励经验值:</label>
|
||||
<%#= f.text_field :score, :class => "panel-form-height-30 fl", :no_label => true, :style => "padding:5px;", :placeholder => "请输入分值(只能是数字)" %>
|
||||
<%= select_tag :challenge_score, options_for_select([100, 200]), :name => 'challenge[score]', :class => " fl", :style => "height: 40px; width:170px;" %>
|
||||
<div class="clear"></div>
|
||||
<span style="display: none" class="c_red ml90" id="new_shixun_score">分值设定不能为空</span>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<% if params[:action] == "edit" %>
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-green fr " onclick="challenge_update()">保存</a>
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-green fr" onclick="challenge_update()">保存</a>
|
||||
<a href="<%= shixun_challenge_path(@challenge, :shixun_id => @shixun) %>" class="task-btn fr mr10">取消</a>
|
||||
<% else %>
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-green fr " onclick="challenge_create()">保存</a>
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-green fr" onclick="challenge_create()">保存</a>
|
||||
<a href="<%= shixun_challenges_path(@shixun) %>" class="task-btn fr mr10">取消</a>
|
||||
<% end %>
|
||||
|
||||
|
|
@ -218,7 +241,7 @@
|
|||
var outputs = document.getElementsByName("sample[output][]");
|
||||
var inputs_labels = document.getElementsByName("sample_inputs_label");
|
||||
for (var j = 0; j < inputs_labels.length; j++) {
|
||||
$(inputs_labels[j]).html("样例" + (j + 1));
|
||||
$(inputs_labels[j]).html("组" + (j + 1));
|
||||
}
|
||||
if (inputs.length == outputs.length) {
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
|
|
@ -232,14 +255,55 @@
|
|||
$(this).parent().parent('.clearfix').remove();
|
||||
var inputs_labels = document.getElementsByName("sample_inputs_label");
|
||||
for (var j = 0; j < inputs_labels.length; j++) {
|
||||
$(inputs_labels[j]).html("样例" + (j + 1));
|
||||
$(inputs_labels[j]).html("组" + (j + 1));
|
||||
}
|
||||
});
|
||||
|
||||
// 根据难易程度设置不同的奖励经验值(适用新建与编辑模式)
|
||||
var list= $('input:radio[name="challenge[difficulty]"]:checked').val(); // 获取select的索引值
|
||||
if(list == 1){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 100; i <= 200; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
}else if(list == 2){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 300; i <= 600; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
}else if(list == 3){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 700; i <= 1000; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
}
|
||||
$("#challenge_score").find("option[value='<%= @challenge.score %>']").attr("selected",true); // 设置option默认值
|
||||
$("#challenge_difficulty_1").click(function(){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 100; i <= 200; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
});
|
||||
$("#challenge_difficulty_2").click(function(){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 300; i <= 600; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
});
|
||||
$("#challenge_difficulty_3").click(function(){
|
||||
$("#challenge_score").empty();
|
||||
for(var i= 700; i <= 1000; i += 100){
|
||||
$("#challenge_score").append("<option value="+ i + ">"+ i +"</option>");
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
// 设置textarea的宽度到140以后出现滚动条
|
||||
var text1 = document.getElementById("textarea_sample_input_test");
|
||||
var text2 = document.getElementById("textarea_sample_output_test");
|
||||
// autoTextarea(text1, 0, 140);
|
||||
// autoTextarea(text2, 0, 140);
|
||||
autoTextarea(text1, 0, 140);
|
||||
autoTextarea(text2, 0, 140);
|
||||
|
||||
var text3 = document.getElementById("textarea_input_test");
|
||||
var text4 = document.getElementById("textarea_output_test");
|
||||
// autoTextarea2(text3, text4, 0, 140);
|
||||
|
|
@ -256,10 +320,25 @@
|
|||
$("#challenge_subject").keydown(function(){
|
||||
$("#new_shixun_pass").hide();
|
||||
});
|
||||
|
||||
// 判断测试集是否锁定, 0未锁定,1锁定
|
||||
function test_set_whether_lock(){
|
||||
var lock = document.getElementsByName("test_set[lock][]");
|
||||
var hid = document.getElementsByName("test_set[hidden][]");
|
||||
for(var o = 0; o < lock.length; o++){
|
||||
if($(lock[o]).prop("checked")){
|
||||
$(hid[o]).val(1);
|
||||
}else{
|
||||
$(hid[o]).val(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function challenge_create(){
|
||||
test_set_whether_lock();
|
||||
if($('#challenge_subject').val().trim() == ""){
|
||||
$("#new_shixun_name").show();
|
||||
}else if($("#challenge_task_pass").val().trim() == ""){
|
||||
}else if($("#challenge_task_pass textarea").val().trim() == ""){
|
||||
$("#new_shixun_pass").show();
|
||||
}else if($("#challenge_score").val().trim()==""){
|
||||
$("#new_shixun_score").show();
|
||||
|
|
@ -269,9 +348,10 @@
|
|||
}
|
||||
|
||||
function challenge_update(){
|
||||
test_set_whether_lock();
|
||||
if($('#challenge_subject').val().trim() == ""){
|
||||
$("#new_shixun_name").show();
|
||||
}else if($("#challenge_task_pass").val().trim() == ""){
|
||||
}else if($("#challenge_task_pass textarea").val().trim() == ""){
|
||||
$("#new_shixun_pass").show();
|
||||
}else if($("#challenge_score").val().trim()==""){
|
||||
$("#new_shixun_score").show();
|
||||
|
|
@ -294,7 +374,7 @@
|
|||
$('#challenge_answer').val(cMirror.getValue());
|
||||
});
|
||||
|
||||
challenge_task_pass_editor = KindEditor.create('#challenge_task_pass',
|
||||
/* challenge_task_pass_editor = KindEditor.create('#challenge_task_pass',
|
||||
{"width":"89.7%",
|
||||
"resizeType":0,
|
||||
"no_label":true,
|
||||
|
|
@ -317,7 +397,7 @@
|
|||
"fileManagerJson":"/kindeditor/filemanager"
|
||||
});
|
||||
|
||||
|
||||
*/
|
||||
function add_tag(){
|
||||
var num = $(".task-bd-grey").children('div').length;
|
||||
var val = $(".task-tag-input").val().trim();
|
||||
|
|
@ -336,5 +416,4 @@
|
|||
var obj = thisObj.parentNode.id;
|
||||
$("#"+obj).remove();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@
|
|||
flowChart : true, // 默认不解析
|
||||
sequenceDiagram : true // 默认不解析
|
||||
});
|
||||
var taskPassMD = editormd.markdownToHTML("challenge_task_pass", {
|
||||
htmlDecode : "style,script,iframe", // you can filter tags decode
|
||||
emoji : true,
|
||||
taskList : true,
|
||||
tex : true, // 默认不解析
|
||||
flowChart : true, // 默认不解析
|
||||
sequenceDiagram : true // 默认不解析
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
|
|
@ -48,7 +56,7 @@
|
|||
</li>
|
||||
<li class="clearfix challenge_bottom">
|
||||
<label class="panel-form-label fl">预备知识:</label>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690" id="wordsView">
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690" id="wordsView" style="width: 90%">
|
||||
<!--id 命名规则:challenge_ready_view-->
|
||||
<textarea style="display:none;"><%= @challenge.ready_knowledge %></textarea>
|
||||
<%#= @challenge.ready_knowledge.blank? ? "无" : (h @challenge.ready_knowledge.html_safe) %>
|
||||
|
|
@ -56,27 +64,15 @@
|
|||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>过关任务:</label>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690" id="challenge_task_pass">
|
||||
<%= h @challenge.task_pass.html_safe %>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690" id="challenge_task_pass" style="width: 90%">
|
||||
<textarea style="display:none;"><%= @challenge.task_pass %></textarea>
|
||||
</div>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl">样例设置:</label>
|
||||
<ul class="fl task-bg-grey panel-box-sizing panel-form-width-690">
|
||||
<% if @challenge_samples.count > 0 %>
|
||||
<% @challenge_samples.each_with_index do |sample, index| %>
|
||||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb">样例<%= index + 1 %></span>
|
||||
</p>
|
||||
<div class="clearfix"><span class="fl fb">样例输入:</span><p class="fl"><%= sample.input %></p></div>
|
||||
<div class="clearfix"><span class="fl fb">样例输出:</span><p class="fl"><%= sample.output %></p></div>
|
||||
</li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
无
|
||||
<% end %>
|
||||
</ul>
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>评测方式:</label>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690">
|
||||
输入输出
|
||||
</div>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl">测试集设置:</label>
|
||||
|
|
@ -86,8 +82,14 @@
|
|||
<li class="clearfix">
|
||||
<p class="clearfix">
|
||||
<span class="color-green fb">测试集<%= index + 1 %></span>
|
||||
<% if test.is_public? %>
|
||||
<i class="fa fa-unlock-alt font-grey ml5" ></i>
|
||||
<% else %>
|
||||
<i class="fa fa-lock font-grey ml5" ></i>
|
||||
<% end %>
|
||||
</p>
|
||||
<div class="clearfix"><p class="fl"><%= test.output %></p></div>
|
||||
<div class="clearfix"><span class="fl fb">输入:</span><p class="fl"><%= test.input %></p></div>
|
||||
<div class="clearfix"><span class="fl fb">输出:</span><p class="fl"><%= test.output %></p></div>
|
||||
</li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
|
@ -113,7 +115,13 @@
|
|||
<div id="hidden_tpm_answer_show" class="undis"><%= @challenge.answer.blank? ? "无" : (@challenge.answer) %></div>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>分值设定:</label>
|
||||
<label class="panel-form-label fl">难易度:</label>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690">
|
||||
简单
|
||||
</div>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl"><span class="c_red mr5">*</span>奖励经验值:</label>
|
||||
<div class="fl task-bg-grey panel-box-sizing panel-form-width-690">
|
||||
<%= @challenge.score %>分
|
||||
</div>
|
||||
|
|
@ -123,7 +131,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('#challenge_task_pass').find('img').css("max-width", "680px");
|
||||
var tpmMirror = $("#tpm_answer_show")[0];
|
||||
var TpmCodeMirror = CodeMirror(tpmMirror, {
|
||||
value: $("#hidden_tpm_answer_show").text(),
|
||||
|
|
|
|||
|
|
@ -1,27 +1,124 @@
|
|||
<div class="col-width fl content-submit ml15 mt15">
|
||||
<div class="panel-header ">
|
||||
<h3 >操作</h3>
|
||||
</div>
|
||||
<div class="content-submitbox">
|
||||
<a href="#" class="task-btn mb10">保存修改</a>
|
||||
<% if @game.status == 0 %>
|
||||
<%= link_to "提交评测", { :controller => 'games',
|
||||
:action => "game_build",
|
||||
:id => @game,
|
||||
:myshixun_id => @myshixun},
|
||||
:class => "task-btn task-btn-green",
|
||||
:onclick => "training_task_submmit();",
|
||||
:remote => true %>
|
||||
<% elsif @game.status == 2 %>
|
||||
<a class="task-btn mb10">评测中..</a>
|
||||
<% elsif @game.status == 1 %>
|
||||
<%= link_to "下 一 步 ", {:controller => 'games',
|
||||
:action => "next_step",
|
||||
:id => @game,
|
||||
:myshixun_id => @myshixun},
|
||||
:class => "task-btn task-btn-green",
|
||||
:onclick => "training_task_submmit();",
|
||||
:remote => true %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt10 -flex c_grey ml15">耗时:<%= game_spend_time(@game.open_time, @game.updated_at) %></span>
|
||||
<div id="code_estimate">
|
||||
<% if @game.status == 0 %>
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-blue mr15 mt8" id="code_test" onclick="training_task_submmit();">评测</a>
|
||||
<% elsif @game.status == 1 %>
|
||||
<a class="task-btn mb10 mt6" id="code_testing">评测中..</a>
|
||||
<% elsif @game.status == 2 %>
|
||||
<%= link_to "下 一 步 ", {:controller => 'games',:action => "next_step", :id => @game,
|
||||
:myshixun_id => @myshixun},
|
||||
:class => "task-btn task-btn-blue mr15 mt8",
|
||||
:onclick => "training_task_submmit();",
|
||||
:remote => true %>
|
||||
<% end %>
|
||||
</div>
|
||||
<script>
|
||||
function training_task_submmit(){
|
||||
var i = 0;
|
||||
$("#ajax-indicator").show();
|
||||
$("#code_estimate").replaceWith("<a class='task-btn mb10 mt6 mr15' id='code_testing'>评测中..</a>");
|
||||
$.ajax({
|
||||
url: '<%= game_build_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
success: function (){
|
||||
//移除载入
|
||||
var temp = $("#ajax-indicator").children('span')[0];
|
||||
$("#ajax-indicator").children('span')[0].remove();
|
||||
$("#ajax-indicator").css('border', 0);
|
||||
$("#ajax-indicator").css('padding', 0);
|
||||
var intId = setInterval(function(){
|
||||
var ajaxTimeoutTest = $.ajax({
|
||||
url: '<%= game_status_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
timeout : 50000,
|
||||
data:'test',
|
||||
success:function(data){
|
||||
//如果查到了,就退出
|
||||
// i变量用来统计setInterval执行了多少次,便于统计访问时间
|
||||
i = i + 1;
|
||||
if(data.status == 2 || data.status == 0){
|
||||
clearInterval(intId);
|
||||
// 显示载入
|
||||
$("#ajax-indicator").html(temp);
|
||||
$("#ajax-indicator").css('border', "1px solid #bbb");
|
||||
$("#ajax-indicator").css('padding', "0.6em");
|
||||
// test_sets 公开测,hide_test_sets 隐藏测试集
|
||||
var html = bt('t:exec_results_1',{
|
||||
test_sets: [
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input7",
|
||||
actual_output: "actual_output7",
|
||||
output: "output7"
|
||||
},
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input8",
|
||||
actual_output: "actual_output8",
|
||||
output: "output8"
|
||||
},
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input9",
|
||||
actual_output: "actual_output9",
|
||||
output: "output9"
|
||||
}
|
||||
],
|
||||
hide_test_sets: [
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input10",
|
||||
actual_output: "actual_output10",
|
||||
output: "output10"
|
||||
},
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input11",
|
||||
actual_output: "actual_output11",
|
||||
output: "output11"
|
||||
},
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input12",
|
||||
actual_output: "actual_output12",
|
||||
output: "output12"
|
||||
}
|
||||
]
|
||||
});
|
||||
$("#game_test_set_results").html(html);
|
||||
if(data.status == 2){
|
||||
if( data.had_done == 0 ) {
|
||||
var htmlvalue = "<%= j (render :partial => 'games/pass_game_show', :locals => { :game=> @game, :myshixun => @myshixun, :had_done => 0}) %>";
|
||||
}else{
|
||||
var htmlvalue = "<%= j (render :partial => 'games/pass_game_show', :locals => { :game=> @game, :myshixun => @myshixun, :had_done => 1}) %>";
|
||||
}
|
||||
// 传递具体屏幕的宽高,实现不同浏览器与不同分辨率都能撑满屏幕
|
||||
pop_box_new2(htmlvalue, window.innerWidth, window.innerHeight);
|
||||
}
|
||||
}
|
||||
// 访问30次,即60s后后台还未改变状态即执行
|
||||
if(i == 30){
|
||||
$.ajax({
|
||||
url: '<%= change_status_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
success:function(data){
|
||||
clearInterval(intId);
|
||||
var html = "<a href='javascript:void(0)' class='task-btn task-btn-blue mr15 mt8' id='code_test' onclick='training_task_submmit();'>评测</a>";
|
||||
$("#code_testing").replaceWith(html);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
complete: function(XMLHttpRequest,status){if(status=='timeout'){
|
||||
ajaxTimeoutTest.abort();
|
||||
clearInterval(intId);
|
||||
}}
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -4,23 +4,26 @@
|
|||
|
||||
<form action="/myshixuns/<%= @myshixun.id %>/games/<%= @game.id %>/file_update?path=<%= @path %>" method="post" id="file_update_id" data-remote="true">
|
||||
<li class="clearfix" xmlns="http://www.w3.org/1999/html" id="edit_code_contents">
|
||||
<textarea class = "" id="challenge-answer-edit-small" name="content" style="height: 98%;width: 98%;"><%= Redmine::CodesetUtil.replace_invalid_utf8(@content) %></textarea>
|
||||
<script>
|
||||
var text = document.getElementById("challenge-answer-edit-small");
|
||||
</script>
|
||||
<textarea class = "" id="challenge-answer-edit-small" name="content"><%= Redmine::CodesetUtil.replace_invalid_utf8(@content) %></textarea>
|
||||
</li>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
// 自动完成
|
||||
/* CodeMirror.commands.autocomplete = function(cm) {
|
||||
cm.showHint({hint: CodeMirror.hint.anyword});
|
||||
};*/
|
||||
var editor_CodeMirror = CodeMirror.fromTextArea(document.getElementById("challenge-answer-edit-small"), {
|
||||
mode: {name: 'text/x-ruby',
|
||||
mode: {name: "text/x-java",
|
||||
// version: 2,
|
||||
singleLineStringErrors: false},
|
||||
lineNumbers: true,
|
||||
theme: "ambiance",
|
||||
// extraKeys: {"Ctrl-Q": "autocomplete"}, // 快捷键
|
||||
indentUnit: 4, //代码缩进为一个tab的距离
|
||||
matchBrackets: true,
|
||||
autoRefresh: true,
|
||||
smartIndent: true //智能换行
|
||||
smartIndent: true//智能换行
|
||||
});
|
||||
editor_CodeMirror.on('change',function(cMirror){
|
||||
// get value right from instance
|
||||
|
|
@ -35,7 +38,7 @@
|
|||
// $("#challenge-answer-edit-small" ).nextAll(".CodeMirror").css("line-height", 1.2);
|
||||
$(document).ready(function(){
|
||||
// 设置codemirror的高度/宽度自适应
|
||||
editor_CodeMirror.setSize("auto",residue_h+50+"px");
|
||||
editor_CodeMirror.setSize("auto","auto");
|
||||
editor_CodeMirror.refresh();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<% if @test_set_error_position.nil? %>
|
||||
<p class="color-light-green mb10"><i class="fa fa-check-circle font-16 " ></i><span class="ml5 mr5"><%= @test_sets_count %>/<%= @test_sets_count %></span> 全部通过</p>
|
||||
<% else %>
|
||||
<p class="color-orange mb10"><i class="fa fa-exclamation-circle font-16" ></i><span class="ml5 mr5"><%= @test_set_error_position %>/<%= @test_sets_count %></span> 编译错误</p>
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
<%= content_for(:header_tags) do %>
|
||||
<%= javascript_include_tag 'baiduTemplate', 'jquery.datetimepicker.js' %>
|
||||
<% end %>
|
||||
<ul id="blacktab_nav">
|
||||
<li id="blacktab_nav_1" class="blacktab_hover " onclick="HoverLi(1);">
|
||||
<a href="javascript:void(0);" class="tab_type">测试结果</a>
|
||||
</li>
|
||||
<li id="blacktab_nav_2" onclick="HoverLi(2);">
|
||||
<a href="javascript:void(0);" class="tab_type" >评测信息</a>
|
||||
</li>
|
||||
<a href="javascript:void(0);" onclick="valuation_extend_and_zoom();" id="valuation_extend_and_zoom"><i class="fa fa-expand font-16 color-grey fr mt10 mr15" ></i></a>
|
||||
<div class="cl"></div>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
<div id="game_test_set_results" class="-flex -relative blacktab-inner"></div>
|
||||
<script id="t:exec_results_1" type="text/html">
|
||||
<div id="blacktab_con_1" class="" >
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll task-padding16">
|
||||
<! for(var i = 0; i < test_sets.length; i++ ) { !>
|
||||
<div class="-task-ces-box mb10 clearfix">
|
||||
<div class="-task-ces-top" onclick="toggle_test_case('<!= test_sets[i].is_public !>', '<!= i !>')" style="cursor:pointer">
|
||||
<i class="fa fa-caret-down" ></i>
|
||||
<span>测试集 <!= i + 1 !></span>
|
||||
<! if( test_sets[i].result == 0 ) { !>
|
||||
<i class="fa fa-exclamation-circle color-orange fr mt8 ml5" ></i>
|
||||
<! } !>
|
||||
<! if(test_sets[i].is_public == 0) { !>
|
||||
<i class="fa fa-lock fr mt8" ></i>
|
||||
<! } !>
|
||||
</div>
|
||||
<! if(test_sets[i].is_public == 1) { !>
|
||||
<div class="-task-ces-info <!= i == 0? '' : 'undis' !>" id="test_case_<!= i !>">
|
||||
<ul>
|
||||
<li><span class="-task-ces-info-left color-blue">测试输入:</span><span><!= test_sets[i].input !></span></li>
|
||||
<li><span class="-task-ces-info-left color-blue">预期输出:</span><span><!= test_sets[i].output !></span></li>
|
||||
<li><span class="-task-ces-info-left color-blue">实际输出:</span><span><!= test_sets[i].actual_output !></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<! } !>
|
||||
</div>
|
||||
<! } !>
|
||||
<! for(var i = 0; i < hide_test_sets.length; i++ ) { !>
|
||||
<div class="-task-ces-box mb10 clearfix">
|
||||
<div class="-task-ces-top">
|
||||
<i class="fa fa-caret-down" ></i>
|
||||
<span>测试集 <!= i + test_sets.length !></span>
|
||||
<! if( hide_test_sets[i].result == 0 ) { !>
|
||||
<i class="fa fa-exclamation-circle color-orange fr mt8 ml5" ></i>
|
||||
<! } !>
|
||||
<! if(hide_test_sets[i].is_public == 0) { !>
|
||||
<i class="fa fa-lock fr mt8" ></i>
|
||||
<! } !>
|
||||
</div>
|
||||
</div>
|
||||
<! } !>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="blacktab_con_2" class="undis " >
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll task-padding16 ">
|
||||
<p class="color-light-green mb10"><i class="fa fa-check-circle font-16 " ></i><span class="ml5 mr5">11/11</span> 全部通过</p>
|
||||
<div class="-task-ces-box mb10 clearfix">
|
||||
<div class="-task-ces-info">
|
||||
<ul>
|
||||
<li><span class="-task-ces-info-left ">样例测试:</span><span class="color-light-green">9/9</span></li>
|
||||
<li><span class="-task-ces-info-left">隐藏测试:</span><span class="color-light-green">9/9</span></li>
|
||||
<li><span class="-task-ces-info-left">经验值:</span><span class="color-light-green">+ 300</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var bt = baidu.template;
|
||||
bt.LEFT_DELIMITER = '<!';
|
||||
bt.RIGHT_DELIMITER = '!>';
|
||||
$(function(){
|
||||
var html = bt('t:exec_results_1',
|
||||
{
|
||||
test_sets: [
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input1",
|
||||
actual_output: "actual_output1",
|
||||
output: "output1"
|
||||
},
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input2",
|
||||
actual_output: "actual_output2",
|
||||
output: "output2"
|
||||
},
|
||||
{
|
||||
is_public: 1,
|
||||
result: 1,
|
||||
input: "input3",
|
||||
actual_output: "actual_output3",
|
||||
output: "output3"
|
||||
}
|
||||
],
|
||||
hide_test_sets: [
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input4",
|
||||
actual_output: "actual_output4",
|
||||
output: "output4"
|
||||
},
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input5",
|
||||
actual_output: "actual_output5",
|
||||
output: "output5"
|
||||
},
|
||||
{
|
||||
is_public: 0,
|
||||
result: 0,
|
||||
input: "input6",
|
||||
actual_output: "actual_output6",
|
||||
output: "output6"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
);
|
||||
$("#game_test_set_results").html(html);
|
||||
});
|
||||
// 公开的测试集允许展开与隐藏
|
||||
function toggle_test_case(t_case, id){
|
||||
if(t_case){
|
||||
$("#test_case_"+id).toggle();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<% if false %>
|
||||
<div id="code_results" class="panel-footer CELL COLS mt15 ml15">
|
||||
</div>
|
||||
<script id="t:exec_results" type="text/html">
|
||||
<div class="col-width content-history CELL mb15" >
|
||||
<div class="panel-header clearfix">
|
||||
<h3 class="fl">测评历史</h3>
|
||||
<div class="fr mt5">
|
||||
<a href="javascript:void(0)" onclick="extend_window()"><i class="fa fa-expand font-14 fl color-grey"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-history-inner">
|
||||
<!--<! if (results.length != 0) { !>-->
|
||||
<! for(var i=0;i< results.length; i++) { !>
|
||||
<! if(results[i][0] == 0){ !>
|
||||
<div class="clearfix history-success mb10">
|
||||
<span class="icon-success fl mr5"><!= results.length - i !></span>
|
||||
<p class="fl">恭喜,您已经完成了本任务!</p>
|
||||
<a href="/myshixuns/<%= @myshixun.id %>/games/<%= @game.id %>/outputs_show?game_output_id=<!= results[i][1] !>" class="fr mr10" data-remote="true">详情</a>
|
||||
</div>
|
||||
<! }else{ !>
|
||||
<div class="clearfix history-fail mb10">
|
||||
<span class="icon-fail fl mr5"><!= results.length - i !></span>
|
||||
<p class="fl">错误结果!</p>
|
||||
<a href="/myshixuns/<%= @myshixun.id %>/games/<%= @game.id %>/outputs_show?game_output_id=<!= results[i][1] !>" class="fr mr10" data-remote="true">详情</a>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
<div class="undis" style="background: #f5f9fc;padding: 10px;margin-top:-10px;margin-bottom: 10px" id="game_exception_results_<!= results[i][1] !>"></div>
|
||||
<! } !>
|
||||
<! } !>
|
||||
<!--<! } !>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-width content-output ml15 CELL mb15">
|
||||
<div class="panel-header clearfix">
|
||||
<h3>测试输出</h3>
|
||||
</div>
|
||||
<div class="content-history-inner" id="game_test_output"></div>
|
||||
<div class="undis" id="game_test_output1">
|
||||
<%= h @latest_output %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-width content-submit ml15 CELL mb15">
|
||||
<div class="panel-header">
|
||||
<h3 >操作</h3>
|
||||
</div>
|
||||
<div class="content-submitbox">
|
||||
<div id="game_rep_modify">
|
||||
</div>
|
||||
<! if(status == 0){ !>
|
||||
<div id="game_commit">
|
||||
<a href="javascript:void(0)" class="task-btn task-btn-green" onclick="training_task_submmit();">提交评测</a>
|
||||
</div>
|
||||
<! }else if(status == 1){ !>
|
||||
<a class="task-btn mb10">评测中..</a>
|
||||
<! }else if(status == 2 && had_done != 1){ !>
|
||||
<%= link_to "下 一 关", {:controller => 'games', :action => "next_step", :id => @game, :myshixun_id => @myshixun}, :class => "task-btn task-btn-green", :remote => true %>
|
||||
<! } !>
|
||||
<! if(had_done == 1) { !>
|
||||
<a href="javascript:void(0)" class="task-btn mb10" >已通关</a>
|
||||
<! } !>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script >
|
||||
var bt = baidu.template;
|
||||
bt.LEFT_DELIMITER = '<!';
|
||||
bt.RIGHT_DELIMITER = '!>';
|
||||
$(function(){
|
||||
var html = bt('t:exec_results',{status: <%= @game.status %>, results: <%= @results %>, had_done: "<%= @had_done %>"});
|
||||
$("#code_results").html(html);
|
||||
$("#game_test_output").html($("#game_test_output1").text())
|
||||
});
|
||||
function training_task_submmit(){
|
||||
var i = 0;
|
||||
$("#ajax-indicator").show();
|
||||
$("#game_commit").html("<a class='task-btn mb10'>评测中..</a>");
|
||||
$.ajax({
|
||||
url: '<%= game_build_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
success: function (){
|
||||
//移除载入
|
||||
var temp = $("#ajax-indicator").children('span')[0];
|
||||
$("#ajax-indicator").children('span')[0].remove();
|
||||
$("#ajax-indicator").css('border', 0);
|
||||
$("#ajax-indicator").css('padding', 0);
|
||||
|
||||
var intId = setInterval(function(){
|
||||
var ajaxTimeoutTest = $.ajax({
|
||||
url: '<%= game_status_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
timeout : 50000,
|
||||
data:'test',
|
||||
success:function(data){
|
||||
//如果查到了,就退出
|
||||
// i变量用来统计setInterval执行了多少次,便于统计访问时间
|
||||
i = i + 1;
|
||||
if(data.status == 2 || data.status == 0){
|
||||
clearInterval(intId);
|
||||
// 显示载入
|
||||
$("#ajax-indicator").html(temp);
|
||||
$("#ajax-indicator").css('border', "1px solid #bbb");
|
||||
$("#ajax-indicator").css('padding', "0.6em");
|
||||
var html = bt('t:exec_results',{status: data.status, results: data.results, had_done: data.had_done});
|
||||
$("#code_results").html(html);
|
||||
console.log(data.output)
|
||||
$("#game_test_output").html(data.output)
|
||||
var html = bt('t:extend_exec_results',{status: data.status, output: data.output, results: data.results, had_done: data.had_done});
|
||||
$("#extend_code_results").html(html);
|
||||
if(data.status == 2){
|
||||
if( data.had_done == 0 ) {
|
||||
var htmlvalue = "<%= j (render :partial => 'games/pass_game_show', :locals => { :game=> @game, :myshixun => @myshixun, :had_done => 0}) %>";
|
||||
}else{
|
||||
var htmlvalue = "<%= j (render :partial => 'games/pass_game_show', :locals => { :game=> @game, :myshixun => @myshixun, :had_done => 1}) %>";
|
||||
}
|
||||
// 传递具体屏幕的宽高,实现不同浏览器与不同分辨率都能撑满屏幕
|
||||
pop_box_new2(htmlvalue, window.innerWidth, window.innerHeight);
|
||||
}
|
||||
}
|
||||
// 访问30次,即60s后后台还未改变状态即执行
|
||||
if(i == 30){
|
||||
$.ajax({
|
||||
url: '<%= change_status_myshixun_game_path(@game, :myshixun_id => @myshixun) %>',
|
||||
success:function(data){
|
||||
clearInterval(intId);
|
||||
var html = bt('t:exec_results',{status: data.status, output: data.output, results: data.results, had_done: data.had_done});
|
||||
$("#code_results").html(html);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
complete: function(XMLHttpRequest,status){if(status=='timeout'){
|
||||
ajaxTimeoutTest.abort();
|
||||
clearInterval(intId);
|
||||
}}
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
function extend_window(){
|
||||
$("#extend_code_results").show();
|
||||
}
|
||||
function file_edit_submit1(){
|
||||
$('#file_update_id').submit();
|
||||
$("#bottom_save_edit").hide();
|
||||
}
|
||||
|
||||
</script>
|
||||
<% end %>
|
||||
|
|
@ -1,47 +1,89 @@
|
|||
<div class="content clearfix CELL _FLEX COLS ml15 mr15 mt15">
|
||||
<div class="content-info col-width CELL" id="tpi_contents_info">
|
||||
<div class="panel-header clearfix" id="contents_top">
|
||||
<h3 class="fl">第<%= @game_challenge.position %>关:<%= @game_challenge.subject %></h3>
|
||||
<div class="fr mt5">
|
||||
<a href="javascript:void(0);" onclick="extend_game_description();"><i class="fa fa-expand font-14 fl color-grey"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab_content clearfix">
|
||||
<ul id="tab_nav">
|
||||
<li id="tab_nav_1" class="tab_hover" onclick="HoverLi(1);">
|
||||
<a href="javascript:void(0);" class="tab_type">过关任务</a>
|
||||
</li>
|
||||
<li id="tab_nav_2" onclick="HoverLi(2);">
|
||||
<a href="javascript:void(0);" class="tab_type" >预备知识</a>
|
||||
</li>
|
||||
<li id="tab_nav_3" onclick="HoverLi(3);">
|
||||
<a href="javascript:void(0);" class="tab_type" >参考答案</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
<style>
|
||||
.editormd-preview-container, .editormd-html-preview{
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
<div class="split-panel--first -layout -vertical -flex -relative -flex-basic50" id="game_left_contents" >
|
||||
<div class="-fit -layout-v">
|
||||
<div class="-layout-v -flex -bg-white -task-ml80">
|
||||
<ul id="tab_nav" class="-tab-nav">
|
||||
<li id="tab_nav_1" class="tab_hover" onclick="HoverLi1(1);">
|
||||
<a href="javascript:void(0);" class="tab_type">过关任务</a>
|
||||
</li>
|
||||
<li id="tab_nav_2" onclick="HoverLi1(2);">
|
||||
<a href="javascript:void(0);" class="tab_type" >预备知识</a>
|
||||
</li>
|
||||
<li id="tab_nav_3" onclick="HoverLi1(3);">
|
||||
<a href="javascript:void(0);" class="tab_type" >参考答案</a>
|
||||
</li>
|
||||
<span class="btn-cir-big fr mt8 mr15" >经验值:<%= @game_challenge.score %></span>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
<div class="-flex -relative greytab-inner" >
|
||||
<div id="tab_con_1" class="tab-info" >
|
||||
<div class="tab-info-inner" id="task_pass_contents">
|
||||
<%= h @game_challenge.task_pass.try(:html_safe) %>
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll task-padding16 panel-box-sizing" id="game_task_pass">
|
||||
<textarea style="display:none;"><%= @game_challenge.task_pass %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab_con_2" class="undis tab-info">
|
||||
<div class="tab-info-inner" id="task_pass_contents">
|
||||
<%= h @game_challenge.ready_knowledge.try(:html_safe) %>
|
||||
<div id="tab_con_2" class="undis tab-info" >
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll task-padding16 panel-box-sizing" id="game_ready_knowledge">
|
||||
<textarea style="display:none;"><%= @game_challenge.ready_knowledge %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab_con_3" class="undis tab-info">
|
||||
<div class="tab-info-inner" style="line-height: 1.9" id="game_answer_show"></div>
|
||||
<div class="undis" id="hidden_game_answer"><%= @game_challenge.answer %></div>
|
||||
<div id="tab_con_3" class="undis tab-info" >
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll task-padding16" id="game_answer_show"></div>
|
||||
<div class="undis" id="hidden_game_answer"><%= @game_challenge.answer %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render :partial => 'games/repository' %>
|
||||
<div class="-layout-h -center -padding-h-16 -horizontal -footer-left" >
|
||||
<ul class="-layout -task-ml80">
|
||||
<li title="<%= @praise.nil? ? "点赞" : "取消点赞" %>" onclick="game_praise()" id="game_praise_tread"><i class="fa fa-thumbs-up mr15 font-20 <%= @praise.nil? ? "" : "color-light-green" %>" alt="赞" ></i></li>
|
||||
<li title="<%= @tread.nil? ? "踩" : "取消踩" %>" onclick="game_tread()" id="game_tread"><i class="fa fa-thumbs-down mr15 font-20 <%= @tread.nil? ? "" : "color-orange" %>" ></i></li>
|
||||
<li title="纠错" onclick="error_correction_pop();"><i class="fa fa-flag mr30 font-20" ></i></li>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split-panel--second -layout -vertical -flex -relative -flex-basic50" id="game_right_contents">
|
||||
<div class="-layout-v -flex">
|
||||
<div class="-flex -relative">
|
||||
<div class="split-panel -fit -vertical">
|
||||
<div class="-layout -stretch -fit -vertical">
|
||||
<div class="split-panel--first -layout -vertical -flex -relative -bg-black -flex-basic60" id="games_repository_contents">
|
||||
<%= render :partial => 'games/repository' %>
|
||||
</div>
|
||||
<div class="split-panel--second -layout -vertical -flex -relative -bg-black -flex-basic40" id="games_valuation_contents">
|
||||
<%= render :partial => 'games/game_results' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--按钮的操作-->
|
||||
<div class="-layout-h -center -bg-grey-90 -grey-20 -bg-darkblack " style="height:48px; ">
|
||||
<div class="-flex -layout-h" id="game_operate_action">
|
||||
<%= render :partial => "games/code_actions" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render :partial => 'games/exec_results' %>
|
||||
<%#= render :partial => 'games/desc_full_show' %>
|
||||
|
||||
|
||||
<script type="text/javascript" language="javascript">
|
||||
<script>
|
||||
var myMirror = $("#game_answer_show")[0];
|
||||
var myCodeMirror = CodeMirror(myMirror, {
|
||||
value: $("#hidden_game_answer").text(),
|
||||
|
|
@ -51,18 +93,70 @@
|
|||
autofocus: true,
|
||||
border: 0
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
$(document).ready(function() {
|
||||
var wordsView;
|
||||
task_md = editormd.markdownToHTML("game_task_pass", {
|
||||
htmlDecode : "style,script,iframe", // you can filter tags decode
|
||||
emoji : true,
|
||||
taskList : true,
|
||||
tex : true, // 默认不解析
|
||||
flowChart : true, // 默认不解析
|
||||
sequenceDiagram : true // 默认不解析
|
||||
});
|
||||
var wordsView;
|
||||
knowledge_md = editormd.markdownToHTML("game_ready_knowledge", {
|
||||
htmlDecode : "style,script,iframe", // you can filter tags decode
|
||||
emoji : true,
|
||||
taskList : true,
|
||||
tex : true, // 默认不解析
|
||||
flowChart : true, // 默认不解析
|
||||
sequenceDiagram : true // 默认不解析
|
||||
});
|
||||
myCodeMirror.setSize('auto','auto');//设置版本库codemirror的高度
|
||||
$("#game_answer_show" ).children(".CodeMirror").css("line-height", 1.2);
|
||||
$("#game_answer_show").children().css("border", 0);
|
||||
// 设置图片最大宽度不能超出边框
|
||||
$('#task_pass_contents').find('img').css("max-width", "100%");
|
||||
$("#task_pass_contents").css("height", residue_h + "px");
|
||||
$(".tab-info-inner").css("height", residue_h+"px");
|
||||
});
|
||||
//课程大纲tab
|
||||
function extend_game_description(){
|
||||
$("#game_left_contents").show();
|
||||
function error_correction_pop(){
|
||||
var htmlvalue = "<%= escape_javascript(render :partial => 'games/send_error_pop') %>";
|
||||
pop_box_new(htmlvalue,460,316);
|
||||
};
|
||||
|
||||
// 点赞/取消点赞功能
|
||||
var h = true;
|
||||
function game_praise(){
|
||||
$.ajax({
|
||||
url: '<%= praise_tread_praise_plus_path({:obj_id => @game_challenge.id, :obj_type => @game_challenge.class}) %>',
|
||||
data: {horizontal: h, game_praise: true},
|
||||
success:function(data){
|
||||
h = !h;
|
||||
if(data.praise){
|
||||
$("#game_praise_tread").children("i").addClass("color-light-green");
|
||||
$("#game_praise_tread").attr("title", "取消点赞")
|
||||
}else{
|
||||
$("#game_praise_tread").children("i").removeClass("color-light-green");
|
||||
$("#game_praise_tread").attr("title", "点赞")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 踩/取消踩功能
|
||||
var d = true;
|
||||
function game_tread(){
|
||||
$.ajax({
|
||||
url: '<%= praise_tread_praise_plus_path({:obj_id => @game_challenge.id, :obj_type => "ChallengeTread"}) %>',
|
||||
data: {horizontal: d, game_praise: true},
|
||||
success:function(data){
|
||||
d = !d;
|
||||
if(data.praise){
|
||||
$("#game_tread").children("i").addClass("color-orange");
|
||||
$("#game_tread").attr("title", "踩")
|
||||
}else{
|
||||
$("#game_tread").children("i").removeClass("color-orange");
|
||||
$("#game_tread").attr("title", "取消踩")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,66 +1,29 @@
|
|||
<div class="content clearfix" >
|
||||
<div class="col-width-10 ml15 mt15 mr15">
|
||||
<div class="panel-header clearfix">
|
||||
<h3 class="fl">全部任务</h3><span class="btn-cir btn-cir-grey ml5 mt3 fl"><%= @games_count %></span>
|
||||
<div class="page--over">
|
||||
<div class="col-width-3 -scroll" style="height:100%;" id="all_task_index">
|
||||
<div class="-task-list-header clearfix">
|
||||
<h3 class="fl">全部任务</h3>
|
||||
</div>
|
||||
<% @games.each do |game| %>
|
||||
<div class="panel-list">
|
||||
<div class=" clearfix panel-inner">
|
||||
<div class=" clearfix -task-list-inner">
|
||||
<span class="icon-success fl mr5"><%= game.challenge.try(:position) %></span>
|
||||
<% if game.status == 0 %>
|
||||
<div class="fl">
|
||||
<h4 class=" panel-inner-title "><i class="fa fa-dot-circle-o font-18 color-green mr5"></i><span class="color-red">第<%= game.challenge.try(:position) %>关</span>
|
||||
<%= link_to game.challenge.try(:subject), myshixun_game_path(game, :myshixun_id => @myshixun), :remote => true %></h4>
|
||||
<p class="ml15 mt15 color-grey">
|
||||
<span class=" mr10">已闯关:<%= had_pass(@myshixun.shixun_id, game.challenge.try(:position)) %>人</span>
|
||||
<span class=" mr10">平均耗时:<%= game_spend_time(avg_spend_time(@myshixun.shixun_id, game.challenge.try(:position)), 0) %></span>
|
||||
</p>
|
||||
</div>
|
||||
<a href="<%= game_build_myshixun_game_path(game, :myshixun_id => @myshixun) %>" class="task-btn task-btn-green fr" id="open_game" data-remote="true">马上开启</a>
|
||||
<div class="cl"></div>
|
||||
<h4 class=" -task-list-title fl"> <%= link_to game.challenge.try(:subject), myshixun_game_path(game, :myshixun_id => @myshixun), :remote => true %></h4>
|
||||
<% elsif game.status == 1 %>
|
||||
<div class="fl">
|
||||
<h4 class=" panel-inner-title "><i class="fa fa-dot-circle-o font-18 color-green mr5"></i><span class="color-red">第<%= game.challenge.try(:position) %>关</span>
|
||||
<%= game.challenge.try(:subject) %></h4>
|
||||
<p class="ml15 mt15 color-grey">
|
||||
<span class=" mr10">已闯关:<%= had_pass(@myshixun.shixun_id, game.challenge.try(:position)) %>人</span>
|
||||
<span class=" mr10">平均耗时:<%= game_spend_time(avg_spend_time(@myshixun.shixun_id, game.challenge.try(:position)), 0) %></span>
|
||||
</p>
|
||||
</div>
|
||||
<a href="#" class=" fr link-color-green">正在解决中</a>
|
||||
<div class="cl"></div>
|
||||
<h4 class="-task-list-title fl"> <%= game.challenge.try(:subject) %></h4>
|
||||
<span class="fr color-light-green">正在解决</span>
|
||||
<% elsif game.status == 2 %>
|
||||
<h4 class="fl panel-inner-title "><i class="fa fa-check-circle font-18 color-green mr5"></i><span class="color-red">第<%= game.challenge.try(:position) %>关</span>
|
||||
<%= link_to game.challenge.try(:subject), myshixun_game_path(game, :myshixun_id => @myshixun), :remote => true %></h4>
|
||||
<!--<p class="fr "><i class="fa fa-trophy color-yellow font-16 mr5 "></i><span class=" color-grey">NO.120</span></p>-->
|
||||
<div class="cl"></div>
|
||||
<p class="ml15 mt15 color-grey">
|
||||
<span class=" mr10">开始时间:<%= format_time(game.created_at) %></span>
|
||||
<span class=" mr10">完成时间:<%= format_time(game.updated_at) %></span>
|
||||
<span class=" mr10">耗时:<%= game_spend_time(game.created_at, game.updated_at) %></span>
|
||||
<span class=" mr10">已闯关:<%= had_pass(@myshixun.shixun_id, game.challenge.try(:position)) %>人</span>
|
||||
<span class=" mr10">得分:<%= game.final_score %>分</span>
|
||||
</p>
|
||||
<h4 class="-task-list-title fl"> <%= link_to game.challenge.try(:subject), myshixun_game_path(game, :myshixun_id => @myshixun), :remote => true %></h4>
|
||||
<a class="fr"> <i class="fa fa-check fr font-18 mt5" ></i></a>
|
||||
<% elsif game.status == 3 %>
|
||||
<div class="fl">
|
||||
<h4 class=" panel-inner-title "><i class="fa fa-lock font-18 color-grey mr5"></i><span class="color-red">第<%= game.challenge.try(:position) %>关</span>
|
||||
<%= game.challenge.try(:subject) %></h4>
|
||||
<p class="ml15 mt15 color-grey">
|
||||
<span class=" mr10">已闯关:<%= had_pass(@myshixun.shixun_id, game.challenge.try(:position)) %>人</span>
|
||||
<span class=" mr10">平均耗时:<%= game_spend_time(avg_spend_time(@myshixun.shixun_id, game.challenge.try(:position)), 0) %></span>
|
||||
</p>
|
||||
</div>
|
||||
<a href="#" class=" task-btn fr"> 已锁定 </a>
|
||||
<div class="cl"></div>
|
||||
<h4 class="-task-list-title fl"> <%= game.challenge.try(:subject) %></h4>
|
||||
<a class="fr"> <i class="fa fa-lock fr font-18 mt5" ></i></a>
|
||||
<% end %>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#open_game").click(function(){
|
||||
$("#open_game").removeClass("task-btn-green");
|
||||
$("#open_game").html("评测中..")
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,27 @@
|
|||
<div class="content-editor col-width2 CELL" id="myshixun_repository">
|
||||
<%= render :partial => 'myshixun_repository' %>
|
||||
<div class="panel-header clearfix" id="top_repository" style="border-bottom:0">
|
||||
<h3 class="fl">
|
||||
<%= render :partial => 'myshixun_breadcrumbs', :locals => {:path => @path, :kind => 'dir'} %>
|
||||
</h3>
|
||||
<div class="fr mt5 -horizontal">
|
||||
<a href="javascript:void(0);" onclick="repository_extend_and_zoom();" id="extend_and_zoom"><i class="fa fa-arrows-alt font-16"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cl"></div>
|
||||
<script>
|
||||
var web_h = window.innerHeight;
|
||||
$(document).ready(function(){
|
||||
|
||||
});
|
||||
function extend_repository(){
|
||||
$("#extend_code_results").hide();
|
||||
$("#myshixun_extend_repository").show();
|
||||
extend_editor.refresh();
|
||||
}
|
||||
</script>
|
||||
<div class="-flex -relative">
|
||||
<div class="fit -scroll">
|
||||
<div class="-layout-v -fit">
|
||||
<div class="-flex -scroll">
|
||||
<div class="content-editor-inner">
|
||||
<% if params[:action] == "entry" || @file_open %>
|
||||
<div id="file_entry_content">
|
||||
<%= render :partial => 'games/file_edit_form', :locals => {:filename => @path, :content => @content} %>
|
||||
</div>
|
||||
<% else %>
|
||||
<% unless @entries.blank? %>
|
||||
<%= render :partial => 'tree', :locals => { :extend => false}%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<div class=" task-popup-title clearfix task-popup-bggrey" style="width: 500px">
|
||||
<h3 class="fl ">问题报告</h3>
|
||||
<a href="javascript:void(0)" onclick="hideModal();"><i class="fa fa-times-circle font-18 link-color-grey fr mt5"></i></a>
|
||||
</div>
|
||||
<div class="task-popup-content">
|
||||
<p class=" font-16">这个任务有什么问题或错误?</p>
|
||||
<p class=" font-12 color-grey">请尽可能清晰的描述</p>
|
||||
<textarea class="task-form-100 panel-box-sizing mt10" placeholder="限100字以内" id="error_contents"></textarea>
|
||||
</div>
|
||||
<div class=" clearfix mb20 mr15">
|
||||
<a href="javascript:void(0);" onclick="hideModal();" class="task-btn fr">取消</a>
|
||||
<a href="javascript:void(0);" onclick="problem_report('<%= User.current.id %>');hideModal();" class="task-btn task-btn-blue fr mr10">确定</a>
|
||||
</div>
|
||||
<script>
|
||||
// 给导师和管理员发送error
|
||||
function problem_report(user){
|
||||
$.ajax({
|
||||
url: '<%= leave_email_activation_message_path(1) %>',
|
||||
data: {user: user, text: $("#error_contents").val(), game_id: '<%= @game.id %>', shixun_name:'<%= @myshixun.name %>', shixun_author_id:'<%= @game_challenge.user_id %>', position:'<%= @game_challenge.position %>'},
|
||||
type: "POST",
|
||||
success: function (data) {
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<% ent_path = Redmine::CodesetUtil.replace_invalid_utf8(sub_path) %>
|
||||
<% ent_name = Redmine::CodesetUtil.replace_invalid_utf8(entry.name) %>
|
||||
<tr id="<%= tr_id %>" class="<%= h params[:parent_id] %> entry <%= entry.kind %>">
|
||||
<td class="filename_no_report hidden">
|
||||
<td class="filename_no_report hidden" style="background: #333;border: solid;">
|
||||
<%= link_to h(ent_name),
|
||||
{:action => (entry.is_dir? ? 'show' : 'entry'), :id => @game, :myshixun_id => @myshixun, :path => to_path_param(ent_path), :rev => @rev, :extend => extend },
|
||||
:remote => true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
$("#myshixun_repository").html('<%= escape_javascript(render :partial => 'games/myshixun_repository') %>');
|
||||
$("#myshixun_extend_repository").html('<%= escape_javascript(render :partial => 'games/myshixun_extend_repository') %>');
|
||||
//$("#game_rep_modify").html('<a href="javascript:void(0)" class="task-btn task-btn-green mb10" id="bottom_save_edit" onclick="file_edit_submit1()">保存修改</a>');
|
||||
$("#games_repository_contents").html('<%= escape_javascript(render :partial => 'games/repository') %>');
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
$("#content_list").html('<%= escape_javascript(render :partial => 'games/games_list') %>');
|
||||
$("#current_task_tab").attr('href','<%= myshixun_game_path(@myshixun.current_task, :myshixun_id => @myshixun) %>');
|
||||
$("#myshixun_top").html('<%= escape_javascript(render :partial => 'myshixuns/myshixun_top') %>');
|
||||
// 左侧导航样式改动
|
||||
$("#current_task_tab").removeClass("leftnav-active");
|
||||
$("#all_task_tab").addClass("leftnav-active");
|
||||
// 宽版隐藏
|
||||
$("#myshixun_extend_repository").hide();
|
||||
$("#game_left_contents").hide();
|
||||
$("#extend_code_results").hide();
|
||||
$("#all_task_show").html('<%= escape_javascript(render :partial => 'games/games_list') %>');
|
||||
$("#all_task_show").css("height", "100%");
|
||||
if(fadein == 0){
|
||||
$("#all_task_show").css("background", "rgba(0,0,0,0.3)");
|
||||
$("#all_task_show").show();
|
||||
$("#all_task_index").css("left","-420px").stop().animate({
|
||||
left: 0
|
||||
}, 400, function(){
|
||||
fadein = 1;
|
||||
});
|
||||
}else{
|
||||
$("#all_task_show").css("background","rgba(0,0,0,0)");
|
||||
$("#all_task_index").css("left", 0).stop().animate({
|
||||
left: "-505px"
|
||||
}, 400, function(){
|
||||
$("#all_task_show").hide();
|
||||
fadein = 0;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,56 @@
|
|||
<%= javascript_include_tag 'baiduTemplate', 'jquery.datetimepicker.js' %>
|
||||
<%= javascript_include_tag "/assets/codemirror/codemirror_python_ruby_c" %>
|
||||
<%= stylesheet_link_tag "/assets/codemirror/codemirror" %>
|
||||
<div id="content_list" class="CELL _FLEX ROWS">
|
||||
<%= render :partial => "game_show" %>
|
||||
</div>
|
||||
<div id="content_attr_1">
|
||||
<%= render :partial => 'desc_full_show'%>
|
||||
</div>
|
||||
<div class="col-width content-all-fix undis" style="border: none" id="myshixun_extend_repository">
|
||||
<%= render :partial => 'myshixun_extend_repository' %>
|
||||
</div>
|
||||
<div id="content_attr_2">
|
||||
<%= render :partial => 'extend_test_output' %>
|
||||
<%= javascript_include_tag "/codemirror/lib/codemirror", "/codemirror/mode/javascript/javascript", "/codemirror/addon/edit/matchbrackets.js", "/codemirror/mode/clike/clike" %>
|
||||
<%= stylesheet_link_tag "/codemirror/lib/codemirror", "/codemirror/theme/ambiance" %>
|
||||
<%= stylesheet_link_tag '/editormd/css/editormd','/editormd/css/editormd.min' %>
|
||||
<%= javascript_include_tag '/editormd/lib/marked.min.js','/editormd/lib/prettify.min.js','/editormd/lib/raphael.min.js','/editormd/lib/underscore.min.js','/editormd/lib/sequence-diagram.min.js',
|
||||
'/editormd/lib/flowchart.min.js','/editormd/lib/jquery.flowchart.min.js','/editormd/editormd.js'%>
|
||||
<div class="page--body -margin-t-64 -flex">
|
||||
<div class="page--over undis" id="all_task_show">
|
||||
</div>
|
||||
<div class="-layout -stretch -fit" id="game_show_content">
|
||||
<%= render :partial => 'games/game_show' %>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var fadein = 0; //控制全部任务的渐入浅出效果
|
||||
// 版本库扩大与缩小
|
||||
var control = 0; // 版本库控制 0表示点击放大 1表示点击缩小
|
||||
var control_1 = 0; // 测评控制 0表示点击放大 1表示点击缩小
|
||||
|
||||
<script type="text/javascript" language="javascript">
|
||||
// contents_h 是中间左侧内容页的高度,不同浏览器的得到高度不一致
|
||||
// contents_top_h 是内容页头部的高度
|
||||
// contents_tab_nav是三个tab的高度
|
||||
// residue_h contents剩余展示内容的高度
|
||||
// 左侧内容
|
||||
var contents_h = $("#tpi_contents_info").height();
|
||||
var contents_top_h = $("#contents_top").height() + 20;
|
||||
var contents_tab_nav = $("#tab_nav").height();
|
||||
var residue_h = contents_h - contents_top_h - contents_tab_nav - 10;
|
||||
$(document).ready(function(){
|
||||
$(".tab-info-inner").css("height", residue_h+"px");
|
||||
$(".content-half-fix").css("height", residue_h + 310 +"px")
|
||||
});
|
||||
//课程大纲tab
|
||||
function g(o){return document.getElementById(o);}
|
||||
function HoverLi(n){
|
||||
for(var i=1;i<=3;i++){
|
||||
g('tab_nav_'+i).className='tab_nomal';
|
||||
g('tab_con_'+i).className='undis';
|
||||
}
|
||||
g('tab_con_'+n).className='dis';
|
||||
g('tab_nav_'+n).className='tab_hover';
|
||||
if(n == 3){
|
||||
// 点击参考答案时,让codemirror展示,如果不refresh的话,第一次展示会隐藏
|
||||
myCodeMirror.refresh();
|
||||
}
|
||||
}
|
||||
$("#close_myshixun_tip").click(function(){
|
||||
$("#myshixun_tip").hide();
|
||||
});
|
||||
function close_big_repository(){
|
||||
$("#myshixun_extend_repository").hide()
|
||||
}
|
||||
</script>
|
||||
function repository_extend_and_zoom(){
|
||||
if(control == 0){
|
||||
$("#game_left_contents").addClass("-flex-basic0");
|
||||
$("#game_right_contents").addClass("-flex-basic100 -task-ml80");
|
||||
$("#games_repository_contents").addClass("-flex-basic100");
|
||||
$("#games_valuation_contents").addClass("-flex-basic0");
|
||||
$("#extend_and_zoom").children("i").addClass("fa-compress");
|
||||
$("#extend_and_zoom").children("i").removeClass("fa-arrows-alt");
|
||||
editor_CodeMirror.setSize("auto", "auto");
|
||||
control = 1;
|
||||
}else if(control == 1){
|
||||
$("#game_left_contents").removeClass("-flex-basic0");
|
||||
$("#game_right_contents").removeClass("-flex-basic100 -task-ml80");
|
||||
$("#games_repository_contents").removeClass("-flex-basic100");
|
||||
$("#games_valuation_contents").removeClass("-flex-basic0");
|
||||
$("#extend_and_zoom").children("i").removeClass("fa-compress");
|
||||
$("#extend_and_zoom").children("i").addClass("fa-arrows-alt");
|
||||
editor_CodeMirror.setSize("auto", "auto");
|
||||
control = 0;
|
||||
}
|
||||
}
|
||||
// 测评的扩大与缩小
|
||||
function valuation_extend_and_zoom(){
|
||||
if(control_1 == 0){
|
||||
$("#games_repository_contents").addClass("-flex-basic0");
|
||||
$("#games_valuation_contents").addClass("-flex-basic100");
|
||||
$("#valuation_extend_and_zoom").children("i").removeClass("fa-expand");
|
||||
$("#valuation_extend_and_zoom").children("i").addClass("fa-compress");
|
||||
control_1 = 1;
|
||||
}else if(control_1 == 1){
|
||||
$("#games_repository_contents").removeClass("-flex-basic0");
|
||||
$("#games_valuation_contents").removeClass("-flex-basic100");
|
||||
$("#valuation_extend_and_zoom").children("i").addClass("fa-expand");
|
||||
$("#valuation_extend_and_zoom").children("i").removeClass("fa-compress");
|
||||
control_1 = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
$("#content_list").html('<%= escape_javascript(render :partial => 'games/game_show') %>');
|
||||
$("#content_attr_1").html('<%= escape_javascript(render :partial => 'games/desc_full_show') %>');
|
||||
$("#myshixun_extend_repository").html('<%= escape_javascript(render :partial => 'games/myshixun_extend_repository') %>');
|
||||
$("#content_attr_2").html('<%= escape_javascript(render :partial => 'games/extend_test_output') %>');
|
||||
$("#myshixun_top").html('<%= escape_javascript(render :partial => 'myshixuns/myshixun_top') %>');
|
||||
$("#game_show_content").html('<%= escape_javascript(render :partial => 'games/game_show') %>');
|
||||
$("#current_task_tab").attr('href','<%= myshixun_game_path(@myshixun.current_task, :myshixun_id => @myshixun) %>');
|
||||
$("#all_task_show").hide();
|
||||
fadein = 0;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<%= favicon %>
|
||||
<%= javascript_heads %>
|
||||
<%= heads_for_theme %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2','css/common', 'css/font-awesome','css/taskstyle','css/structure','scm','css/public', 'css/project','css/popup','repository','css/gantt', 'css/calendar', 'css/moduel', 'css/cssPlus-v.0.2-build' %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2','css/common', 'css/font-awesome','css/taskstyle','css/structure','scm','css/public', 'css/project','css/popup','repository','css/gantt', 'css/calendar', 'css/moduel', 'css/cssPlus-v.0.2-build', 'css/bigdata-common','css/bigdata-public', 'css/bigdata-popup' %>
|
||||
<%= call_hook :view_layouts_base_html_head %>
|
||||
<!-- page specific tags -->
|
||||
<%= yield :header_tags -%>
|
||||
|
|
@ -18,39 +18,30 @@
|
|||
<body onload="prettyPrint();">
|
||||
<div class="cl"></div>
|
||||
|
||||
<div class="task-container EXTENDER">
|
||||
<div class="COLS">
|
||||
<div class="leftbar CELL" style="width: 120px;">
|
||||
<div class="CELL">
|
||||
<div class="user-info" style="width: 120px;height:120px;padding-top: 10px;">
|
||||
<%= link_to image_tag(url_to_avatar(@myshixun.owner), :width => "100", :height => "100"), user_path(@myshixun.owner), :alt => "用户头像", :class => "user-info-img", :style => "border-radius: 100px;margin-left: 5px;" %>
|
||||
<!--<a href="#" class="user-info-img"><img src="images/user/male.jpg" width="100" height="" alt="100"></a>-->
|
||||
<%#= link_to @myshixun.owner.try(:show_name), user_path(@myshixun.owner), :class => "user-info-name" %>
|
||||
</div>
|
||||
<div class="leftnav">
|
||||
<ul>
|
||||
<li class="leftnav-box" style="width: 120px">
|
||||
<a href="<%= myshixun_game_path(@myshixun.current_task, :myshixun_id => @myshixun) %>" class="leftnav-box-inner leftnav-active" style="width: 120px" id="current_task_tab" data-remote="true">
|
||||
<i class="fa fa-gamepad font-28"></i><br/><span class="font-16">当前任务</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="leftnav-box" style="width: 120px">
|
||||
<a href="<%= myshixun_games_path(@myshixun) %>" class="leftnav-box-inner" style="width: 120px" id="all_task_tab" data-remote="true">
|
||||
<i class="fa fa-qrcode font-28"></i><br/><span class="font-16">全部任务</span><span class="btn-cir ml5"><%= @myshixun.games.count %></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightbar CELL _FLEX ROWS">
|
||||
<div id="myshixun_top">
|
||||
<%= render :partial => "myshixuns/myshixun_top" %>
|
||||
</div>
|
||||
|
||||
<%= yield %>
|
||||
</div>
|
||||
<div class="page -layout-v -fit ">
|
||||
<div class="page--header clearfix" id="myshixun_top">
|
||||
<%= render :partial => 'myshixuns/myshixun_top' %>
|
||||
</div>
|
||||
<div class="page--leftnav">
|
||||
<div class="user-info">
|
||||
<%= link_to image_tag(url_to_avatar(@myshixun.owner), :width => "50", :height => "50"), user_path(@myshixun.owner), :alt => "用户头像", :class => "user-info-img" %>
|
||||
<%= link_to @myshixun.owner.try(:show_name), user_path(@myshixun.owner), :class => "user-info-name" %>
|
||||
</div>
|
||||
<div class="leftnav">
|
||||
<ul>
|
||||
<li class="leftnav-box">
|
||||
<a href="<%= myshixun_game_path(@myshixun.current_task, :myshixun_id => @myshixun) %>" class="leftnav-box-inner leftnav-active" id="current_task_tab" data-remote="true">
|
||||
<i class="fa fa-gamepad font-20"></i><br/><span class="font-12">当前任务</span>
|
||||
</a></li>
|
||||
<li class="leftnav-box">
|
||||
<a href="<%= myshixun_games_path(@myshixun, :fadein => true) %>" class="leftnav-box-inner" id="all_task_tab" data-remote="true">
|
||||
<i class="fa fa-qrcode font-20"></i><br/><span class="font-12">全部任务</span><br/><span class="btn-cir ml5"><%= @myshixun.games.count %></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%= yield %>
|
||||
</div>
|
||||
|
||||
<div id="ajax-indicator" style="display:none;">
|
||||
|
|
@ -61,13 +52,43 @@
|
|||
</body>
|
||||
<!-- MathJax的配置 -->
|
||||
<script type="text/javascript" src="/javascripts/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
|
||||
function g(o){return document.getElementById(o);}
|
||||
function HoverLi1(n){
|
||||
for(var i=1;i<=3;i++){
|
||||
g('tab_nav_'+i).className='tab_nomal';
|
||||
g('tab_con_'+i).className='undis';
|
||||
}
|
||||
g('tab_con_'+n).className='dis';
|
||||
g('tab_nav_'+n).className='tab_hover';
|
||||
if(n == 3){
|
||||
// 点击参考答案时,让codemirror展示,如果不refresh的话,第一次展示会隐藏
|
||||
myCodeMirror.refresh();
|
||||
}
|
||||
}
|
||||
function HoverLi(n){
|
||||
for(var i=1;i<=2;i++){
|
||||
g('blacktab_nav_'+i).className='blacktab_nomal';
|
||||
g('blacktab_con_'+i).className='undis';
|
||||
}
|
||||
g('blacktab_con_'+n).className='dis';
|
||||
g('blacktab_nav_'+n).className='blacktab_hover';
|
||||
}
|
||||
|
||||
// 点击全部任务左侧tab样式切换
|
||||
var c = 0; // 设置全部任务展开样式,与不展开时的样式
|
||||
$("#all_task_tab").click(function(){
|
||||
$("#all_task_tab").addClass("leftnav-active");
|
||||
$("#current_task_tab").removeClass("leftnav-active");
|
||||
if( c == 0){
|
||||
$("#all_task_tab").addClass('leftnav-active');
|
||||
c = 1;
|
||||
}else if(c == 1){
|
||||
$("#all_task_tab").removeClass('leftnav-active');
|
||||
c = 0;
|
||||
}
|
||||
});
|
||||
$("#current_task_tab").click(function(){
|
||||
$("#current_task_tab").addClass("leftnav-active");
|
||||
$("#all_task_tab").removeClass("leftnav-active");
|
||||
$("#current_task_tab").addClass('leftnav-active');
|
||||
$("#all_task_tab").removeClass('leftnav-active');
|
||||
c = 0;
|
||||
});
|
||||
</script>
|
||||
<%= javascript_include_tag 'cookie','project',"avatars", 'header','prettify','select_list_move','attachments' %>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<%= favicon %>
|
||||
<%= javascript_heads %>
|
||||
<%= heads_for_theme %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2','css/common', 'css/taskstyle', 'css/structure','scm','css/public', 'css/project','css/popup','repository','css/gantt', 'css/calendar', 'css/moduel', 'css/font-awesome' %>
|
||||
<%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2','css/common', 'css/bigdata-common','css/bigdata-public', 'css/taskstyle', 'css/structure','scm','css/public', 'css/project','css/popup','repository','css/gantt', 'css/calendar', 'css/moduel', 'css/font-awesome' %>
|
||||
<%= call_hook :view_layouts_base_html_head %>
|
||||
<!-- page specific tags -->
|
||||
<%= yield :header_tags -%>
|
||||
|
|
@ -17,9 +17,28 @@
|
|||
<!--add by huang-->
|
||||
<body onload="prettyPrint();">
|
||||
<div class="navContainer"> <%= render :partial => User.current.logged? ? 'layouts/logined_header' : 'layouts/unlogin_header' %></div>
|
||||
|
||||
<div id="shixun_top">
|
||||
<%= render :partial => 'shixuns/shixun_top' %>
|
||||
</div>
|
||||
|
||||
<div class="task-header-nav">
|
||||
<ul class="task-header-navs clearfix">
|
||||
<li><a href="<%= shixun_challenges_url(@shixun) %>" class="<%= params[:action] == "index" ? "active" : "" %>" >实训</a></li>
|
||||
<% unless @shixun.repository.nil? %>
|
||||
<li>
|
||||
<%= link_to l(:project_module_repository),
|
||||
({:controller => 'repositories',
|
||||
:action => 'shixun_show',
|
||||
:id => @shixun,
|
||||
:repository_id => shixun_repository(@shixun).try(:identifier)}),
|
||||
:class => (params[:controller] == 'repositories' ? 'active' : '') %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if User.current.manager_of_shixun?(@shixun) %>
|
||||
<li><a href="<%= settings_shixun_path(@shixun) %>" class="<%= params[:action] == "settings" ? "active" : "" %>">配置</a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<%#= render_flash_messages %>
|
||||
<div class="task-pm-content">
|
||||
<%= render_flash_messages %>
|
||||
|
|
@ -33,9 +52,7 @@
|
|||
<div class="cl"></div>
|
||||
<%= render :partial => 'layouts/footer' %>
|
||||
<div class="cl"></div>
|
||||
<% if hidden_unproject_infos %>
|
||||
<%= render :partial => 'layouts/new_feedback' %>
|
||||
<% end %>
|
||||
|
||||
<div id="ajax-indicator" style="display:none;">
|
||||
<span><%= l(:label_loading) %></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
<div class="rightbar-header clearfix CELL">
|
||||
<h2 class="rightbar-h2 fl"><%= @myshixun.name %></h2>
|
||||
<a href="javascript:void(0)" class="fr rightbar-pause" id="leave_shixun_page"><i class="fa fa-power-off font-20 mt5"></i><span class="ml10">暂时离开</span></a>
|
||||
<ul class="rightbar-score fr" >
|
||||
<!--<li><i class="fa fa-signal font-14 mr5"></i>排名:<a href="#" class="">123</a></li>-->
|
||||
<% if params[:action] == "show" && (Time.now - @game.created_at) > 60 %>
|
||||
<li><i class="fa fa-trophy font-14 mr5"></i>积分:<%= shixun_final_score(@myshixun) %></li>
|
||||
<li><i class="fa fa-clock-o font-14 mr5"></i>耗时:<%= @game.status != 2 ? game_spend_time(@game.created_at, Time.now) : game_spend_time(@game.created_at, @game.updated_at) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<div class="-layout-h ml10 fl">
|
||||
<h2 class="-header-title task-hide font-20 color-white"><%= @myshixun.name %></h2>
|
||||
<i class="fa fa-angle-right color-white font-20 ml5 mr5 mt10"></i>
|
||||
<span class="font-20 color-white">第<%= @game_challenge.position %>关</span>
|
||||
</div>
|
||||
<script>
|
||||
$("#leave_shixun_page").click(function(){
|
||||
var htmlvalue = "<%= j (render :partial => 'games/leave_tip') %>";
|
||||
pop_box_new(htmlvalue,460,316);
|
||||
});
|
||||
</script>
|
||||
<div class="-layout-h mr10 fr mt3 -header-right-box">
|
||||
<div class="-header-right clearfix">
|
||||
<img src="/images/task/coin.png" class="fl" width="30" height="30">
|
||||
<span class="ml5 color-white fl"><%= sum_final_score %></span>
|
||||
</div>
|
||||
<!--<div class="-header-right-info ">-->
|
||||
<!--<font></font>-->
|
||||
<!--<div class="-task-bar-bg"><span class="-task-bar-inner -task-widht-30"></span>3k/8K</div>-->
|
||||
<!--<span class="font-12 color-grey">距离下次升级需要 +5000 经验值</span>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<script>
|
||||
$(document).ready(function(){
|
||||
$.ajax({
|
||||
url:"<%= rep_tree_changes_shixun_path(@project, :rev => @rev, :ent_path => ent_path, :gpid => @project.gpid) %>",
|
||||
url:"<%= repository_tree_changes_project_path(@project, :rev => @rev, :ent_path => ent_path, :gpid => @project.gpid) %>",
|
||||
type: "GET",
|
||||
data: "text",
|
||||
success:function(data){
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
:repository_id => @repository.identifier_param,
|
||||
:path => to_path_param(ent_path),
|
||||
:rev => @rev,
|
||||
|
||||
:depth => (depth + 1),
|
||||
:parent_id => tr_id)) %>');"> </span>
|
||||
<% end %>
|
||||
|
|
@ -24,42 +23,7 @@
|
|||
{:action => (entry.is_dir? ? 'shixun_show' : 'shixun_entry'), :id => @shixun, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
|
||||
:class => (entry.is_dir? ? 'old-icon old-icon-folder' : "old-icon old-icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
|
||||
</td>
|
||||
<div id="children_tree">
|
||||
<td class="tree-author c_grey">
|
||||
<div class="hidden" id="changes_author_<%= tr_id %>">
|
||||
<%#= (latest_changes.author_name) if latest_changes %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="tree-comments c_grey hidden">
|
||||
<div class="hidden" id="changes_message_<%= tr_id %>">
|
||||
<%#= (latest_changes.message) if latest_changes %>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="tree-age c_grey">
|
||||
<div class="hidden" id="changes_time_<%= tr_id %>">
|
||||
<%# 为了转换UTC时间,时差8小时 %>
|
||||
<%#= distance_of_time_in_words(latest_changes.time, Time.now) if latest_changes %>
|
||||
<%#= link_to "", repository_tree_changes_project_path(@project, :rev => @rev, :ent_name => ent_name) %>
|
||||
</div>
|
||||
</td>
|
||||
</div>
|
||||
</tr>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$.ajax({
|
||||
url:"<%= repository_tree_changes_project_path(@shixun, :rev => @rev, :ent_path => ent_path, :gpid => @shixun.gpid) %>",
|
||||
type: "GET",
|
||||
data: "text",
|
||||
success:function(data){
|
||||
$('#changes_message_<%=tr_id %>').html(data.message)
|
||||
$('#changes_author_<%=tr_id %>').html(data.author_name)
|
||||
$('#changes_time_<%=tr_id %>').html(data.time)
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<p class="c_orange ml70" id="project_name_notice" style="display: none;">实训名称不能为空</p>
|
||||
</li>
|
||||
<li class="ml45 mb10">
|
||||
<label class="fl mr5"> 预备知识 :</label>
|
||||
<label class="fl ml30 mr5">简介 :</label>
|
||||
<%= f.kindeditor :description, :editor_id => 'project_create_editor',
|
||||
:owner_id => @project.nil? ? 0: @project.id,
|
||||
:owner_type => OwnerTypeHelper::PROJECT,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
var htmlvalue = "<%= escape_javascript(render :partial => 'shixuns/monitor_tip') %>";
|
||||
pop_box_new(htmlvalue,460,316);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<div style="width:460px;">
|
||||
<div style="width:360px;">
|
||||
<div class="task-popup-title clearfix">
|
||||
<h3 class="fl color-grey">提示</h3>
|
||||
<a href="javascript:void(0);" onclick="hideModal()"><i class="fa fa-times-circle font-18 link-color-grey fr"></i></a>
|
||||
|
|
@ -7,21 +7,8 @@
|
|||
<div class="sy_popup_con" style="width:380px;">
|
||||
<ul class="sy_popup_add" >
|
||||
<li class="center mb30" style="line-height:20px">
|
||||
<%= @notice %>
|
||||
服务器异常,请联系系统管理员
|
||||
</li>
|
||||
<div class="task-popup-submit clearfix">
|
||||
<a href="javascript:void(0);" class="task-btn fl" onclick="hideModal();show_begin_challeng();">取消</a>
|
||||
<% if @had_exec == true %>
|
||||
<%= link_to "确 定", myshixun_path(@tpm), :class => "task-btn task-btn-green fr", :onclick => "hideModal();show_begin_challeng();", :target => "_blank" %>
|
||||
<% else %>
|
||||
<a href="<%= shixun_exec_shixun_path(@shixun) %>" class="task-btn task-btn-green fr" onclick="hideModal();show_begin_challeng()" target="_blank">确定</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function show_begin_challeng(){
|
||||
$("#challenge_begin").show();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<span class="c_red ml5 w690" id="project_name_notice" style="padding-left:100px;display: none;">实训名称不能为空!</span>
|
||||
</li>
|
||||
<li class="clearfix">
|
||||
<label class="panel-form-label fl">预备知识:</label>
|
||||
<label class="panel-form-label fl">简介:</label>
|
||||
<%= f.kindeditor :description, :editor_id => 'shixun_setting_editor',
|
||||
:owner_id => @shixun.nil? ? 0: @shixun.id,
|
||||
:owner_type => OwnerTypeHelper::PROJECT,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="task-pd15-box task-setting-tab">
|
||||
<div class="alert alert-blue mb15" id="warning_frame">
|
||||
<button data-dismiss="alert" class="task-close fr" type="button" onclick="close_waring_frame();">×</button>
|
||||
<!--<button data-dismiss="alert" class="task-close fr" type="button" onclick="close_waring_frame();">×</button>-->
|
||||
温馨提示:每个项目只能创建一个版本库。
|
||||
</div>
|
||||
<%= error_messages_for 'project' %>
|
||||
|
|
|
|||
|
|
@ -6,31 +6,18 @@
|
|||
<span class="ml5 mr5 fl">/ </span><p class="task-header-title fl" title="<%= @shixun.name %>"><%= @shixun.name %></p>
|
||||
</h2>
|
||||
<% if allow_shixun_exec(@shixun) %>
|
||||
<%= link_to "开始挑战", shixun_monitor_shixun_path(@shixun),
|
||||
:class => "fr task-btn task-btn-orange",
|
||||
:id => "challenge_begin",
|
||||
:remote => true %>
|
||||
<% if User.current.id == @shixun.user_id %>
|
||||
<% if @shixun.status == 1 %>
|
||||
<a class="fr task-btn task-btn-orange">已发布</a>
|
||||
<% else %>
|
||||
<%= link_to "发布实训", publish_shixun_path(@shixun), :class => "fr task-btn task-btn-orange", :id => "challenge_begin", :remote => true %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% if @shixun.status == 1 %>
|
||||
<%= link_to "开始挑战", shixun_exec_shixun_path(@shixun), :class => "fr task-btn task-btn-orange", :id => "challenge_begin" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-header-nav">
|
||||
<ul class="task-header-navs clearfix">
|
||||
<li><a href="<%= shixun_challenges_url(@shixun) %>" class="<%= params[:action] == "index" ? "active" : "" %>" >实训</a></li>
|
||||
<% unless @shixun.repository.nil? %>
|
||||
<li>
|
||||
<%= link_to l(:project_module_repository),
|
||||
({:controller => 'repositories',
|
||||
:action => 'shixun_show',
|
||||
:id => @shixun,
|
||||
:repository_id => shixun_repository(@shixun).try(:identifier)}),
|
||||
:class => (params[:controller] == 'repositories' ? 'active' : '')
|
||||
%>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if User.current.manager_of_shixun?(@shixun) %>
|
||||
<li><a href="<%= settings_shixun_path(@shixun) %>" class="<%= params[:action] == "settings" ? "active" : "" %>">配置</a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<% if @jenkin_error %>
|
||||
var htmlvalue = "<%= escape_javascript(render :partial => 'shixuns/monitor_tip') %>";
|
||||
pop_box_new(htmlvalue,460,406);
|
||||
<% else %>
|
||||
$("#shixun_top").html('<%= escape_javascript(render :partial => 'shixuns/shixun_top') %>');
|
||||
<% end %>
|
||||
|
|
@ -8,9 +8,9 @@
|
|||
<li id="tab_nav_2" onclick="HoverLi(2);">
|
||||
<a href="javascript:void(0);" class="tab_type" >版本库</a>
|
||||
</li>
|
||||
<li id="tab_nav_3" onclick="HoverLi(3);">
|
||||
<a href="javascript:void(0);" class="tab_type" >测试脚本</a>
|
||||
</li>
|
||||
<!--<li id="tab_nav_3" onclick="HoverLi(3);">-->
|
||||
<!--<a href="javascript:void(0);" class="tab_type" >测试脚本</a>-->
|
||||
<!--</li>-->
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
<div id="tab_con_1" class="" >
|
||||
|
|
@ -19,9 +19,9 @@
|
|||
<div id="tab_con_2" class="undis" >
|
||||
<%= render :partial=>"shixuns/settings_repository" %>
|
||||
</div>
|
||||
<div id="tab_con_3" class="undis" >
|
||||
<%= render :partial=>"shixuns/settings_challenges" %>
|
||||
</div>
|
||||
<!--<div id="tab_con_3" class="undis" >-->
|
||||
<!--<%#= render :partial=>"shixuns/settings_challenges" %>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<li><a href="javascript:void(0)" id="pro_st_tb_1" class="<%= show_memu == 'edit_project' ? 'active' : ''%>" onclick="project_setting(1);">信息</a></li>
|
||||
<!--<li id="pro_st_tb_5" class="pro_st_normaltab" onclick="project_setting(5);">问题类别</li>-->
|
||||
<li><a href="javascript:void(0)" id="pro_st_tb_6" class="<%= show_memu == 'manage_repository' ? 'active' : ''%>" onclick="project_setting(6);">版本库</a></li>
|
||||
<li><a href="javascript:void(0)" id="pro_st_tb_7" class="<%= show_memu == 'trainig_task' ? 'active' : ''%>" onclick="project_setting(7);">实训任务</a></li>
|
||||
<!--<li><a href="javascript:void(0)" id="pro_st_tb_7" class="<%#= show_memu == 'trainig_task' ? 'active' : ''%>" onclick="project_setting(7);">实训任务</a></li>-->
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -45,16 +45,16 @@
|
|||
<%= render :partial=>"shixuns/settings_repository" %>
|
||||
</div><!--tbc_06 end-->
|
||||
|
||||
<div class="<%= show_memu == 'training_task' ? 'pro_st_dis' : 'pro_st_undis'%>" id="pro_st_tbc_07">
|
||||
<%= render :partial=>"shixuns/settings_challenges" %>
|
||||
</div><!--tbc_06 end-->
|
||||
<!--<div class="<%#= show_memu == 'training_task' ? 'pro_st_dis' : 'pro_st_undis'%>" id="pro_st_tbc_07">-->
|
||||
<!--<%#= render :partial=>"shixuns/settings_challenges" %>-->
|
||||
<!--</div><!–tbc_06 end–>-->
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<script type="text/javascript">
|
||||
function g(o){return document.getElementById(o);}
|
||||
function HoverLi(n){
|
||||
for(var i=1;i<=3;i++){
|
||||
for(var i=1;i<=2;i++){
|
||||
g('tab_nav_'+i).className='tab_nomal';
|
||||
g('tab_con_'+i).className='undis';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ RedmineApp::Application.routes.draw do
|
|||
get 'shixun_job_create'
|
||||
get 'shixun_job_update'
|
||||
get 'rep_tree_changes', :action => 'rep_tree_changes', :as => 'rep_tree_changes'
|
||||
match 'publish', :via => [:get, :post]
|
||||
end
|
||||
collection do
|
||||
end
|
||||
|
|
@ -1314,7 +1315,7 @@ RedmineApp::Application.routes.draw do
|
|||
post 'attachments/upload_attachment_version',:to=>'attachments#upload_attachment_version',:as=>'upload_attachment_version'
|
||||
get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
|
||||
get 'attachments/download_history/:id/:filename', :to => 'attachments#download_history', :id => /\d+/, :filename => /.*/, :as => 'download_history_attachment'
|
||||
get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
|
||||
get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/, :as => 'download_attachment'
|
||||
get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
|
||||
get 'attachments/autocomplete'
|
||||
get 'attachments/update_attachment_publish_time/:id',:to=>'attachments#update_attachment_publish_time',:as=>'update_attachment_publish_time'
|
||||
|
|
@ -1581,6 +1582,7 @@ RedmineApp::Application.routes.draw do
|
|||
match 'sys/fetch_changesets', :via => :get
|
||||
|
||||
match 'uploads', :to => 'attachments#upload', :via => :post
|
||||
match 'upload_with_markdown', :to => 'attachments#upload_with_markdown', :via => :post
|
||||
# Added by Tao
|
||||
match 'upload_avatar', :to => 'avatar#upload', :via => :post
|
||||
match 'delete_avatar', :to => 'avatar#delete_image',:via => :post
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
class AddEvaluationWayToChallenge < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :challenges, :evaluation_way, :integer, :default => 0
|
||||
add_column :challenges, :difficulty, :integer, :default => 1
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddScriptToChallenge < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :challenges, :pipeline_script, :text
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddActualOutputToTestSets < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :test_sets, :actual_output, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class AddHiddenToTestSets < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :test_sets, :is_public, :boolean, :default => true
|
||||
add_column :test_sets, :result, :boolean, :default => true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddOpenTimeToGame < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :games, :open_time, :datetime
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
class UpdateOpenTimeToGame < ActiveRecord::Migration
|
||||
def up
|
||||
Game.all.each do |game|
|
||||
game.update_attribute(:open_time, game.created_at)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddPositionToTestSets < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :test_sets, :position, :integer
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
*.txt text
|
||||
*.js text
|
||||
*.html text
|
||||
*.md text
|
||||
*.json text
|
||||
*.yml text
|
||||
*.css text
|
||||
*.svg text
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/node_modules
|
||||
/demo
|
||||
/doc
|
||||
/test
|
||||
/test*.html
|
||||
/index.html
|
||||
/mode/*/*test.js
|
||||
/mode/*/*.html
|
||||
/mode/index.html
|
||||
.*
|
||||
bin
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- stable
|
||||
sudo: false
|
||||
|
|
@ -0,0 +1,639 @@
|
|||
List of CodeMirror contributors. Updated before every release.
|
||||
|
||||
4r2r
|
||||
Aaron Brooks
|
||||
Abdelouahab
|
||||
Abe Fettig
|
||||
Adam Ahmed
|
||||
Adam King
|
||||
adanlobato
|
||||
Adán Lobato
|
||||
Adrian Aichner
|
||||
Adrian Heine
|
||||
Adrien Bertrand
|
||||
aeroson
|
||||
Ahmad Amireh
|
||||
Ahmad M. Zawawi
|
||||
ahoward
|
||||
Akeksandr Motsjonov
|
||||
Alasdair Smith
|
||||
Alberto González Palomo
|
||||
Alberto Pose
|
||||
Albert Xing
|
||||
Alexander Pavlov
|
||||
Alexander Schepanovski
|
||||
Alexander Shvets
|
||||
Alexander Solovyov
|
||||
Alexandre Bique
|
||||
alexey-k
|
||||
Alex Piggott
|
||||
Aliaksei Chapyzhenka
|
||||
Allen Sarkisyan
|
||||
Ami Fischman
|
||||
Amin Shali
|
||||
Amin Ullah Khan
|
||||
amshali@google.com
|
||||
Amsul
|
||||
amuntean
|
||||
Amy
|
||||
Ananya Sen
|
||||
anaran
|
||||
AndersMad
|
||||
Anders Nawroth
|
||||
Anderson Mesquita
|
||||
Anders Wåglund
|
||||
Andrea G
|
||||
Andreas Reischuck
|
||||
Andres Taylor
|
||||
Andre von Houck
|
||||
Andrew Cheng
|
||||
Andrey Fedorov
|
||||
Andrey Klyuchnikov
|
||||
Andrey Lushnikov
|
||||
Andrey Shchekin
|
||||
Andy Joslin
|
||||
Andy Kimball
|
||||
Andy Li
|
||||
Angelo
|
||||
angelozerr
|
||||
angelo.zerr@gmail.com
|
||||
Ankit
|
||||
Ankit Ahuja
|
||||
Ansel Santosa
|
||||
Anthony Dugois
|
||||
anthonygego
|
||||
Anthony Gégo
|
||||
Anthony Grimes
|
||||
Anton Kovalyov
|
||||
Apollo Zhu
|
||||
AQNOUCH Mohammed
|
||||
areos
|
||||
Arnab Bose
|
||||
Arthur Müller
|
||||
Arun Narasani
|
||||
as3boyan
|
||||
atelierbram
|
||||
AtomicPages LLC
|
||||
Atul Bhouraskar
|
||||
Aurelian Oancea
|
||||
Axel Lewenhaupt
|
||||
Barret Rennie
|
||||
Basarat Ali Syed
|
||||
Bastian Müller
|
||||
belhaj
|
||||
Bem Jones-Bey
|
||||
benbro
|
||||
Beni Cherniavsky-Paskin
|
||||
Benjamin DeCoste
|
||||
Ben Keen
|
||||
Ben Miller
|
||||
Ben Mosher
|
||||
Bernhard Sirlinger
|
||||
Bert Chang
|
||||
Bharad
|
||||
BigBlueHat
|
||||
Billy Moon
|
||||
binny
|
||||
B Krishna Chaitanya
|
||||
Blaine G
|
||||
blukat29
|
||||
boomyjee
|
||||
borawjm
|
||||
Brad Metcalf
|
||||
Brandon Frohs
|
||||
Brandon Wamboldt
|
||||
Brett Zamir
|
||||
Brian Grinstead
|
||||
Brian Sletten
|
||||
brrd
|
||||
Bruce Mitchener
|
||||
Bryan Massoth
|
||||
Caitlin Potter
|
||||
Calin Barbat
|
||||
callodacity
|
||||
Camilo Roca
|
||||
Chad Jolly
|
||||
Chandra Sekhar Pydi
|
||||
Charles Skelton
|
||||
Cheah Chu Yeow
|
||||
Chris Coyier
|
||||
Chris Ford
|
||||
Chris Granger
|
||||
Chris Houseknecht
|
||||
Chris Lohfink
|
||||
Chris Morgan
|
||||
Chris Smith
|
||||
Christian Oyarzun
|
||||
Christian Petrov
|
||||
Christopher Brown
|
||||
Christopher Kramer
|
||||
Christopher Mitchell
|
||||
Christopher Pfohl
|
||||
Chunliang Lyu
|
||||
ciaranj
|
||||
CodeAnimal
|
||||
coderaiser
|
||||
Cole R Lawrence
|
||||
ComFreek
|
||||
Curtis Gagliardi
|
||||
dagsta
|
||||
daines
|
||||
Dale Jung
|
||||
Dan Bentley
|
||||
Dan Heberden
|
||||
Daniel, Dao Quang Minh
|
||||
Daniele Di Sarli
|
||||
Daniel Faust
|
||||
Daniel Huigens
|
||||
Daniel Kesler
|
||||
Daniel KJ
|
||||
Daniel Neel
|
||||
Daniel Parnell
|
||||
Danny Yoo
|
||||
darealshinji
|
||||
Darius Roberts
|
||||
Dave Brondsema
|
||||
Dave Myers
|
||||
David Barnett
|
||||
David H. Bronke
|
||||
David Mignot
|
||||
David Pathakjee
|
||||
David Vázquez
|
||||
David Whittington
|
||||
deebugger
|
||||
Deep Thought
|
||||
Devin Abbott
|
||||
Devon Carew
|
||||
Dick Choi
|
||||
dignifiedquire
|
||||
Dimage Sapelkin
|
||||
Dmitry Kiselyov
|
||||
domagoj412
|
||||
Dominator008
|
||||
Domizio Demichelis
|
||||
Doug Wikle
|
||||
Drew Bratcher
|
||||
Drew Hintz
|
||||
Drew Khoury
|
||||
Drini Cami
|
||||
Dror BG
|
||||
duralog
|
||||
eborden
|
||||
edsharp
|
||||
ekhaled
|
||||
Elisée
|
||||
Emmanuel Schanzer
|
||||
Enam Mijbah Noor
|
||||
Eric Allam
|
||||
Erik Welander
|
||||
eustas
|
||||
Fabien Dubosson
|
||||
Fabien O'Carroll
|
||||
Fabio Zendhi Nagao
|
||||
Faiza Alsaied
|
||||
Fauntleroy
|
||||
fbuchinger
|
||||
feizhang365
|
||||
Felipe Lalanne
|
||||
Felix Raab
|
||||
ficristo
|
||||
Filip Noetzel
|
||||
Filip Stollár
|
||||
flack
|
||||
ForbesLindesay
|
||||
Forbes Lindesay
|
||||
Ford_Lawnmower
|
||||
Forrest Oliphant
|
||||
Frank Wiegand
|
||||
Gabriel Gheorghian
|
||||
Gabriel Horner
|
||||
Gabriel Nahmias
|
||||
galambalazs
|
||||
Gary Sheng
|
||||
Gautam Mehta
|
||||
Gavin Douglas
|
||||
gekkoe
|
||||
Geordie Hall
|
||||
geowarin
|
||||
Gerard Braad
|
||||
Gergely Hegykozi
|
||||
Giovanni Calò
|
||||
Glebov Boris
|
||||
Glenn Jorde
|
||||
Glenn Ruehle
|
||||
Golevka
|
||||
Google Inc.
|
||||
Gordon Smith
|
||||
Grant Skinner
|
||||
greengiant
|
||||
Gregory Koberger
|
||||
Grzegorz Mazur
|
||||
Guillaume Massé
|
||||
Guillaume Massé
|
||||
guraga
|
||||
Gustavo Rodrigues
|
||||
Hakan Tunc
|
||||
Hans Engel
|
||||
Hardest
|
||||
Harshvardhan Gupta
|
||||
Hasan Karahan
|
||||
Hector Oswaldo Caballero
|
||||
Hendrik Wallbaum
|
||||
Herculano Campos
|
||||
Hiroyuki Makino
|
||||
hitsthings
|
||||
Hocdoc
|
||||
Hugues Malphettes
|
||||
Ian Beck
|
||||
Ian Dickinson
|
||||
Ian Wehrman
|
||||
Ian Wetherbee
|
||||
Ice White
|
||||
ICHIKAWA, Yuji
|
||||
idleberg
|
||||
ilvalle
|
||||
Ingo Richter
|
||||
Irakli Gozalishvili
|
||||
Ivan Kurnosov
|
||||
Ivoah
|
||||
Jacob Lee
|
||||
Jake Peyser
|
||||
Jakob Miland
|
||||
Jakub Vrana
|
||||
Jakub Vrána
|
||||
James Campos
|
||||
James Howard
|
||||
James Thorne
|
||||
Jamie Hill
|
||||
Jan Jongboom
|
||||
jankeromnes
|
||||
Jan Keromnes
|
||||
Jan Odvarko
|
||||
Jan Schär
|
||||
Jan T. Sott
|
||||
Jared Dean
|
||||
Jared Forsyth
|
||||
Jared Jacobs
|
||||
Jason
|
||||
Jason Barnabe
|
||||
Jason Grout
|
||||
Jason Johnston
|
||||
Jason San Jose
|
||||
Jason Siefken
|
||||
Jaydeep Solanki
|
||||
Jean Boussier
|
||||
Jeff Blaisdell
|
||||
Jeff Jenkins
|
||||
jeffkenton
|
||||
Jeff Pickhardt
|
||||
jem (graphite)
|
||||
Jeremy Parmenter
|
||||
Jim
|
||||
Jim Avery
|
||||
JobJob
|
||||
jochenberger
|
||||
Jochen Berger
|
||||
Joel Einbinder
|
||||
joelpinheiro
|
||||
Johan Ask
|
||||
John Connor
|
||||
John-David Dalton
|
||||
John Engler
|
||||
John Lees-Miller
|
||||
John Snelson
|
||||
John Van Der Loo
|
||||
Jon Ander Peñalba
|
||||
Jonas Döbertin
|
||||
Jonathan Malmaud
|
||||
Jon Gacnik
|
||||
jongalloway
|
||||
Jon Malmaud
|
||||
Jon Sangster
|
||||
Joost-Wim Boekesteijn
|
||||
Joseph Pecoraro
|
||||
Josh Barnes
|
||||
Josh Cohen
|
||||
Josh Soref
|
||||
Joshua Newman
|
||||
Josh Watzman
|
||||
jots
|
||||
jsoojeon
|
||||
ju1ius
|
||||
Juan Benavides Romero
|
||||
Jucovschi Constantin
|
||||
Juho Vuori
|
||||
Julien Rebetez
|
||||
Justin Andresen
|
||||
Justin Hileman
|
||||
jwallers@gmail.com
|
||||
kaniga
|
||||
karevn
|
||||
Kayur Patel
|
||||
Kazuhito Hokamura
|
||||
Ken Newman
|
||||
ken restivo
|
||||
Ken Rockot
|
||||
Kevin Earls
|
||||
Kevin Muret
|
||||
Kevin Sawicki
|
||||
Kevin Ushey
|
||||
Klaus Silveira
|
||||
Koh Zi Han, Cliff
|
||||
komakino
|
||||
Konstantin Lopuhin
|
||||
koops
|
||||
Kris Ciccarello
|
||||
ks-ifware
|
||||
kubelsmieci
|
||||
KwanEsq
|
||||
Kyle Kelley
|
||||
Lanfei
|
||||
Lanny
|
||||
Laszlo Vidacs
|
||||
leaf corcoran
|
||||
Leonid Khachaturov
|
||||
Leon Sorokin
|
||||
Leonya Khachaturov
|
||||
Liam Newman
|
||||
Libo Cannici
|
||||
LloydMilligan
|
||||
LM
|
||||
lochel
|
||||
Lorenzo Stoakes
|
||||
Luca Fabbri
|
||||
Luciano Longo
|
||||
Lu Fangjian
|
||||
Luke Browning
|
||||
Luke Granger-Brown
|
||||
Luke Stagner
|
||||
lynschinzer
|
||||
M1cha
|
||||
Madhura Jayaratne
|
||||
Maksim Lin
|
||||
Maksym Taran
|
||||
Malay Majithia
|
||||
Manideep
|
||||
Manuel Rego Casasnovas
|
||||
Marat Dreizin
|
||||
Marcel Gerber
|
||||
Marcelo Camargo
|
||||
Marco Aurélio
|
||||
Marco Munizaga
|
||||
Marcus Bointon
|
||||
Marek Rudnicki
|
||||
Marijn Haverbeke
|
||||
Mário Gonçalves
|
||||
Mario Pietsch
|
||||
Mark Anderson
|
||||
Mark Lentczner
|
||||
Marko Bonaci
|
||||
Mark Peace
|
||||
Markus Bordihn
|
||||
Martin Balek
|
||||
Martín Gaitán
|
||||
Martin Hasoň
|
||||
Martin Hunt
|
||||
Martin Laine
|
||||
Martin Zagora
|
||||
Mason Malone
|
||||
Mateusz Paprocki
|
||||
Mathias Bynens
|
||||
mats cronqvist
|
||||
Matt Gaide
|
||||
Matthew Bauer
|
||||
Matthew Beale
|
||||
matthewhayes
|
||||
Matthew Rathbone
|
||||
Matthias Bussonnier
|
||||
Matthias BUSSONNIER
|
||||
Matt McDonald
|
||||
Matt Pass
|
||||
Matt Sacks
|
||||
mauricio
|
||||
Maximilian Hils
|
||||
Maxim Kraev
|
||||
Max Kirsch
|
||||
Max Schaefer
|
||||
Max Xiantu
|
||||
mbarkhau
|
||||
McBrainy
|
||||
melpon
|
||||
Metatheos
|
||||
Micah Dubinko
|
||||
Michael
|
||||
Michael Goderbauer
|
||||
Michael Grey
|
||||
Michael Kaminsky
|
||||
Michael Lehenbauer
|
||||
Michael Zhou
|
||||
Michal Dorner
|
||||
Mighty Guava
|
||||
Miguel Castillo
|
||||
mihailik
|
||||
Mike
|
||||
Mike Brevoort
|
||||
Mike Diaz
|
||||
Mike Ivanov
|
||||
Mike Kadin
|
||||
Mike Kobit
|
||||
MinRK
|
||||
Miraculix87
|
||||
misfo
|
||||
mkaminsky11
|
||||
mloginov
|
||||
Moritz Schwörer
|
||||
mps
|
||||
ms
|
||||
mtaran-google
|
||||
Mu-An Chiou
|
||||
Narciso Jaramillo
|
||||
Nathan Williams
|
||||
ndr
|
||||
nerbert
|
||||
nextrevision
|
||||
ngn
|
||||
nguillaumin
|
||||
Ng Zhi An
|
||||
Nicholas Bollweg
|
||||
Nicholas Bollweg (Nick)
|
||||
Nick Kreeger
|
||||
Nick Small
|
||||
Nicolò Ribaudo
|
||||
Niels van Groningen
|
||||
nightwing
|
||||
Nikita Beloglazov
|
||||
Nikita Vasilyev
|
||||
Nikolay Kostov
|
||||
nilp0inter
|
||||
Nisarg Jhaveri
|
||||
nlwillia
|
||||
noragrossman
|
||||
Norman Rzepka
|
||||
Oreoluwa Onatemowo
|
||||
Oskar Segersvärd
|
||||
pablo
|
||||
pabloferz
|
||||
Page
|
||||
Panupong Pasupat
|
||||
paris
|
||||
Paris
|
||||
Paris Kasidiaris
|
||||
Patil Arpith
|
||||
Patrick Stoica
|
||||
Patrick Strawderman
|
||||
Paul Garvin
|
||||
Paul Ivanov
|
||||
Paul Masson
|
||||
Pavel
|
||||
Pavel Feldman
|
||||
Pavel Petržela
|
||||
Pavel Strashkin
|
||||
Paweł Bartkiewicz
|
||||
peteguhl
|
||||
peter
|
||||
Peter Flynn
|
||||
peterkroon
|
||||
Peter Kroon
|
||||
Philipp A
|
||||
Philip Stadermann
|
||||
Pierre Gerold
|
||||
Piët Delport
|
||||
Pontus Melke
|
||||
prasanthj
|
||||
Prasanth J
|
||||
Prayag Verma
|
||||
Radek Piórkowski
|
||||
Rahul
|
||||
Rahul Anand
|
||||
ramwin1
|
||||
Randall Mason
|
||||
Randy Burden
|
||||
Randy Edmunds
|
||||
Rasmus Erik Voel Jensen
|
||||
ray ratchup
|
||||
Ray Ratchup
|
||||
Remi Nyborg
|
||||
Richard Denton
|
||||
Richard van der Meer
|
||||
Richard Z.H. Wang
|
||||
Rishi Goomar
|
||||
Robert Crossfield
|
||||
Roberto Abdelkader Martínez Pérez
|
||||
robertop23
|
||||
Robert Plummer
|
||||
Rrandom
|
||||
Rrrandom
|
||||
Ruslan Osmanov
|
||||
Ryan Petrello
|
||||
Ryan Prior
|
||||
sabaca
|
||||
Sam Lee
|
||||
Samuel Ainsworth
|
||||
Sam Wilson
|
||||
sandeepshetty
|
||||
Sander AKA Redsandro
|
||||
Sander Verweij
|
||||
santec
|
||||
Sascha Peilicke
|
||||
satamas
|
||||
satchmorun
|
||||
sathyamoorthi
|
||||
S. Chris Colbert
|
||||
SCLINIC\jdecker
|
||||
Scott Aikin
|
||||
Scott Goodhew
|
||||
Sebastian Zaha
|
||||
Sergey Goder
|
||||
Sergey Tselovalnikov
|
||||
Se-Won Kim
|
||||
shaund
|
||||
shaun gilchrist
|
||||
Shawn A
|
||||
Shea Bunge
|
||||
sheopory
|
||||
Shiv Deepak
|
||||
Shmuel Englard
|
||||
Shubham Jain
|
||||
Siamak Mokhtari
|
||||
silverwind
|
||||
sinkuu
|
||||
snasa
|
||||
soliton4
|
||||
sonson
|
||||
spastorelli
|
||||
srajanpaliwal
|
||||
Stanislav Oaserele
|
||||
Stas Kobzar
|
||||
Stefan Borsje
|
||||
Steffen Beyer
|
||||
Steffen Bruchmann
|
||||
Stephen Lavelle
|
||||
Steve Champagne
|
||||
Steve Hoover
|
||||
Steve O'Hara
|
||||
stoskov
|
||||
Stu Kennedy
|
||||
Sungho Kim
|
||||
sverweij
|
||||
Taha Jahangir
|
||||
takamori
|
||||
Tako Schotanus
|
||||
Takuji Shimokawa
|
||||
Tarmil
|
||||
TDaglis
|
||||
tel
|
||||
tfjgeorge
|
||||
Thaddee Tyl
|
||||
thanasis
|
||||
TheHowl
|
||||
themrmax
|
||||
think
|
||||
Thomas Dvornik
|
||||
Thomas Kluyver
|
||||
Thomas Schmid
|
||||
Tim Alby
|
||||
Tim Baumann
|
||||
Timothy Farrell
|
||||
Timothy Gu
|
||||
Timothy Hatcher
|
||||
TobiasBg
|
||||
Todd Berman
|
||||
Tomas-A
|
||||
Tomas Varaneckas
|
||||
Tom Erik Støwer
|
||||
Tom Klancer
|
||||
Tom MacWright
|
||||
Tony Jian
|
||||
Travis Heppe
|
||||
Triangle717
|
||||
Tristan Tarrant
|
||||
TSUYUSATO Kitsune
|
||||
twifkak
|
||||
VapidWorx
|
||||
Vestimir Markov
|
||||
vf
|
||||
Victor Bocharsky
|
||||
Vincent Woo
|
||||
Volker Mische
|
||||
Weiyan Shao
|
||||
wenli
|
||||
Wes Cossick
|
||||
Wesley Wiser
|
||||
Will Binns-Smith
|
||||
Will Dean
|
||||
William Jamieson
|
||||
William Stein
|
||||
Willy
|
||||
Wojtek Ptak
|
||||
Wu Cheng-Han
|
||||
Xavier Mendez
|
||||
Yassin N. Hassan
|
||||
YNH Webdev
|
||||
Yunchi Luo
|
||||
Yuvi Panda
|
||||
Zac Anger
|
||||
Zachary Dremann
|
||||
Zeno Rocha
|
||||
Zhang Hao
|
||||
zziuni
|
||||
魏鹏刚
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,88 @@
|
|||
# How to contribute
|
||||
|
||||
- [Getting help](#getting-help)
|
||||
- [Submitting bug reports](#submitting-bug-reports)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Getting help
|
||||
|
||||
Community discussion, questions, and informal bug reporting is done on the
|
||||
[discuss.CodeMirror forum](http://discuss.codemirror.net).
|
||||
|
||||
## Submitting bug reports
|
||||
|
||||
The preferred way to report bugs is to use the
|
||||
[GitHub issue tracker](http://github.com/codemirror/CodeMirror/issues). Before
|
||||
reporting a bug, read these pointers.
|
||||
|
||||
**Note:** The issue tracker is for *bugs*, not requests for help. Questions
|
||||
should be asked on the
|
||||
[discuss.CodeMirror forum](http://discuss.codemirror.net) instead.
|
||||
|
||||
### Reporting bugs effectively
|
||||
|
||||
- CodeMirror is maintained by volunteers. They don't owe you anything, so be
|
||||
polite. Reports with an indignant or belligerent tone tend to be moved to the
|
||||
bottom of the pile.
|
||||
|
||||
- Include information about **the browser in which the problem occurred**. Even
|
||||
if you tested several browsers, and the problem occurred in all of them,
|
||||
mention this fact in the bug report. Also include browser version numbers and
|
||||
the operating system that you're on.
|
||||
|
||||
- Mention which release of CodeMirror you're using. Preferably, try also with
|
||||
the current development snapshot, to ensure the problem has not already been
|
||||
fixed.
|
||||
|
||||
- Mention very precisely what went wrong. "X is broken" is not a good bug
|
||||
report. What did you expect to happen? What happened instead? Describe the
|
||||
exact steps a maintainer has to take to make the problem occur. We can not
|
||||
fix something that we can not observe.
|
||||
|
||||
- If the problem can not be reproduced in any of the demos included in the
|
||||
CodeMirror distribution, please provide an HTML document that demonstrates
|
||||
the problem. The best way to do this is to go to
|
||||
[jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and
|
||||
include the resulting link in your bug report.
|
||||
|
||||
## Contributing code
|
||||
|
||||
- Make sure you have a [GitHub Account](https://github.com/signup/free)
|
||||
- Fork [CodeMirror](https://github.com/codemirror/CodeMirror/)
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||
- Make your changes
|
||||
- If your changes are easy to test or likely to regress, add tests.
|
||||
Tests for the core go into `test/test.js`, some modes have their own
|
||||
test suite under `mode/XXX/test.js`. Feel free to add new test
|
||||
suites to modes that don't have one yet (be sure to link the new
|
||||
tests into `test/index.html`).
|
||||
- Follow the general code style of the rest of the project (see
|
||||
below). Run `bin/lint` to verify that the linter is happy.
|
||||
- Make sure all tests pass. Visit `test/index.html` in your browser to
|
||||
run them.
|
||||
- Submit a pull request
|
||||
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||
Don't put more than one feature/fix in a single pull request.
|
||||
|
||||
By contributing code to CodeMirror you
|
||||
|
||||
- agree to license the contributed code under CodeMirror's [MIT
|
||||
license](http://codemirror.net/LICENSE).
|
||||
|
||||
- confirm that you have the right to contribute and license the code
|
||||
in question. (Either you hold all rights on the code, or the rights
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
|
||||
### Coding standards
|
||||
|
||||
- 2 spaces per indentation level, no tabs.
|
||||
|
||||
- Note that the linter (`bin/lint`) which is run after each commit
|
||||
complains about unused variables and functions. Prefix their names
|
||||
with an underscore to muffle it.
|
||||
|
||||
- CodeMirror does *not* follow JSHint or JSLint prescribed style.
|
||||
Patches that try to 'fix' code to pass one of these linters will be
|
||||
unceremoniously discarded.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# CodeMirror
|
||||
[](https://travis-ci.org/codemirror/CodeMirror)
|
||||
[](https://www.npmjs.org/package/codemirror)
|
||||
[](https://gitter.im/codemirror/CodeMirror)
|
||||
[Funding status: ](https://marijnhaverbeke.nl/fund/)
|
||||
|
||||
CodeMirror is a versatile text editor implemented in JavaScript for
|
||||
the browser. It is specialized for editing code, and comes with over
|
||||
100 language modes and various addons that implement more advanced
|
||||
editing functionality.
|
||||
|
||||
A rich programming API and a CSS theming system are available for
|
||||
customizing CodeMirror to fit your application, and extending it with
|
||||
new functionality.
|
||||
|
||||
You can find more information (and the
|
||||
[manual](http://codemirror.net/doc/manual.html)) on the [project
|
||||
page](http://codemirror.net). For questions and discussion, use the
|
||||
[discussion forum](https://discuss.codemirror.net/).
|
||||
|
||||
See
|
||||
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
|
||||
for contributing guidelines.
|
||||
|
||||
The CodeMirror community aims to be welcoming to everybody. We use the
|
||||
[Contributor Covenant
|
||||
(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
|
||||
conduct.
|
||||
|
||||
### Quickstart
|
||||
|
||||
To build the project, make sure you have Node.js installed (at least version 6)
|
||||
and then `npm install`. To run, just open `index.html` in your
|
||||
browser (you don't need to run a webserver). Run the tests with `npm test`.
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var noOptions = {};
|
||||
var nonWS = /[^\s\u00a0]/;
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function firstNonWS(str) {
|
||||
var found = str.search(nonWS);
|
||||
return found == -1 ? 0 : found;
|
||||
}
|
||||
|
||||
CodeMirror.commands.toggleComment = function(cm) {
|
||||
cm.toggleComment();
|
||||
};
|
||||
|
||||
CodeMirror.defineExtension("toggleComment", function(options) {
|
||||
if (!options) options = noOptions;
|
||||
var cm = this;
|
||||
var minLine = Infinity, ranges = this.listSelections(), mode = null;
|
||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||
var from = ranges[i].from(), to = ranges[i].to();
|
||||
if (from.line >= minLine) continue;
|
||||
if (to.line >= minLine) to = Pos(minLine, 0);
|
||||
minLine = from.line;
|
||||
if (mode == null) {
|
||||
if (cm.uncomment(from, to, options)) mode = "un";
|
||||
else { cm.lineComment(from, to, options); mode = "line"; }
|
||||
} else if (mode == "un") {
|
||||
cm.uncomment(from, to, options);
|
||||
} else {
|
||||
cm.lineComment(from, to, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Rough heuristic to try and detect lines that are part of multi-line string
|
||||
function probablyInsideString(cm, pos, line) {
|
||||
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
|
||||
}
|
||||
|
||||
function getMode(cm, pos) {
|
||||
var mode = cm.getMode()
|
||||
return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = getMode(self, from);
|
||||
var firstLine = self.getLine(from.line);
|
||||
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
|
||||
|
||||
var commentString = options.lineComment || mode.lineComment;
|
||||
if (!commentString) {
|
||||
if (options.blockCommentStart || mode.blockCommentStart) {
|
||||
options.fullLines = true;
|
||||
self.blockComment(from, to, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
|
||||
var pad = options.padding == null ? " " : options.padding;
|
||||
var blankLines = options.commentBlankLines || from.line == to.line;
|
||||
|
||||
self.operation(function() {
|
||||
if (options.indent) {
|
||||
var baseString = null;
|
||||
for (var i = from.line; i < end; ++i) {
|
||||
var line = self.getLine(i);
|
||||
var whitespace = line.slice(0, firstNonWS(line));
|
||||
if (baseString == null || baseString.length > whitespace.length) {
|
||||
baseString = whitespace;
|
||||
}
|
||||
}
|
||||
for (var i = from.line; i < end; ++i) {
|
||||
var line = self.getLine(i), cut = baseString.length;
|
||||
if (!blankLines && !nonWS.test(line)) continue;
|
||||
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
|
||||
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
|
||||
}
|
||||
} else {
|
||||
for (var i = from.line; i < end; ++i) {
|
||||
if (blankLines || nonWS.test(self.getLine(i)))
|
||||
self.replaceRange(commentString + pad, Pos(i, 0));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("blockComment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = getMode(self, from);
|
||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) {
|
||||
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
|
||||
self.lineComment(from, to, options);
|
||||
return;
|
||||
}
|
||||
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
|
||||
|
||||
var end = Math.min(to.line, self.lastLine());
|
||||
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
|
||||
|
||||
var pad = options.padding == null ? " " : options.padding;
|
||||
if (from.line > end) return;
|
||||
|
||||
self.operation(function() {
|
||||
if (options.fullLines != false) {
|
||||
var lastLineHasText = nonWS.test(self.getLine(end));
|
||||
self.replaceRange(pad + endString, Pos(end));
|
||||
self.replaceRange(startString + pad, Pos(from.line, 0));
|
||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
|
||||
if (i != end || lastLineHasText)
|
||||
self.replaceRange(lead + pad, Pos(i, 0));
|
||||
} else {
|
||||
self.replaceRange(endString, to);
|
||||
self.replaceRange(startString, from);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
||||
if (!options) options = noOptions;
|
||||
var self = this, mode = getMode(self, from);
|
||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
|
||||
|
||||
// Try finding line comments
|
||||
var lineString = options.lineComment || mode.lineComment, lines = [];
|
||||
var pad = options.padding == null ? " " : options.padding, didSomething;
|
||||
lineComment: {
|
||||
if (!lineString) break lineComment;
|
||||
for (var i = start; i <= end; ++i) {
|
||||
var line = self.getLine(i);
|
||||
var found = line.indexOf(lineString);
|
||||
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
|
||||
if (found == -1 && nonWS.test(line)) break lineComment;
|
||||
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
|
||||
lines.push(line);
|
||||
}
|
||||
self.operation(function() {
|
||||
for (var i = start; i <= end; ++i) {
|
||||
var line = lines[i - start];
|
||||
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
|
||||
if (pos < 0) continue;
|
||||
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
|
||||
didSomething = true;
|
||||
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
|
||||
}
|
||||
});
|
||||
if (didSomething) return true;
|
||||
}
|
||||
|
||||
// Try block comments
|
||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||
if (!startString || !endString) return false;
|
||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||
var startLine = self.getLine(start), open = startLine.indexOf(startString)
|
||||
if (open == -1) return false
|
||||
var endLine = end == start ? startLine : self.getLine(end)
|
||||
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
|
||||
if (close == -1 && start != end) {
|
||||
endLine = self.getLine(--end);
|
||||
close = endLine.indexOf(endString);
|
||||
}
|
||||
var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
|
||||
if (close == -1 ||
|
||||
!/comment/.test(self.getTokenTypeAt(insideStart)) ||
|
||||
!/comment/.test(self.getTokenTypeAt(insideEnd)) ||
|
||||
self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
|
||||
return false;
|
||||
|
||||
// Avoid killing block comments completely outside the selection.
|
||||
// Positions of the last startString before the start of the selection, and the first endString after it.
|
||||
var lastStart = startLine.lastIndexOf(startString, from.ch);
|
||||
var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
|
||||
if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
|
||||
// Positions of the first endString after the end of the selection, and the last startString before it.
|
||||
firstEnd = endLine.indexOf(endString, to.ch);
|
||||
var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
|
||||
lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
|
||||
if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
|
||||
|
||||
self.operation(function() {
|
||||
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
|
||||
Pos(end, close + endString.length));
|
||||
var openEnd = open + startString.length;
|
||||
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
|
||||
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
|
||||
if (lead) for (var i = start + 1; i <= end; ++i) {
|
||||
var line = self.getLine(i), found = line.indexOf(lead);
|
||||
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
|
||||
var foundEnd = found + lead.length;
|
||||
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
|
||||
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
var modes = ["clike", "css", "javascript"];
|
||||
|
||||
for (var i = 0; i < modes.length; ++i)
|
||||
CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
|
||||
|
||||
function continueComment(cm) {
|
||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
var ranges = cm.listSelections(), mode, inserts = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var pos = ranges[i].head, token = cm.getTokenAt(pos);
|
||||
if (token.type != "comment") return CodeMirror.Pass;
|
||||
var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode;
|
||||
if (!mode) mode = modeHere;
|
||||
else if (mode != modeHere) return CodeMirror.Pass;
|
||||
|
||||
var insert = null;
|
||||
if (mode.blockCommentStart && mode.blockCommentContinue) {
|
||||
var end = token.string.indexOf(mode.blockCommentEnd);
|
||||
var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
|
||||
if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) {
|
||||
// Comment ended, don't continue it
|
||||
} else if (token.string.indexOf(mode.blockCommentStart) == 0) {
|
||||
insert = full.slice(0, token.start);
|
||||
if (!/^\s*$/.test(insert)) {
|
||||
insert = "";
|
||||
for (var j = 0; j < token.start; ++j) insert += " ";
|
||||
}
|
||||
} else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
|
||||
found + mode.blockCommentContinue.length > token.start &&
|
||||
/^\s*$/.test(full.slice(0, found))) {
|
||||
insert = full.slice(0, found);
|
||||
}
|
||||
if (insert != null) insert += mode.blockCommentContinue;
|
||||
}
|
||||
if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
|
||||
var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
|
||||
if (found > -1) {
|
||||
insert = line.slice(0, found);
|
||||
if (/\S/.test(insert)) insert = null;
|
||||
else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
|
||||
}
|
||||
}
|
||||
if (insert == null) return CodeMirror.Pass;
|
||||
inserts[i] = "\n" + insert;
|
||||
}
|
||||
|
||||
cm.operation(function() {
|
||||
for (var i = ranges.length - 1; i >= 0; i--)
|
||||
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert");
|
||||
});
|
||||
}
|
||||
|
||||
function continueLineCommentEnabled(cm) {
|
||||
var opt = cm.getOption("continueComments");
|
||||
if (opt && typeof opt == "object")
|
||||
return opt.continueLineComment !== false;
|
||||
return true;
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
|
||||
if (prev && prev != CodeMirror.Init)
|
||||
cm.removeKeyMap("continueComment");
|
||||
if (val) {
|
||||
var key = "Enter";
|
||||
if (typeof val == "string")
|
||||
key = val;
|
||||
else if (typeof val == "object" && val.key)
|
||||
key = val.key;
|
||||
var map = {name: "continueComment"};
|
||||
map[key] = continueComment;
|
||||
cm.addKeyMap(map);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
.CodeMirror-dialog {
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
background: inherit;
|
||||
z-index: 15;
|
||||
padding: .1em .8em;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
border-bottom: 1px solid #eee;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 20em;
|
||||
color: inherit;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog button {
|
||||
font-size: 70%;
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
function dialogDiv(cm, template, bottom) {
|
||||
var wrap = cm.getWrapperElement();
|
||||
var dialog;
|
||||
dialog = wrap.appendChild(document.createElement("div"));
|
||||
if (bottom)
|
||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
||||
else
|
||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
||||
|
||||
if (typeof template == "string") {
|
||||
dialog.innerHTML = template;
|
||||
} else { // Assuming it's a detached DOM element.
|
||||
dialog.appendChild(template);
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
function closeNotification(cm, newVal) {
|
||||
if (cm.state.currentNotificationClose)
|
||||
cm.state.currentNotificationClose();
|
||||
cm.state.currentNotificationClose = newVal;
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
||||
if (!options) options = {};
|
||||
|
||||
closeNotification(this, null);
|
||||
|
||||
var dialog = dialogDiv(this, template, options.bottom);
|
||||
var closed = false, me = this;
|
||||
function close(newVal) {
|
||||
if (typeof newVal == 'string') {
|
||||
inp.value = newVal;
|
||||
} else {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
dialog.parentNode.removeChild(dialog);
|
||||
me.focus();
|
||||
|
||||
if (options.onClose) options.onClose(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||
if (inp) {
|
||||
inp.focus();
|
||||
|
||||
if (options.value) {
|
||||
inp.value = options.value;
|
||||
if (options.selectValueOnOpen !== false) {
|
||||
inp.select();
|
||||
}
|
||||
}
|
||||
|
||||
if (options.onInput)
|
||||
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
||||
if (options.onKeyUp)
|
||||
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
||||
|
||||
CodeMirror.on(inp, "keydown", function(e) {
|
||||
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
||||
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
|
||||
inp.blur();
|
||||
CodeMirror.e_stop(e);
|
||||
close();
|
||||
}
|
||||
if (e.keyCode == 13) callback(inp.value, e);
|
||||
});
|
||||
|
||||
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
||||
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
||||
CodeMirror.on(button, "click", function() {
|
||||
close();
|
||||
me.focus();
|
||||
});
|
||||
|
||||
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
|
||||
|
||||
button.focus();
|
||||
}
|
||||
return close;
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
||||
closeNotification(this, null);
|
||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||
var buttons = dialog.getElementsByTagName("button");
|
||||
var closed = false, me = this, blurring = 1;
|
||||
function close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
dialog.parentNode.removeChild(dialog);
|
||||
me.focus();
|
||||
}
|
||||
buttons[0].focus();
|
||||
for (var i = 0; i < buttons.length; ++i) {
|
||||
var b = buttons[i];
|
||||
(function(callback) {
|
||||
CodeMirror.on(b, "click", function(e) {
|
||||
CodeMirror.e_preventDefault(e);
|
||||
close();
|
||||
if (callback) callback(me);
|
||||
});
|
||||
})(callbacks[i]);
|
||||
CodeMirror.on(b, "blur", function() {
|
||||
--blurring;
|
||||
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
||||
});
|
||||
CodeMirror.on(b, "focus", function() { ++blurring; });
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* openNotification
|
||||
* Opens a notification, that can be closed with an optional timer
|
||||
* (default 5000ms timer) and always closes on click.
|
||||
*
|
||||
* If a notification is opened while another is opened, it will close the
|
||||
* currently opened one and open the new one immediately.
|
||||
*/
|
||||
CodeMirror.defineExtension("openNotification", function(template, options) {
|
||||
closeNotification(this, close);
|
||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||
var closed = false, doneTimer;
|
||||
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
||||
|
||||
function close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
clearTimeout(doneTimer);
|
||||
dialog.parentNode.removeChild(dialog);
|
||||
}
|
||||
|
||||
CodeMirror.on(dialog, 'click', function(e) {
|
||||
CodeMirror.e_preventDefault(e);
|
||||
close();
|
||||
});
|
||||
|
||||
if (duration)
|
||||
doneTimer = setTimeout(close, duration);
|
||||
|
||||
return close;
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"))
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod)
|
||||
else // Plain browser env
|
||||
mod(CodeMirror)
|
||||
})(function(CodeMirror) {
|
||||
"use strict"
|
||||
|
||||
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
|
||||
if (cm.state.autoRefresh) {
|
||||
stopListening(cm, cm.state.autoRefresh)
|
||||
cm.state.autoRefresh = null
|
||||
}
|
||||
if (val && cm.display.wrapper.offsetHeight == 0)
|
||||
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
|
||||
})
|
||||
|
||||
function startListening(cm, state) {
|
||||
function check() {
|
||||
if (cm.display.wrapper.offsetHeight) {
|
||||
stopListening(cm, state)
|
||||
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
|
||||
cm.refresh()
|
||||
} else {
|
||||
state.timeout = setTimeout(check, state.delay)
|
||||
}
|
||||
}
|
||||
state.timeout = setTimeout(check, state.delay)
|
||||
state.hurry = function() {
|
||||
clearTimeout(state.timeout)
|
||||
state.timeout = setTimeout(check, 50)
|
||||
}
|
||||
CodeMirror.on(window, "mouseup", state.hurry)
|
||||
CodeMirror.on(window, "keyup", state.hurry)
|
||||
}
|
||||
|
||||
function stopListening(_cm, state) {
|
||||
clearTimeout(state.timeout)
|
||||
CodeMirror.off(window, "mouseup", state.hurry)
|
||||
CodeMirror.off(window, "keyup", state.hurry)
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
.CodeMirror-fullscreen {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
height: auto;
|
||||
z-index: 9;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
|
||||
if (old == CodeMirror.Init) old = false;
|
||||
if (!old == !val) return;
|
||||
if (val) setFullscreen(cm);
|
||||
else setNormal(cm);
|
||||
});
|
||||
|
||||
function setFullscreen(cm) {
|
||||
var wrap = cm.getWrapperElement();
|
||||
cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
|
||||
width: wrap.style.width, height: wrap.style.height};
|
||||
wrap.style.width = "";
|
||||
wrap.style.height = "auto";
|
||||
wrap.className += " CodeMirror-fullscreen";
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
cm.refresh();
|
||||
}
|
||||
|
||||
function setNormal(cm) {
|
||||
var wrap = cm.getWrapperElement();
|
||||
wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
|
||||
document.documentElement.style.overflow = "";
|
||||
var info = cm.state.fullScreenRestore;
|
||||
wrap.style.width = info.width; wrap.style.height = info.height;
|
||||
window.scrollTo(info.scrollLeft, info.scrollTop);
|
||||
cm.refresh();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
CodeMirror.defineExtension("addPanel", function(node, options) {
|
||||
options = options || {};
|
||||
|
||||
if (!this.state.panels) initPanels(this);
|
||||
|
||||
var info = this.state.panels;
|
||||
var wrapper = info.wrapper;
|
||||
var cmWrapper = this.getWrapperElement();
|
||||
|
||||
if (options.after instanceof Panel && !options.after.cleared) {
|
||||
wrapper.insertBefore(node, options.before.node.nextSibling);
|
||||
} else if (options.before instanceof Panel && !options.before.cleared) {
|
||||
wrapper.insertBefore(node, options.before.node);
|
||||
} else if (options.replace instanceof Panel && !options.replace.cleared) {
|
||||
wrapper.insertBefore(node, options.replace.node);
|
||||
options.replace.clear();
|
||||
} else if (options.position == "bottom") {
|
||||
wrapper.appendChild(node);
|
||||
} else if (options.position == "before-bottom") {
|
||||
wrapper.insertBefore(node, cmWrapper.nextSibling);
|
||||
} else if (options.position == "after-top") {
|
||||
wrapper.insertBefore(node, cmWrapper);
|
||||
} else {
|
||||
wrapper.insertBefore(node, wrapper.firstChild);
|
||||
}
|
||||
|
||||
var height = (options && options.height) || node.offsetHeight;
|
||||
this._setSize(null, info.heightLeft -= height);
|
||||
info.panels++;
|
||||
if (options.stable && isAtTop(this, node))
|
||||
this.scrollTo(null, this.getScrollInfo().top + height)
|
||||
|
||||
return new Panel(this, node, options, height);
|
||||
});
|
||||
|
||||
function Panel(cm, node, options, height) {
|
||||
this.cm = cm;
|
||||
this.node = node;
|
||||
this.options = options;
|
||||
this.height = height;
|
||||
this.cleared = false;
|
||||
}
|
||||
|
||||
Panel.prototype.clear = function() {
|
||||
if (this.cleared) return;
|
||||
this.cleared = true;
|
||||
var info = this.cm.state.panels;
|
||||
this.cm._setSize(null, info.heightLeft += this.height);
|
||||
if (this.options.stable && isAtTop(this.cm, this.node))
|
||||
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
|
||||
info.wrapper.removeChild(this.node);
|
||||
if (--info.panels == 0) removePanels(this.cm);
|
||||
};
|
||||
|
||||
Panel.prototype.changed = function(height) {
|
||||
var newHeight = height == null ? this.node.offsetHeight : height;
|
||||
var info = this.cm.state.panels;
|
||||
this.cm._setSize(null, info.height += (newHeight - this.height));
|
||||
this.height = newHeight;
|
||||
};
|
||||
|
||||
function initPanels(cm) {
|
||||
var wrap = cm.getWrapperElement();
|
||||
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
|
||||
var height = parseInt(style.height);
|
||||
var info = cm.state.panels = {
|
||||
setHeight: wrap.style.height,
|
||||
heightLeft: height,
|
||||
panels: 0,
|
||||
wrapper: document.createElement("div")
|
||||
};
|
||||
wrap.parentNode.insertBefore(info.wrapper, wrap);
|
||||
var hasFocus = cm.hasFocus();
|
||||
info.wrapper.appendChild(wrap);
|
||||
if (hasFocus) cm.focus();
|
||||
|
||||
cm._setSize = cm.setSize;
|
||||
if (height != null) cm.setSize = function(width, newHeight) {
|
||||
if (newHeight == null) return this._setSize(width, newHeight);
|
||||
info.setHeight = newHeight;
|
||||
if (typeof newHeight != "number") {
|
||||
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
|
||||
if (px) {
|
||||
newHeight = Number(px[1]);
|
||||
} else {
|
||||
info.wrapper.style.height = newHeight;
|
||||
newHeight = info.wrapper.offsetHeight;
|
||||
info.wrapper.style.height = "";
|
||||
}
|
||||
}
|
||||
cm._setSize(width, info.heightLeft += (newHeight - height));
|
||||
height = newHeight;
|
||||
};
|
||||
}
|
||||
|
||||
function removePanels(cm) {
|
||||
var info = cm.state.panels;
|
||||
cm.state.panels = null;
|
||||
|
||||
var wrap = cm.getWrapperElement();
|
||||
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
|
||||
wrap.style.height = info.setHeight;
|
||||
cm.setSize = cm._setSize;
|
||||
cm.setSize();
|
||||
}
|
||||
|
||||
function isAtTop(cm, dom) {
|
||||
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
|
||||
if (sibling == cm.getWrapperElement()) return true
|
||||
return false
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
|
||||
var prev = old && old != CodeMirror.Init;
|
||||
if (val && !prev) {
|
||||
cm.on("blur", onBlur);
|
||||
cm.on("change", onChange);
|
||||
cm.on("swapDoc", onChange);
|
||||
onChange(cm);
|
||||
} else if (!val && prev) {
|
||||
cm.off("blur", onBlur);
|
||||
cm.off("change", onChange);
|
||||
cm.off("swapDoc", onChange);
|
||||
clearPlaceholder(cm);
|
||||
var wrapper = cm.getWrapperElement();
|
||||
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
|
||||
}
|
||||
|
||||
if (val && !cm.hasFocus()) onBlur(cm);
|
||||
});
|
||||
|
||||
function clearPlaceholder(cm) {
|
||||
if (cm.state.placeholder) {
|
||||
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
|
||||
cm.state.placeholder = null;
|
||||
}
|
||||
}
|
||||
function setPlaceholder(cm) {
|
||||
clearPlaceholder(cm);
|
||||
var elt = cm.state.placeholder = document.createElement("pre");
|
||||
elt.style.cssText = "height: 0; overflow: visible";
|
||||
elt.className = "CodeMirror-placeholder";
|
||||
var placeHolder = cm.getOption("placeholder")
|
||||
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
|
||||
elt.appendChild(placeHolder)
|
||||
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
|
||||
}
|
||||
|
||||
function onBlur(cm) {
|
||||
if (isEmpty(cm)) setPlaceholder(cm);
|
||||
}
|
||||
function onChange(cm) {
|
||||
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
|
||||
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
|
||||
|
||||
if (empty) setPlaceholder(cm);
|
||||
else clearPlaceholder(cm);
|
||||
}
|
||||
|
||||
function isEmpty(cm) {
|
||||
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("rulers", false, function(cm, val) {
|
||||
if (cm.state.rulerDiv) {
|
||||
cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
|
||||
cm.state.rulerDiv = null
|
||||
cm.off("refresh", drawRulers)
|
||||
}
|
||||
if (val && val.length) {
|
||||
cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
|
||||
cm.state.rulerDiv.className = "CodeMirror-rulers"
|
||||
drawRulers(cm)
|
||||
cm.on("refresh", drawRulers)
|
||||
}
|
||||
});
|
||||
|
||||
function drawRulers(cm) {
|
||||
cm.state.rulerDiv.textContent = ""
|
||||
var val = cm.getOption("rulers");
|
||||
var cw = cm.defaultCharWidth();
|
||||
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
|
||||
cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
var elt = document.createElement("div");
|
||||
elt.className = "CodeMirror-ruler";
|
||||
var col, conf = val[i];
|
||||
if (typeof conf == "number") {
|
||||
col = conf;
|
||||
} else {
|
||||
col = conf.column;
|
||||
if (conf.className) elt.className += " " + conf.className;
|
||||
if (conf.color) elt.style.borderColor = conf.color;
|
||||
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
|
||||
if (conf.width) elt.style.borderLeftWidth = conf.width;
|
||||
}
|
||||
elt.style.left = (left + col * cw) + "px";
|
||||
cm.state.rulerDiv.appendChild(elt)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
var defaults = {
|
||||
pairs: "()[]{}''\"\"",
|
||||
triples: "",
|
||||
explode: "[]{}"
|
||||
};
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.removeKeyMap(keyMap);
|
||||
cm.state.closeBrackets = null;
|
||||
}
|
||||
if (val) {
|
||||
cm.state.closeBrackets = val;
|
||||
cm.addKeyMap(keyMap);
|
||||
}
|
||||
});
|
||||
|
||||
function getOption(conf, name) {
|
||||
if (name == "pairs" && typeof conf == "string") return conf;
|
||||
if (typeof conf == "object" && conf[name] != null) return conf[name];
|
||||
return defaults[name];
|
||||
}
|
||||
|
||||
var bind = defaults.pairs + "`";
|
||||
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
|
||||
for (var i = 0; i < bind.length; i++)
|
||||
keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
|
||||
|
||||
function handler(ch) {
|
||||
return function(cm) { return handleChar(cm, ch); };
|
||||
}
|
||||
|
||||
function getConfig(cm) {
|
||||
var deflt = cm.state.closeBrackets;
|
||||
if (!deflt || deflt.override) return deflt;
|
||||
var mode = cm.getModeAt(cm.getCursor());
|
||||
return mode.closeBrackets || deflt;
|
||||
}
|
||||
|
||||
function handleBackspace(cm) {
|
||||
var conf = getConfig(cm);
|
||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var pairs = getOption(conf, "pairs");
|
||||
var ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var around = charsAround(cm, ranges[i].head);
|
||||
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||
}
|
||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||
var cur = ranges[i].head;
|
||||
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnter(cm) {
|
||||
var conf = getConfig(cm);
|
||||
var explode = conf && getOption(conf, "explode");
|
||||
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var around = charsAround(cm, ranges[i].head);
|
||||
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
||||
}
|
||||
cm.operation(function() {
|
||||
cm.replaceSelection("\n\n", null);
|
||||
cm.execCommand("goCharLeft");
|
||||
ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var line = ranges[i].head.line;
|
||||
cm.indentLine(line, null, true);
|
||||
cm.indentLine(line + 1, null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function contractSelection(sel) {
|
||||
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
|
||||
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
|
||||
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
|
||||
}
|
||||
|
||||
function handleChar(cm, ch) {
|
||||
var conf = getConfig(cm);
|
||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
|
||||
var pairs = getOption(conf, "pairs");
|
||||
var pos = pairs.indexOf(ch);
|
||||
if (pos == -1) return CodeMirror.Pass;
|
||||
var triples = getOption(conf, "triples");
|
||||
|
||||
var identical = pairs.charAt(pos + 1) == ch;
|
||||
var ranges = cm.listSelections();
|
||||
var opening = pos % 2 == 0;
|
||||
|
||||
var type;
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var range = ranges[i], cur = range.head, curType;
|
||||
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
||||
if (opening && !range.empty()) {
|
||||
curType = "surround";
|
||||
} else if ((identical || !opening) && next == ch) {
|
||||
if (identical && stringStartsAfter(cm, cur))
|
||||
curType = "both";
|
||||
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
||||
curType = "skipThree";
|
||||
else
|
||||
curType = "skip";
|
||||
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
|
||||
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
|
||||
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
|
||||
curType = "addFour";
|
||||
} else if (identical) {
|
||||
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
|
||||
else return CodeMirror.Pass;
|
||||
} else if (opening && (cm.getLine(cur.line).length == cur.ch ||
|
||||
isClosingBracket(next, pairs) ||
|
||||
/\s/.test(next))) {
|
||||
curType = "both";
|
||||
} else {
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
if (!type) type = curType;
|
||||
else if (type != curType) return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
|
||||
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
|
||||
cm.operation(function() {
|
||||
if (type == "skip") {
|
||||
cm.execCommand("goCharRight");
|
||||
} else if (type == "skipThree") {
|
||||
for (var i = 0; i < 3; i++)
|
||||
cm.execCommand("goCharRight");
|
||||
} else if (type == "surround") {
|
||||
var sels = cm.getSelections();
|
||||
for (var i = 0; i < sels.length; i++)
|
||||
sels[i] = left + sels[i] + right;
|
||||
cm.replaceSelections(sels, "around");
|
||||
sels = cm.listSelections().slice();
|
||||
for (var i = 0; i < sels.length; i++)
|
||||
sels[i] = contractSelection(sels[i]);
|
||||
cm.setSelections(sels);
|
||||
} else if (type == "both") {
|
||||
cm.replaceSelection(left + right, null);
|
||||
cm.triggerElectric(left + right);
|
||||
cm.execCommand("goCharLeft");
|
||||
} else if (type == "addFour") {
|
||||
cm.replaceSelection(left + left + left + left, "before");
|
||||
cm.execCommand("goCharRight");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isClosingBracket(ch, pairs) {
|
||||
var pos = pairs.lastIndexOf(ch);
|
||||
return pos > -1 && pos % 2 == 1;
|
||||
}
|
||||
|
||||
function charsAround(cm, pos) {
|
||||
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
||||
Pos(pos.line, pos.ch + 1));
|
||||
return str.length == 2 ? str : null;
|
||||
}
|
||||
|
||||
// Project the token type that will exists after the given char is
|
||||
// typed, and use it to determine whether it would cause the start
|
||||
// of a string token.
|
||||
function enteringString(cm, pos, ch) {
|
||||
var line = cm.getLine(pos.line);
|
||||
var token = cm.getTokenAt(pos);
|
||||
if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false;
|
||||
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
|
||||
stream.pos = stream.start = token.start;
|
||||
for (;;) {
|
||||
var type1 = cm.getMode().token(stream, token.state);
|
||||
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
|
||||
stream.start = stream.pos;
|
||||
}
|
||||
}
|
||||
|
||||
function stringStartsAfter(cm, pos) {
|
||||
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
|
||||
return /\bstring/.test(token.type) && token.start == pos.ch
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
/**
|
||||
* Tag-closer extension for CodeMirror.
|
||||
*
|
||||
* This extension adds an "autoCloseTags" option that can be set to
|
||||
* either true to get the default behavior, or an object to further
|
||||
* configure its behavior.
|
||||
*
|
||||
* These are supported options:
|
||||
*
|
||||
* `whenClosing` (default true)
|
||||
* Whether to autoclose when the '/' of a closing tag is typed.
|
||||
* `whenOpening` (default true)
|
||||
* Whether to autoclose the tag when the final '>' of an opening
|
||||
* tag is typed.
|
||||
* `dontCloseTags` (default is empty tags for HTML, none for XML)
|
||||
* An array of tag names that should not be autoclosed.
|
||||
* `indentTags` (default is block tags for HTML, none for XML)
|
||||
* An array of tag names that should, when opened, cause a
|
||||
* blank line to be added inside the tag, and the blank line and
|
||||
* closing line to be indented.
|
||||
*
|
||||
* See demos/closetag.html for a usage example.
|
||||
*/
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
|
||||
if (old != CodeMirror.Init && old)
|
||||
cm.removeKeyMap("autoCloseTags");
|
||||
if (!val) return;
|
||||
var map = {name: "autoCloseTags"};
|
||||
if (typeof val != "object" || val.whenClosing)
|
||||
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
|
||||
if (typeof val != "object" || val.whenOpening)
|
||||
map["'>'"] = function(cm) { return autoCloseGT(cm); };
|
||||
cm.addKeyMap(map);
|
||||
});
|
||||
|
||||
var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr"];
|
||||
var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
|
||||
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
|
||||
|
||||
function autoCloseGT(cm) {
|
||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
var ranges = cm.listSelections(), replacements = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
|
||||
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
|
||||
if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
|
||||
|
||||
var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
|
||||
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
|
||||
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
|
||||
|
||||
var tagName = state.tagName;
|
||||
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
|
||||
var lowerTagName = tagName.toLowerCase();
|
||||
// Don't process the '>' at the end of an end-tag or self-closing tag
|
||||
if (!tagName ||
|
||||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
|
||||
tok.type == "tag" && state.type == "closeTag" ||
|
||||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
|
||||
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
|
||||
closingTagExists(cm, tagName, pos, state, true))
|
||||
return CodeMirror.Pass;
|
||||
|
||||
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
|
||||
replacements[i] = {indent: indent,
|
||||
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
|
||||
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
|
||||
}
|
||||
|
||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||
var info = replacements[i];
|
||||
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
|
||||
var sel = cm.listSelections().slice(0);
|
||||
sel[i] = {head: info.newPos, anchor: info.newPos};
|
||||
cm.setSelections(sel);
|
||||
if (info.indent) {
|
||||
cm.indentLine(info.newPos.line, null, true);
|
||||
cm.indentLine(info.newPos.line + 1, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function autoCloseCurrent(cm, typingSlash) {
|
||||
var ranges = cm.listSelections(), replacements = [];
|
||||
var head = typingSlash ? "/" : "</";
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
||||
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
|
||||
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
|
||||
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
|
||||
tok.start != pos.ch - 1))
|
||||
return CodeMirror.Pass;
|
||||
// Kludge to get around the fact that we are not in XML mode
|
||||
// when completing in JS/CSS snippet in htmlmixed mode. Does not
|
||||
// work for other XML embedded languages (there is no general
|
||||
// way to go from a mixed mode to its current XML state).
|
||||
var replacement;
|
||||
if (inner.mode.name != "xml") {
|
||||
if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
|
||||
replacement = head + "script";
|
||||
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
|
||||
replacement = head + "style";
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
} else {
|
||||
if (!state.context || !state.context.tagName ||
|
||||
closingTagExists(cm, state.context.tagName, pos, state))
|
||||
return CodeMirror.Pass;
|
||||
replacement = head + state.context.tagName;
|
||||
}
|
||||
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
|
||||
replacements[i] = replacement;
|
||||
}
|
||||
cm.replaceSelections(replacements);
|
||||
ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++)
|
||||
if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
|
||||
cm.indentLine(ranges[i].head.line);
|
||||
}
|
||||
|
||||
function autoCloseSlash(cm) {
|
||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
return autoCloseCurrent(cm, true);
|
||||
}
|
||||
|
||||
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
|
||||
|
||||
function indexOf(collection, elt) {
|
||||
if (collection.indexOf) return collection.indexOf(elt);
|
||||
for (var i = 0, e = collection.length; i < e; ++i)
|
||||
if (collection[i] == elt) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If xml-fold is loaded, we use its functionality to try and verify
|
||||
// whether a given tag is actually unclosed.
|
||||
function closingTagExists(cm, tagName, pos, state, newTag) {
|
||||
if (!CodeMirror.scanForClosingTag) return false;
|
||||
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
|
||||
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
|
||||
if (!nextClose || nextClose.tag != tagName) return false;
|
||||
var cx = state.context;
|
||||
// If the immediate wrapping context contains onCx instances of
|
||||
// the same tag, a closing tag only exists if there are at least
|
||||
// that many closing tags of that type following.
|
||||
for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
|
||||
pos = nextClose.to;
|
||||
for (var i = 1; i < onCx; i++) {
|
||||
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
|
||||
if (!next || next.tag != tagName) return false;
|
||||
pos = next.to;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
|
||||
emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
|
||||
unorderedListRE = /[*+-]\s/;
|
||||
|
||||
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
|
||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||
var ranges = cm.listSelections(), replacements = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var pos = ranges[i].head;
|
||||
var eolState = cm.getStateAfter(pos.line);
|
||||
var inList = eolState.list !== false;
|
||||
var inQuote = eolState.quote !== 0;
|
||||
|
||||
var line = cm.getLine(pos.line), match = listRE.exec(line);
|
||||
if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
|
||||
cm.execCommand("newlineAndIndent");
|
||||
return;
|
||||
}
|
||||
if (emptyListRE.test(line)) {
|
||||
if (!/>\s*$/.test(line)) cm.replaceRange("", {
|
||||
line: pos.line, ch: 0
|
||||
}, {
|
||||
line: pos.line, ch: pos.ch + 1
|
||||
});
|
||||
replacements[i] = "\n";
|
||||
} else {
|
||||
var indent = match[1], after = match[5];
|
||||
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
|
||||
? match[2].replace("x", " ")
|
||||
: (parseInt(match[3], 10) + 1) + match[4];
|
||||
|
||||
replacements[i] = "\n" + indent + bullet + after;
|
||||
}
|
||||
}
|
||||
|
||||
cm.replaceSelections(replacements);
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
|
||||
(document.documentMode == null || document.documentMode < 8);
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
|
||||
|
||||
function findMatchingBracket(cm, where, strict, config) {
|
||||
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
|
||||
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
|
||||
if (!match) return null;
|
||||
var dir = match.charAt(1) == ">" ? 1 : -1;
|
||||
if (strict && (dir > 0) != (pos == where.ch)) return null;
|
||||
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
|
||||
|
||||
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
|
||||
if (found == null) return null;
|
||||
return {from: Pos(where.line, pos), to: found && found.pos,
|
||||
match: found && found.ch == match.charAt(0), forward: dir > 0};
|
||||
}
|
||||
|
||||
// bracketRegex is used to specify which type of bracket to scan
|
||||
// should be a regexp, e.g. /[[\]]/
|
||||
//
|
||||
// Note: If "where" is on an open bracket, then this bracket is ignored.
|
||||
//
|
||||
// Returns false when no bracket was found, null when it reached
|
||||
// maxScanLines and gave up
|
||||
function scanForBracket(cm, where, dir, style, config) {
|
||||
var maxScanLen = (config && config.maxScanLineLength) || 10000;
|
||||
var maxScanLines = (config && config.maxScanLines) || 1000;
|
||||
|
||||
var stack = [];
|
||||
var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
|
||||
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
|
||||
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
|
||||
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
|
||||
var line = cm.getLine(lineNo);
|
||||
if (!line) continue;
|
||||
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
|
||||
if (line.length > maxScanLen) continue;
|
||||
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
|
||||
for (; pos != end; pos += dir) {
|
||||
var ch = line.charAt(pos);
|
||||
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
|
||||
var match = matching[ch];
|
||||
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
|
||||
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
|
||||
else stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
|
||||
}
|
||||
|
||||
function matchBrackets(cm, autoclear, config) {
|
||||
// Disable brace matching in long lines, since it'll cause hugely slow updates
|
||||
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
|
||||
var marks = [], ranges = cm.listSelections();
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
|
||||
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
|
||||
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
|
||||
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
|
||||
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
|
||||
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
|
||||
}
|
||||
}
|
||||
|
||||
if (marks.length) {
|
||||
// Kludge to work around the IE bug from issue #1193, where text
|
||||
// input stops going to the textare whever this fires.
|
||||
if (ie_lt8 && cm.state.focused) cm.focus();
|
||||
|
||||
var clear = function() {
|
||||
cm.operation(function() {
|
||||
for (var i = 0; i < marks.length; i++) marks[i].clear();
|
||||
});
|
||||
};
|
||||
if (autoclear) setTimeout(clear, 800);
|
||||
else return clear;
|
||||
}
|
||||
}
|
||||
|
||||
var currentlyHighlighted = null;
|
||||
function doMatchBrackets(cm) {
|
||||
cm.operation(function() {
|
||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
||||
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
|
||||
});
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.off("cursorActivity", doMatchBrackets);
|
||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
||||
}
|
||||
if (val) {
|
||||
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
||||
cm.on("cursorActivity", doMatchBrackets);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
|
||||
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
|
||||
return findMatchingBracket(this, pos, strict, config);
|
||||
});
|
||||
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
|
||||
return scanForBracket(this, pos, dir, style, config);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.off("cursorActivity", doMatchTags);
|
||||
cm.off("viewportChange", maybeUpdateMatch);
|
||||
clear(cm);
|
||||
}
|
||||
if (val) {
|
||||
cm.state.matchBothTags = typeof val == "object" && val.bothTags;
|
||||
cm.on("cursorActivity", doMatchTags);
|
||||
cm.on("viewportChange", maybeUpdateMatch);
|
||||
doMatchTags(cm);
|
||||
}
|
||||
});
|
||||
|
||||
function clear(cm) {
|
||||
if (cm.state.tagHit) cm.state.tagHit.clear();
|
||||
if (cm.state.tagOther) cm.state.tagOther.clear();
|
||||
cm.state.tagHit = cm.state.tagOther = null;
|
||||
}
|
||||
|
||||
function doMatchTags(cm) {
|
||||
cm.state.failedTagMatch = false;
|
||||
cm.operation(function() {
|
||||
clear(cm);
|
||||
if (cm.somethingSelected()) return;
|
||||
var cur = cm.getCursor(), range = cm.getViewport();
|
||||
range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);
|
||||
var match = CodeMirror.findMatchingTag(cm, cur, range);
|
||||
if (!match) return;
|
||||
if (cm.state.matchBothTags) {
|
||||
var hit = match.at == "open" ? match.open : match.close;
|
||||
if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"});
|
||||
}
|
||||
var other = match.at == "close" ? match.open : match.close;
|
||||
if (other)
|
||||
cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
|
||||
else
|
||||
cm.state.failedTagMatch = true;
|
||||
});
|
||||
}
|
||||
|
||||
function maybeUpdateMatch(cm) {
|
||||
if (cm.state.failedTagMatch) doMatchTags(cm);
|
||||
}
|
||||
|
||||
CodeMirror.commands.toMatchingTag = function(cm) {
|
||||
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
|
||||
if (found) {
|
||||
var other = found.at == "close" ? found.open : found.close;
|
||||
if (other) cm.extendSelection(other.to, other.from);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
|
||||
if (prev == CodeMirror.Init) prev = false;
|
||||
if (prev && !val)
|
||||
cm.removeOverlay("trailingspace");
|
||||
else if (!prev && val)
|
||||
cm.addOverlay({
|
||||
token: function(stream) {
|
||||
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
|
||||
if (i > stream.pos) { stream.pos = i; return null; }
|
||||
stream.pos = l;
|
||||
return "trailingspace";
|
||||
},
|
||||
name: "trailingspace"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
|
||||
var line = start.line, lineText = cm.getLine(line);
|
||||
var tokenType;
|
||||
|
||||
function findOpening(openCh) {
|
||||
for (var at = start.ch, pass = 0;;) {
|
||||
var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
|
||||
if (found == -1) {
|
||||
if (pass == 1) break;
|
||||
pass = 1;
|
||||
at = lineText.length;
|
||||
continue;
|
||||
}
|
||||
if (pass == 1 && found < start.ch) break;
|
||||
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
|
||||
if (!/^(comment|string)/.test(tokenType)) return found + 1;
|
||||
at = found - 1;
|
||||
}
|
||||
}
|
||||
|
||||
var startToken = "{", endToken = "}", startCh = findOpening("{");
|
||||
if (startCh == null) {
|
||||
startToken = "[", endToken = "]";
|
||||
startCh = findOpening("[");
|
||||
}
|
||||
|
||||
if (startCh == null) return;
|
||||
var count = 1, lastLine = cm.lastLine(), end, endCh;
|
||||
outer: for (var i = line; i <= lastLine; ++i) {
|
||||
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
||||
for (;;) {
|
||||
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
|
||||
if (nextOpen < 0) nextOpen = text.length;
|
||||
if (nextClose < 0) nextClose = text.length;
|
||||
pos = Math.min(nextOpen, nextClose);
|
||||
if (pos == text.length) break;
|
||||
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
|
||||
if (pos == nextOpen) ++count;
|
||||
else if (!--count) { end = i; endCh = pos; break outer; }
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
if (end == null || line == end && endCh == startCh) return;
|
||||
return {from: CodeMirror.Pos(line, startCh),
|
||||
to: CodeMirror.Pos(end, endCh)};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
||||
function hasImport(line) {
|
||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||
if (start.type != "keyword" || start.string != "import") return null;
|
||||
// Now find closing semicolon, return its position
|
||||
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
|
||||
var text = cm.getLine(i), semi = text.indexOf(";");
|
||||
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
|
||||
}
|
||||
}
|
||||
|
||||
var startLine = start.line, has = hasImport(startLine), prev;
|
||||
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
|
||||
return null;
|
||||
for (var end = has.end;;) {
|
||||
var next = hasImport(end.line + 1);
|
||||
if (next == null) break;
|
||||
end = next.end;
|
||||
}
|
||||
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
||||
function hasInclude(line) {
|
||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
||||
}
|
||||
|
||||
var startLine = start.line, has = hasInclude(startLine);
|
||||
if (has == null || hasInclude(startLine - 1) != null) return null;
|
||||
for (var end = startLine;;) {
|
||||
var next = hasInclude(end + 1);
|
||||
if (next == null) break;
|
||||
++end;
|
||||
}
|
||||
return {from: CodeMirror.Pos(startLine, has + 1),
|
||||
to: cm.clipPos(CodeMirror.Pos(end))};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
|
||||
return mode.blockCommentStart && mode.blockCommentEnd;
|
||||
}, function(cm, start) {
|
||||
var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
|
||||
if (!startToken || !endToken) return;
|
||||
var line = start.line, lineText = cm.getLine(line);
|
||||
|
||||
var startCh;
|
||||
for (var at = start.ch, pass = 0;;) {
|
||||
var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
|
||||
if (found == -1) {
|
||||
if (pass == 1) return;
|
||||
pass = 1;
|
||||
at = lineText.length;
|
||||
continue;
|
||||
}
|
||||
if (pass == 1 && found < start.ch) return;
|
||||
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
|
||||
(found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
|
||||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
|
||||
startCh = found + startToken.length;
|
||||
break;
|
||||
}
|
||||
at = found - 1;
|
||||
}
|
||||
|
||||
var depth = 1, lastLine = cm.lastLine(), end, endCh;
|
||||
outer: for (var i = line; i <= lastLine; ++i) {
|
||||
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
||||
for (;;) {
|
||||
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
|
||||
if (nextOpen < 0) nextOpen = text.length;
|
||||
if (nextClose < 0) nextClose = text.length;
|
||||
pos = Math.min(nextOpen, nextClose);
|
||||
if (pos == text.length) break;
|
||||
if (pos == nextOpen) ++depth;
|
||||
else if (!--depth) { end = i; endCh = pos; break outer; }
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
if (end == null || line == end && endCh == startCh) return;
|
||||
return {from: CodeMirror.Pos(line, startCh),
|
||||
to: CodeMirror.Pos(end, endCh)};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
function doFold(cm, pos, options, force) {
|
||||
if (options && options.call) {
|
||||
var finder = options;
|
||||
options = null;
|
||||
} else {
|
||||
var finder = getOption(cm, options, "rangeFinder");
|
||||
}
|
||||
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
||||
var minSize = getOption(cm, options, "minFoldSize");
|
||||
|
||||
function getRange(allowFolded) {
|
||||
var range = finder(cm, pos);
|
||||
if (!range || range.to.line - range.from.line < minSize) return null;
|
||||
var marks = cm.findMarksAt(range.from);
|
||||
for (var i = 0; i < marks.length; ++i) {
|
||||
if (marks[i].__isFold && force !== "fold") {
|
||||
if (!allowFolded) return null;
|
||||
range.cleared = true;
|
||||
marks[i].clear();
|
||||
}
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
var range = getRange(true);
|
||||
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
||||
pos = CodeMirror.Pos(pos.line - 1, 0);
|
||||
range = getRange(false);
|
||||
}
|
||||
if (!range || range.cleared || force === "unfold") return;
|
||||
|
||||
var myWidget = makeWidget(cm, options);
|
||||
CodeMirror.on(myWidget, "mousedown", function(e) {
|
||||
myRange.clear();
|
||||
CodeMirror.e_preventDefault(e);
|
||||
});
|
||||
var myRange = cm.markText(range.from, range.to, {
|
||||
replacedWith: myWidget,
|
||||
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
||||
__isFold: true
|
||||
});
|
||||
myRange.on("clear", function(from, to) {
|
||||
CodeMirror.signal(cm, "unfold", cm, from, to);
|
||||
});
|
||||
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
||||
}
|
||||
|
||||
function makeWidget(cm, options) {
|
||||
var widget = getOption(cm, options, "widget");
|
||||
if (typeof widget == "string") {
|
||||
var text = document.createTextNode(widget);
|
||||
widget = document.createElement("span");
|
||||
widget.appendChild(text);
|
||||
widget.className = "CodeMirror-foldmarker";
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
// Clumsy backwards-compatible interface
|
||||
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
||||
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
||||
};
|
||||
|
||||
// New-style interface
|
||||
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
||||
doFold(this, pos, options, force);
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("isFolded", function(pos) {
|
||||
var marks = this.findMarksAt(pos);
|
||||
for (var i = 0; i < marks.length; ++i)
|
||||
if (marks[i].__isFold) return true;
|
||||
});
|
||||
|
||||
CodeMirror.commands.toggleFold = function(cm) {
|
||||
cm.foldCode(cm.getCursor());
|
||||
};
|
||||
CodeMirror.commands.fold = function(cm) {
|
||||
cm.foldCode(cm.getCursor(), null, "fold");
|
||||
};
|
||||
CodeMirror.commands.unfold = function(cm) {
|
||||
cm.foldCode(cm.getCursor(), null, "unfold");
|
||||
};
|
||||
CodeMirror.commands.foldAll = function(cm) {
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
|
||||
});
|
||||
};
|
||||
CodeMirror.commands.unfoldAll = function(cm) {
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||
cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
|
||||
});
|
||||
};
|
||||
|
||||
CodeMirror.registerHelper("fold", "combine", function() {
|
||||
var funcs = Array.prototype.slice.call(arguments, 0);
|
||||
return function(cm, start) {
|
||||
for (var i = 0; i < funcs.length; ++i) {
|
||||
var found = funcs[i](cm, start);
|
||||
if (found) return found;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
||||
var helpers = cm.getHelpers(start, "fold");
|
||||
for (var i = 0; i < helpers.length; i++) {
|
||||
var cur = helpers[i](cm, start);
|
||||
if (cur) return cur;
|
||||
}
|
||||
});
|
||||
|
||||
var defaultOptions = {
|
||||
rangeFinder: CodeMirror.fold.auto,
|
||||
widget: "\u2194",
|
||||
minFoldSize: 0,
|
||||
scanUp: false,
|
||||
clearOnEnter: true
|
||||
};
|
||||
|
||||
CodeMirror.defineOption("foldOptions", null);
|
||||
|
||||
function getOption(cm, options, name) {
|
||||
if (options && options[name] !== undefined)
|
||||
return options[name];
|
||||
var editorOptions = cm.options.foldOptions;
|
||||
if (editorOptions && editorOptions[name] !== undefined)
|
||||
return editorOptions[name];
|
||||
return defaultOptions[name];
|
||||
}
|
||||
|
||||
CodeMirror.defineExtension("foldOption", function(options, name) {
|
||||
return getOption(this, options, name);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
.CodeMirror-foldmarker {
|
||||
color: blue;
|
||||
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
||||
font-family: arial;
|
||||
line-height: .3;
|
||||
cursor: pointer;
|
||||
}
|
||||
.CodeMirror-foldgutter {
|
||||
width: .7em;
|
||||
}
|
||||
.CodeMirror-foldgutter-open,
|
||||
.CodeMirror-foldgutter-folded {
|
||||
cursor: pointer;
|
||||
}
|
||||
.CodeMirror-foldgutter-open:after {
|
||||
content: "\25BE";
|
||||
}
|
||||
.CodeMirror-foldgutter-folded:after {
|
||||
content: "\25B8";
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("./foldcode"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "./foldcode"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
||||
cm.state.foldGutter = null;
|
||||
cm.off("gutterClick", onGutterClick);
|
||||
cm.off("change", onChange);
|
||||
cm.off("viewportChange", onViewportChange);
|
||||
cm.off("fold", onFold);
|
||||
cm.off("unfold", onFold);
|
||||
cm.off("swapDoc", onChange);
|
||||
}
|
||||
if (val) {
|
||||
cm.state.foldGutter = new State(parseOptions(val));
|
||||
updateInViewport(cm);
|
||||
cm.on("gutterClick", onGutterClick);
|
||||
cm.on("change", onChange);
|
||||
cm.on("viewportChange", onViewportChange);
|
||||
cm.on("fold", onFold);
|
||||
cm.on("unfold", onFold);
|
||||
cm.on("swapDoc", onChange);
|
||||
}
|
||||
});
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function State(options) {
|
||||
this.options = options;
|
||||
this.from = this.to = 0;
|
||||
}
|
||||
|
||||
function parseOptions(opts) {
|
||||
if (opts === true) opts = {};
|
||||
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
||||
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
||||
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
||||
return opts;
|
||||
}
|
||||
|
||||
function isFolded(cm, line) {
|
||||
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
||||
for (var i = 0; i < marks.length; ++i)
|
||||
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
||||
}
|
||||
|
||||
function marker(spec) {
|
||||
if (typeof spec == "string") {
|
||||
var elt = document.createElement("div");
|
||||
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
||||
return elt;
|
||||
} else {
|
||||
return spec.cloneNode(true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFoldInfo(cm, from, to) {
|
||||
var opts = cm.state.foldGutter.options, cur = from;
|
||||
var minSize = cm.foldOption(opts, "minFoldSize");
|
||||
var func = cm.foldOption(opts, "rangeFinder");
|
||||
cm.eachLine(from, to, function(line) {
|
||||
var mark = null;
|
||||
if (isFolded(cm, cur)) {
|
||||
mark = marker(opts.indicatorFolded);
|
||||
} else {
|
||||
var pos = Pos(cur, 0);
|
||||
var range = func && func(cm, pos);
|
||||
if (range && range.to.line - range.from.line >= minSize)
|
||||
mark = marker(opts.indicatorOpen);
|
||||
}
|
||||
cm.setGutterMarker(line, opts.gutter, mark);
|
||||
++cur;
|
||||
});
|
||||
}
|
||||
|
||||
function updateInViewport(cm) {
|
||||
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
cm.operation(function() {
|
||||
updateFoldInfo(cm, vp.from, vp.to);
|
||||
});
|
||||
state.from = vp.from; state.to = vp.to;
|
||||
}
|
||||
|
||||
function onGutterClick(cm, line, gutter) {
|
||||
var state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
var opts = state.options;
|
||||
if (gutter != opts.gutter) return;
|
||||
var folded = isFolded(cm, line);
|
||||
if (folded) folded.clear();
|
||||
else cm.foldCode(Pos(line, 0), opts.rangeFinder);
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
var opts = state.options;
|
||||
state.from = state.to = 0;
|
||||
clearTimeout(state.changeUpdate);
|
||||
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
||||
}
|
||||
|
||||
function onViewportChange(cm) {
|
||||
var state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
var opts = state.options;
|
||||
clearTimeout(state.changeUpdate);
|
||||
state.changeUpdate = setTimeout(function() {
|
||||
var vp = cm.getViewport();
|
||||
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
||||
updateInViewport(cm);
|
||||
} else {
|
||||
cm.operation(function() {
|
||||
if (vp.from < state.from) {
|
||||
updateFoldInfo(cm, vp.from, state.from);
|
||||
state.from = vp.from;
|
||||
}
|
||||
if (vp.to > state.to) {
|
||||
updateFoldInfo(cm, state.to, vp.to);
|
||||
state.to = vp.to;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, opts.updateViewportTimeSpan || 400);
|
||||
}
|
||||
|
||||
function onFold(cm, from) {
|
||||
var state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
var line = from.line;
|
||||
if (line >= state.from && line < state.to)
|
||||
updateFoldInfo(cm, line, line + 1);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
function lineIndent(cm, lineNo) {
|
||||
var text = cm.getLine(lineNo)
|
||||
var spaceTo = text.search(/\S/)
|
||||
if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))
|
||||
return -1
|
||||
return CodeMirror.countColumn(text, null, cm.getOption("tabSize"))
|
||||
}
|
||||
!
|
||||
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
|
||||
var myIndent = lineIndent(cm, start.line)
|
||||
if (myIndent < 0) return
|
||||
var lastLineInFold = null
|
||||
|
||||
// Go through lines until we find a line that definitely doesn't belong in
|
||||
// the block we're folding, or to the end.
|
||||
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
|
||||
var indent = lineIndent(cm, i)
|
||||
if (indent == -1) {
|
||||
} else if (indent > myIndent) {
|
||||
// Lines with a greater indent are considered part of the block.
|
||||
lastLineInFold = i;
|
||||
} else {
|
||||
// If this line has non-space, non-comment content, and is
|
||||
// indented less or equal to the start line, it is the start of
|
||||
// another block.
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastLineInFold) return {
|
||||
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
|
||||
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("fold", "markdown", function(cm, start) {
|
||||
var maxDepth = 100;
|
||||
|
||||
function isHeader(lineNo) {
|
||||
var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
|
||||
return tokentype && /\bheader\b/.test(tokentype);
|
||||
}
|
||||
|
||||
function headerLevel(lineNo, line, nextLine) {
|
||||
var match = line && line.match(/^#+/);
|
||||
if (match && isHeader(lineNo)) return match[0].length;
|
||||
match = nextLine && nextLine.match(/^[=\-]+\s*$/);
|
||||
if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2;
|
||||
return maxDepth;
|
||||
}
|
||||
|
||||
var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);
|
||||
var level = headerLevel(start.line, firstLine, nextLine);
|
||||
if (level === maxDepth) return undefined;
|
||||
|
||||
var lastLineNo = cm.lastLine();
|
||||
var end = start.line, nextNextLine = cm.getLine(end + 2);
|
||||
while (end < lastLineNo) {
|
||||
if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;
|
||||
++end;
|
||||
nextLine = nextNextLine;
|
||||
nextNextLine = cm.getLine(end + 2);
|
||||
}
|
||||
|
||||
return {
|
||||
from: CodeMirror.Pos(start.line, firstLine.length),
|
||||
to: CodeMirror.Pos(end, cm.getLine(end).length)
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }
|
||||
|
||||
var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
|
||||
var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
|
||||
var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g");
|
||||
|
||||
function Iter(cm, line, ch, range) {
|
||||
this.line = line; this.ch = ch;
|
||||
this.cm = cm; this.text = cm.getLine(line);
|
||||
this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
|
||||
this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
|
||||
}
|
||||
|
||||
function tagAt(iter, ch) {
|
||||
var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));
|
||||
return type && /\btag\b/.test(type);
|
||||
}
|
||||
|
||||
function nextLine(iter) {
|
||||
if (iter.line >= iter.max) return;
|
||||
iter.ch = 0;
|
||||
iter.text = iter.cm.getLine(++iter.line);
|
||||
return true;
|
||||
}
|
||||
function prevLine(iter) {
|
||||
if (iter.line <= iter.min) return;
|
||||
iter.text = iter.cm.getLine(--iter.line);
|
||||
iter.ch = iter.text.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
function toTagEnd(iter) {
|
||||
for (;;) {
|
||||
var gt = iter.text.indexOf(">", iter.ch);
|
||||
if (gt == -1) { if (nextLine(iter)) continue; else return; }
|
||||
if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }
|
||||
var lastSlash = iter.text.lastIndexOf("/", gt);
|
||||
var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
|
||||
iter.ch = gt + 1;
|
||||
return selfClose ? "selfClose" : "regular";
|
||||
}
|
||||
}
|
||||
function toTagStart(iter) {
|
||||
for (;;) {
|
||||
var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1;
|
||||
if (lt == -1) { if (prevLine(iter)) continue; else return; }
|
||||
if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }
|
||||
xmlTagStart.lastIndex = lt;
|
||||
iter.ch = lt;
|
||||
var match = xmlTagStart.exec(iter.text);
|
||||
if (match && match.index == lt) return match;
|
||||
}
|
||||
}
|
||||
|
||||
function toNextTag(iter) {
|
||||
for (;;) {
|
||||
xmlTagStart.lastIndex = iter.ch;
|
||||
var found = xmlTagStart.exec(iter.text);
|
||||
if (!found) { if (nextLine(iter)) continue; else return; }
|
||||
if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }
|
||||
iter.ch = found.index + found[0].length;
|
||||
return found;
|
||||
}
|
||||
}
|
||||
function toPrevTag(iter) {
|
||||
for (;;) {
|
||||
var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1;
|
||||
if (gt == -1) { if (prevLine(iter)) continue; else return; }
|
||||
if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }
|
||||
var lastSlash = iter.text.lastIndexOf("/", gt);
|
||||
var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
|
||||
iter.ch = gt + 1;
|
||||
return selfClose ? "selfClose" : "regular";
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingClose(iter, tag) {
|
||||
var stack = [];
|
||||
for (;;) {
|
||||
var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);
|
||||
if (!next || !(end = toTagEnd(iter))) return;
|
||||
if (end == "selfClose") continue;
|
||||
if (next[1]) { // closing tag
|
||||
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {
|
||||
stack.length = i;
|
||||
break;
|
||||
}
|
||||
if (i < 0 && (!tag || tag == next[2])) return {
|
||||
tag: next[2],
|
||||
from: Pos(startLine, startCh),
|
||||
to: Pos(iter.line, iter.ch)
|
||||
};
|
||||
} else { // opening tag
|
||||
stack.push(next[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function findMatchingOpen(iter, tag) {
|
||||
var stack = [];
|
||||
for (;;) {
|
||||
var prev = toPrevTag(iter);
|
||||
if (!prev) return;
|
||||
if (prev == "selfClose") { toTagStart(iter); continue; }
|
||||
var endLine = iter.line, endCh = iter.ch;
|
||||
var start = toTagStart(iter);
|
||||
if (!start) return;
|
||||
if (start[1]) { // closing tag
|
||||
stack.push(start[2]);
|
||||
} else { // opening tag
|
||||
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {
|
||||
stack.length = i;
|
||||
break;
|
||||
}
|
||||
if (i < 0 && (!tag || tag == start[2])) return {
|
||||
tag: start[2],
|
||||
from: Pos(iter.line, iter.ch),
|
||||
to: Pos(endLine, endCh)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("fold", "xml", function(cm, start) {
|
||||
var iter = new Iter(cm, start.line, 0);
|
||||
for (;;) {
|
||||
var openTag = toNextTag(iter), end;
|
||||
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
|
||||
if (!openTag[1] && end != "selfClose") {
|
||||
var startPos = Pos(iter.line, iter.ch);
|
||||
var endPos = findMatchingClose(iter, openTag[2]);
|
||||
return endPos && {from: startPos, to: endPos.from};
|
||||
}
|
||||
}
|
||||
});
|
||||
CodeMirror.findMatchingTag = function(cm, pos, range) {
|
||||
var iter = new Iter(cm, pos.line, pos.ch, range);
|
||||
if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
|
||||
var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
|
||||
var start = end && toTagStart(iter);
|
||||
if (!end || !start || cmp(iter, pos) > 0) return;
|
||||
var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
|
||||
if (end == "selfClose") return {open: here, close: null, at: "open"};
|
||||
|
||||
if (start[1]) { // closing tag
|
||||
return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
|
||||
} else { // opening tag
|
||||
iter = new Iter(cm, to.line, to.ch, range);
|
||||
return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.findEnclosingTag = function(cm, pos, range) {
|
||||
var iter = new Iter(cm, pos.line, pos.ch, range);
|
||||
for (;;) {
|
||||
var open = findMatchingOpen(iter);
|
||||
if (!open) break;
|
||||
var forward = new Iter(cm, pos.line, pos.ch, range);
|
||||
var close = findMatchingClose(forward, open.tag);
|
||||
if (close) return {open: open, close: close};
|
||||
}
|
||||
};
|
||||
|
||||
// Used by addon/edit/closetag.js
|
||||
CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
|
||||
var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
|
||||
return findMatchingClose(iter, name);
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var WORD = /[\w$]+/, RANGE = 500;
|
||||
|
||||
CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
|
||||
var word = options && options.word || WORD;
|
||||
var range = options && options.range || RANGE;
|
||||
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
|
||||
var end = cur.ch, start = end;
|
||||
while (start && word.test(curLine.charAt(start - 1))) --start;
|
||||
var curWord = start != end && curLine.slice(start, end);
|
||||
|
||||
var list = options && options.list || [], seen = {};
|
||||
var re = new RegExp(word.source, "g");
|
||||
for (var dir = -1; dir <= 1; dir += 2) {
|
||||
var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
|
||||
for (; line != endLine; line += dir) {
|
||||
var text = editor.getLine(line), m;
|
||||
while (m = re.exec(text)) {
|
||||
if (line == cur.line && m[0] === curWord) continue;
|
||||
if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
|
||||
seen[m[0]] = true;
|
||||
list.push(m[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../../mode/css/css"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../../mode/css/css"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
|
||||
"first-letter": 1, "first-line": 1, "first-child": 1,
|
||||
before: 1, after: 1, lang: 1};
|
||||
|
||||
CodeMirror.registerHelper("hint", "css", function(cm) {
|
||||
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
|
||||
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
|
||||
if (inner.mode.name != "css") return;
|
||||
|
||||
if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
|
||||
return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
|
||||
to: CodeMirror.Pos(cur.line, token.end)};
|
||||
|
||||
var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
|
||||
if (/[^\w$_-]/.test(word)) {
|
||||
word = ""; start = end = cur.ch;
|
||||
}
|
||||
|
||||
var spec = CodeMirror.resolveMode("text/css");
|
||||
|
||||
var result = [];
|
||||
function add(keywords) {
|
||||
for (var name in keywords)
|
||||
if (!word || name.lastIndexOf(word, 0) == 0)
|
||||
result.push(name);
|
||||
}
|
||||
|
||||
var st = inner.state.state;
|
||||
if (st == "pseudo" || token.type == "variable-3") {
|
||||
add(pseudoClasses);
|
||||
} else if (st == "block" || st == "maybeprop") {
|
||||
add(spec.propertyKeywords);
|
||||
} else if (st == "prop" || st == "parens" || st == "at" || st == "params") {
|
||||
add(spec.valueKeywords);
|
||||
add(spec.colorKeywords);
|
||||
} else if (st == "media" || st == "media_parens") {
|
||||
add(spec.mediaTypes);
|
||||
add(spec.mediaFeatures);
|
||||
}
|
||||
|
||||
if (result.length) return {
|
||||
list: result,
|
||||
from: CodeMirror.Pos(cur.line, start),
|
||||
to: CodeMirror.Pos(cur.line, end)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("./xml-hint"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "./xml-hint"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" ");
|
||||
var targets = ["_blank", "_self", "_top", "_parent"];
|
||||
var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"];
|
||||
var methods = ["get", "post", "put", "delete"];
|
||||
var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
|
||||
var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech",
|
||||
"3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait",
|
||||
"orientation:landscape", "device-height: [X]", "device-width: [X]"];
|
||||
var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags
|
||||
|
||||
var data = {
|
||||
a: {
|
||||
attrs: {
|
||||
href: null, ping: null, type: null,
|
||||
media: media,
|
||||
target: targets,
|
||||
hreflang: langs
|
||||
}
|
||||
},
|
||||
abbr: s,
|
||||
acronym: s,
|
||||
address: s,
|
||||
applet: s,
|
||||
area: {
|
||||
attrs: {
|
||||
alt: null, coords: null, href: null, target: null, ping: null,
|
||||
media: media, hreflang: langs, type: null,
|
||||
shape: ["default", "rect", "circle", "poly"]
|
||||
}
|
||||
},
|
||||
article: s,
|
||||
aside: s,
|
||||
audio: {
|
||||
attrs: {
|
||||
src: null, mediagroup: null,
|
||||
crossorigin: ["anonymous", "use-credentials"],
|
||||
preload: ["none", "metadata", "auto"],
|
||||
autoplay: ["", "autoplay"],
|
||||
loop: ["", "loop"],
|
||||
controls: ["", "controls"]
|
||||
}
|
||||
},
|
||||
b: s,
|
||||
base: { attrs: { href: null, target: targets } },
|
||||
basefont: s,
|
||||
bdi: s,
|
||||
bdo: s,
|
||||
big: s,
|
||||
blockquote: { attrs: { cite: null } },
|
||||
body: s,
|
||||
br: s,
|
||||
button: {
|
||||
attrs: {
|
||||
form: null, formaction: null, name: null, value: null,
|
||||
autofocus: ["", "autofocus"],
|
||||
disabled: ["", "autofocus"],
|
||||
formenctype: encs,
|
||||
formmethod: methods,
|
||||
formnovalidate: ["", "novalidate"],
|
||||
formtarget: targets,
|
||||
type: ["submit", "reset", "button"]
|
||||
}
|
||||
},
|
||||
canvas: { attrs: { width: null, height: null } },
|
||||
caption: s,
|
||||
center: s,
|
||||
cite: s,
|
||||
code: s,
|
||||
col: { attrs: { span: null } },
|
||||
colgroup: { attrs: { span: null } },
|
||||
command: {
|
||||
attrs: {
|
||||
type: ["command", "checkbox", "radio"],
|
||||
label: null, icon: null, radiogroup: null, command: null, title: null,
|
||||
disabled: ["", "disabled"],
|
||||
checked: ["", "checked"]
|
||||
}
|
||||
},
|
||||
data: { attrs: { value: null } },
|
||||
datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } },
|
||||
datalist: { attrs: { data: null } },
|
||||
dd: s,
|
||||
del: { attrs: { cite: null, datetime: null } },
|
||||
details: { attrs: { open: ["", "open"] } },
|
||||
dfn: s,
|
||||
dir: s,
|
||||
div: s,
|
||||
dl: s,
|
||||
dt: s,
|
||||
em: s,
|
||||
embed: { attrs: { src: null, type: null, width: null, height: null } },
|
||||
eventsource: { attrs: { src: null } },
|
||||
fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } },
|
||||
figcaption: s,
|
||||
figure: s,
|
||||
font: s,
|
||||
footer: s,
|
||||
form: {
|
||||
attrs: {
|
||||
action: null, name: null,
|
||||
"accept-charset": charsets,
|
||||
autocomplete: ["on", "off"],
|
||||
enctype: encs,
|
||||
method: methods,
|
||||
novalidate: ["", "novalidate"],
|
||||
target: targets
|
||||
}
|
||||
},
|
||||
frame: s,
|
||||
frameset: s,
|
||||
h1: s, h2: s, h3: s, h4: s, h5: s, h6: s,
|
||||
head: {
|
||||
attrs: {},
|
||||
children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"]
|
||||
},
|
||||
header: s,
|
||||
hgroup: s,
|
||||
hr: s,
|
||||
html: {
|
||||
attrs: { manifest: null },
|
||||
children: ["head", "body"]
|
||||
},
|
||||
i: s,
|
||||
iframe: {
|
||||
attrs: {
|
||||
src: null, srcdoc: null, name: null, width: null, height: null,
|
||||
sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"],
|
||||
seamless: ["", "seamless"]
|
||||
}
|
||||
},
|
||||
img: {
|
||||
attrs: {
|
||||
alt: null, src: null, ismap: null, usemap: null, width: null, height: null,
|
||||
crossorigin: ["anonymous", "use-credentials"]
|
||||
}
|
||||
},
|
||||
input: {
|
||||
attrs: {
|
||||
alt: null, dirname: null, form: null, formaction: null,
|
||||
height: null, list: null, max: null, maxlength: null, min: null,
|
||||
name: null, pattern: null, placeholder: null, size: null, src: null,
|
||||
step: null, value: null, width: null,
|
||||
accept: ["audio/*", "video/*", "image/*"],
|
||||
autocomplete: ["on", "off"],
|
||||
autofocus: ["", "autofocus"],
|
||||
checked: ["", "checked"],
|
||||
disabled: ["", "disabled"],
|
||||
formenctype: encs,
|
||||
formmethod: methods,
|
||||
formnovalidate: ["", "novalidate"],
|
||||
formtarget: targets,
|
||||
multiple: ["", "multiple"],
|
||||
readonly: ["", "readonly"],
|
||||
required: ["", "required"],
|
||||
type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month",
|
||||
"week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
|
||||
"file", "submit", "image", "reset", "button"]
|
||||
}
|
||||
},
|
||||
ins: { attrs: { cite: null, datetime: null } },
|
||||
kbd: s,
|
||||
keygen: {
|
||||
attrs: {
|
||||
challenge: null, form: null, name: null,
|
||||
autofocus: ["", "autofocus"],
|
||||
disabled: ["", "disabled"],
|
||||
keytype: ["RSA"]
|
||||
}
|
||||
},
|
||||
label: { attrs: { "for": null, form: null } },
|
||||
legend: s,
|
||||
li: { attrs: { value: null } },
|
||||
link: {
|
||||
attrs: {
|
||||
href: null, type: null,
|
||||
hreflang: langs,
|
||||
media: media,
|
||||
sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"]
|
||||
}
|
||||
},
|
||||
map: { attrs: { name: null } },
|
||||
mark: s,
|
||||
menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } },
|
||||
meta: {
|
||||
attrs: {
|
||||
content: null,
|
||||
charset: charsets,
|
||||
name: ["viewport", "application-name", "author", "description", "generator", "keywords"],
|
||||
"http-equiv": ["content-language", "content-type", "default-style", "refresh"]
|
||||
}
|
||||
},
|
||||
meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } },
|
||||
nav: s,
|
||||
noframes: s,
|
||||
noscript: s,
|
||||
object: {
|
||||
attrs: {
|
||||
data: null, type: null, name: null, usemap: null, form: null, width: null, height: null,
|
||||
typemustmatch: ["", "typemustmatch"]
|
||||
}
|
||||
},
|
||||
ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } },
|
||||
optgroup: { attrs: { disabled: ["", "disabled"], label: null } },
|
||||
option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } },
|
||||
output: { attrs: { "for": null, form: null, name: null } },
|
||||
p: s,
|
||||
param: { attrs: { name: null, value: null } },
|
||||
pre: s,
|
||||
progress: { attrs: { value: null, max: null } },
|
||||
q: { attrs: { cite: null } },
|
||||
rp: s,
|
||||
rt: s,
|
||||
ruby: s,
|
||||
s: s,
|
||||
samp: s,
|
||||
script: {
|
||||
attrs: {
|
||||
type: ["text/javascript"],
|
||||
src: null,
|
||||
async: ["", "async"],
|
||||
defer: ["", "defer"],
|
||||
charset: charsets
|
||||
}
|
||||
},
|
||||
section: s,
|
||||
select: {
|
||||
attrs: {
|
||||
form: null, name: null, size: null,
|
||||
autofocus: ["", "autofocus"],
|
||||
disabled: ["", "disabled"],
|
||||
multiple: ["", "multiple"]
|
||||
}
|
||||
},
|
||||
small: s,
|
||||
source: { attrs: { src: null, type: null, media: null } },
|
||||
span: s,
|
||||
strike: s,
|
||||
strong: s,
|
||||
style: {
|
||||
attrs: {
|
||||
type: ["text/css"],
|
||||
media: media,
|
||||
scoped: null
|
||||
}
|
||||
},
|
||||
sub: s,
|
||||
summary: s,
|
||||
sup: s,
|
||||
table: s,
|
||||
tbody: s,
|
||||
td: { attrs: { colspan: null, rowspan: null, headers: null } },
|
||||
textarea: {
|
||||
attrs: {
|
||||
dirname: null, form: null, maxlength: null, name: null, placeholder: null,
|
||||
rows: null, cols: null,
|
||||
autofocus: ["", "autofocus"],
|
||||
disabled: ["", "disabled"],
|
||||
readonly: ["", "readonly"],
|
||||
required: ["", "required"],
|
||||
wrap: ["soft", "hard"]
|
||||
}
|
||||
},
|
||||
tfoot: s,
|
||||
th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } },
|
||||
thead: s,
|
||||
time: { attrs: { datetime: null } },
|
||||
title: s,
|
||||
tr: s,
|
||||
track: {
|
||||
attrs: {
|
||||
src: null, label: null, "default": null,
|
||||
kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"],
|
||||
srclang: langs
|
||||
}
|
||||
},
|
||||
tt: s,
|
||||
u: s,
|
||||
ul: s,
|
||||
"var": s,
|
||||
video: {
|
||||
attrs: {
|
||||
src: null, poster: null, width: null, height: null,
|
||||
crossorigin: ["anonymous", "use-credentials"],
|
||||
preload: ["auto", "metadata", "none"],
|
||||
autoplay: ["", "autoplay"],
|
||||
mediagroup: ["movie"],
|
||||
muted: ["", "muted"],
|
||||
controls: ["", "controls"]
|
||||
}
|
||||
},
|
||||
wbr: s
|
||||
};
|
||||
|
||||
var globalAttrs = {
|
||||
accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||
"class": null,
|
||||
contenteditable: ["true", "false"],
|
||||
contextmenu: null,
|
||||
dir: ["ltr", "rtl", "auto"],
|
||||
draggable: ["true", "false", "auto"],
|
||||
dropzone: ["copy", "move", "link", "string:", "file:"],
|
||||
hidden: ["hidden"],
|
||||
id: null,
|
||||
inert: ["inert"],
|
||||
itemid: null,
|
||||
itemprop: null,
|
||||
itemref: null,
|
||||
itemscope: ["itemscope"],
|
||||
itemtype: null,
|
||||
lang: ["en", "es"],
|
||||
spellcheck: ["true", "false"],
|
||||
style: null,
|
||||
tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||
title: null,
|
||||
translate: ["yes", "no"],
|
||||
onclick: null,
|
||||
rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"]
|
||||
};
|
||||
function populate(obj) {
|
||||
for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))
|
||||
obj.attrs[attr] = globalAttrs[attr];
|
||||
}
|
||||
|
||||
populate(s);
|
||||
for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)
|
||||
populate(data[tag]);
|
||||
|
||||
CodeMirror.htmlSchema = data;
|
||||
function htmlHint(cm, options) {
|
||||
var local = {schemaInfo: data};
|
||||
if (options) for (var opt in options) local[opt] = options[opt];
|
||||
return CodeMirror.hint.xml(cm, local);
|
||||
}
|
||||
CodeMirror.registerHelper("hint", "html", htmlHint);
|
||||
});
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function forEach(arr, f) {
|
||||
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
||||
}
|
||||
|
||||
function arrayContains(arr, item) {
|
||||
if (!Array.prototype.indexOf) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (arr[i] === item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return arr.indexOf(item) != -1;
|
||||
}
|
||||
|
||||
function scriptHint(editor, keywords, getToken, options) {
|
||||
// Find the token at the cursor
|
||||
var cur = editor.getCursor(), token = getToken(editor, cur);
|
||||
if (/\b(?:string|comment)\b/.test(token.type)) return;
|
||||
token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
|
||||
|
||||
// If it's not a 'word-style' token, ignore the token.
|
||||
if (!/^[\w$_]*$/.test(token.string)) {
|
||||
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
|
||||
type: token.string == "." ? "property" : null};
|
||||
} else if (token.end > cur.ch) {
|
||||
token.end = cur.ch;
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
|
||||
var tprop = token;
|
||||
// If it is a property, find out what it is a property of.
|
||||
while (tprop.type == "property") {
|
||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||
if (tprop.string != ".") return;
|
||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||
if (!context) var context = [];
|
||||
context.push(tprop);
|
||||
}
|
||||
return {list: getCompletions(token, context, keywords, options),
|
||||
from: Pos(cur.line, token.start),
|
||||
to: Pos(cur.line, token.end)};
|
||||
}
|
||||
|
||||
function javascriptHint(editor, options) {
|
||||
return scriptHint(editor, javascriptKeywords,
|
||||
function (e, cur) {return e.getTokenAt(cur);},
|
||||
options);
|
||||
};
|
||||
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
||||
|
||||
function getCoffeeScriptToken(editor, cur) {
|
||||
// This getToken, it is for coffeescript, imitates the behavior of
|
||||
// getTokenAt method in javascript.js, that is, returning "property"
|
||||
// type and treat "." as indepenent token.
|
||||
var token = editor.getTokenAt(cur);
|
||||
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
|
||||
token.end = token.start;
|
||||
token.string = '.';
|
||||
token.type = "property";
|
||||
}
|
||||
else if (/^\.[\w$_]*$/.test(token.string)) {
|
||||
token.type = "property";
|
||||
token.start++;
|
||||
token.string = token.string.replace(/\./, '');
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function coffeescriptHint(editor, options) {
|
||||
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
|
||||
}
|
||||
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
|
||||
|
||||
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
|
||||
"toUpperCase toLowerCase split concat match replace search").split(" ");
|
||||
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
|
||||
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
|
||||
var funcProps = "prototype apply call bind".split(" ");
|
||||
var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
|
||||
"if in instanceof new null return switch throw true try typeof var void while with").split(" ");
|
||||
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
||||
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
||||
|
||||
function forAllProps(obj, callback) {
|
||||
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||
for (var name in obj) callback(name)
|
||||
} else {
|
||||
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
||||
Object.getOwnPropertyNames(o).forEach(callback)
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletions(token, context, keywords, options) {
|
||||
var found = [], start = token.string, global = options && options.globalScope || window;
|
||||
function maybeAdd(str) {
|
||||
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
|
||||
}
|
||||
function gatherCompletions(obj) {
|
||||
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
||||
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
||||
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
||||
forAllProps(obj, maybeAdd)
|
||||
}
|
||||
|
||||
if (context && context.length) {
|
||||
// If this is a property, see if it belongs to some object we can
|
||||
// find in the current environment.
|
||||
var obj = context.pop(), base;
|
||||
if (obj.type && obj.type.indexOf("variable") === 0) {
|
||||
if (options && options.additionalContext)
|
||||
base = options.additionalContext[obj.string];
|
||||
if (!options || options.useGlobalScope !== false)
|
||||
base = base || global[obj.string];
|
||||
} else if (obj.type == "string") {
|
||||
base = "";
|
||||
} else if (obj.type == "atom") {
|
||||
base = 1;
|
||||
} else if (obj.type == "function") {
|
||||
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
|
||||
(typeof global.jQuery == 'function'))
|
||||
base = global.jQuery();
|
||||
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
|
||||
base = global._();
|
||||
}
|
||||
while (base != null && context.length)
|
||||
base = base[context.pop().string];
|
||||
if (base != null) gatherCompletions(base);
|
||||
} else {
|
||||
// If not, just look in the global object and any local scope
|
||||
// (reading into JS mode internals to get at the local and global variables)
|
||||
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
|
||||
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
|
||||
if (!options || options.useGlobalScope !== false)
|
||||
gatherCompletions(global);
|
||||
forEach(keywords, maybeAdd);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
.CodeMirror-hints {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
|
||||
-webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
border-radius: 3px;
|
||||
border: 1px solid silver;
|
||||
|
||||
background: white;
|
||||
font-size: 90%;
|
||||
font-family: monospace;
|
||||
|
||||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
white-space: pre;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.CodeMirror-hint-active {
|
||||
background: #08f;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -0,0 +1,438 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
|
||||
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
|
||||
|
||||
// This is the old interface, kept around for now to stay
|
||||
// backwards-compatible.
|
||||
CodeMirror.showHint = function(cm, getHints, options) {
|
||||
if (!getHints) return cm.showHint(options);
|
||||
if (options && options.async) getHints.async = true;
|
||||
var newOpts = {hint: getHints};
|
||||
if (options) for (var prop in options) newOpts[prop] = options[prop];
|
||||
return cm.showHint(newOpts);
|
||||
};
|
||||
|
||||
CodeMirror.defineExtension("showHint", function(options) {
|
||||
options = parseOptions(this, this.getCursor("start"), options);
|
||||
var selections = this.listSelections()
|
||||
if (selections.length > 1) return;
|
||||
// By default, don't allow completion when something is selected.
|
||||
// A hint function can have a `supportsSelection` property to
|
||||
// indicate that it can handle selections.
|
||||
if (this.somethingSelected()) {
|
||||
if (!options.hint.supportsSelection) return;
|
||||
// Don't try with cross-line selections
|
||||
for (var i = 0; i < selections.length; i++)
|
||||
if (selections[i].head.line != selections[i].anchor.line) return;
|
||||
}
|
||||
|
||||
if (this.state.completionActive) this.state.completionActive.close();
|
||||
var completion = this.state.completionActive = new Completion(this, options);
|
||||
if (!completion.options.hint) return;
|
||||
|
||||
CodeMirror.signal(this, "startCompletion", this);
|
||||
completion.update(true);
|
||||
});
|
||||
|
||||
function Completion(cm, options) {
|
||||
this.cm = cm;
|
||||
this.options = options;
|
||||
this.widget = null;
|
||||
this.debounce = 0;
|
||||
this.tick = 0;
|
||||
this.startPos = this.cm.getCursor("start");
|
||||
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
|
||||
|
||||
var self = this;
|
||||
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
|
||||
}
|
||||
|
||||
var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
|
||||
return setTimeout(fn, 1000/60);
|
||||
};
|
||||
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
|
||||
|
||||
Completion.prototype = {
|
||||
close: function() {
|
||||
if (!this.active()) return;
|
||||
this.cm.state.completionActive = null;
|
||||
this.tick = null;
|
||||
this.cm.off("cursorActivity", this.activityFunc);
|
||||
|
||||
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
|
||||
if (this.widget) this.widget.close();
|
||||
CodeMirror.signal(this.cm, "endCompletion", this.cm);
|
||||
},
|
||||
|
||||
active: function() {
|
||||
return this.cm.state.completionActive == this;
|
||||
},
|
||||
|
||||
pick: function(data, i) {
|
||||
var completion = data.list[i];
|
||||
if (completion.hint) completion.hint(this.cm, data, completion);
|
||||
else this.cm.replaceRange(getText(completion), completion.from || data.from,
|
||||
completion.to || data.to, "complete");
|
||||
CodeMirror.signal(data, "pick", completion);
|
||||
this.close();
|
||||
},
|
||||
|
||||
cursorActivity: function() {
|
||||
if (this.debounce) {
|
||||
cancelAnimationFrame(this.debounce);
|
||||
this.debounce = 0;
|
||||
}
|
||||
|
||||
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
|
||||
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
|
||||
pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
|
||||
(pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
|
||||
this.close();
|
||||
} else {
|
||||
var self = this;
|
||||
this.debounce = requestAnimationFrame(function() {self.update();});
|
||||
if (this.widget) this.widget.disable();
|
||||
}
|
||||
},
|
||||
|
||||
update: function(first) {
|
||||
if (this.tick == null) return
|
||||
var self = this, myTick = ++this.tick
|
||||
fetchHints(this.options.hint, this.cm, this.options, function(data) {
|
||||
if (self.tick == myTick) self.finishUpdate(data, first)
|
||||
})
|
||||
},
|
||||
|
||||
finishUpdate: function(data, first) {
|
||||
if (this.data) CodeMirror.signal(this.data, "update");
|
||||
|
||||
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
|
||||
if (this.widget) this.widget.close();
|
||||
|
||||
if (data && this.data && isNewCompletion(this.data, data)) return;
|
||||
this.data = data;
|
||||
|
||||
if (data && data.list.length) {
|
||||
if (picked && data.list.length == 1) {
|
||||
this.pick(data, 0);
|
||||
} else {
|
||||
this.widget = new Widget(this, data);
|
||||
CodeMirror.signal(data, "shown");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function isNewCompletion(old, nw) {
|
||||
var moved = CodeMirror.cmpPos(nw.from, old.from)
|
||||
return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
|
||||
}
|
||||
|
||||
function parseOptions(cm, pos, options) {
|
||||
var editor = cm.options.hintOptions;
|
||||
var out = {};
|
||||
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
|
||||
if (editor) for (var prop in editor)
|
||||
if (editor[prop] !== undefined) out[prop] = editor[prop];
|
||||
if (options) for (var prop in options)
|
||||
if (options[prop] !== undefined) out[prop] = options[prop];
|
||||
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
|
||||
return out;
|
||||
}
|
||||
|
||||
function getText(completion) {
|
||||
if (typeof completion == "string") return completion;
|
||||
else return completion.text;
|
||||
}
|
||||
|
||||
function buildKeyMap(completion, handle) {
|
||||
var baseMap = {
|
||||
Up: function() {handle.moveFocus(-1);},
|
||||
Down: function() {handle.moveFocus(1);},
|
||||
PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
|
||||
PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
|
||||
Home: function() {handle.setFocus(0);},
|
||||
End: function() {handle.setFocus(handle.length - 1);},
|
||||
Enter: handle.pick,
|
||||
Tab: handle.pick,
|
||||
Esc: handle.close
|
||||
};
|
||||
var custom = completion.options.customKeys;
|
||||
var ourMap = custom ? {} : baseMap;
|
||||
function addBinding(key, val) {
|
||||
var bound;
|
||||
if (typeof val != "string")
|
||||
bound = function(cm) { return val(cm, handle); };
|
||||
// This mechanism is deprecated
|
||||
else if (baseMap.hasOwnProperty(val))
|
||||
bound = baseMap[val];
|
||||
else
|
||||
bound = val;
|
||||
ourMap[key] = bound;
|
||||
}
|
||||
if (custom)
|
||||
for (var key in custom) if (custom.hasOwnProperty(key))
|
||||
addBinding(key, custom[key]);
|
||||
var extra = completion.options.extraKeys;
|
||||
if (extra)
|
||||
for (var key in extra) if (extra.hasOwnProperty(key))
|
||||
addBinding(key, extra[key]);
|
||||
return ourMap;
|
||||
}
|
||||
|
||||
function getHintElement(hintsElement, el) {
|
||||
while (el && el != hintsElement) {
|
||||
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
|
||||
el = el.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function Widget(completion, data) {
|
||||
this.completion = completion;
|
||||
this.data = data;
|
||||
this.picked = false;
|
||||
var widget = this, cm = completion.cm;
|
||||
|
||||
var hints = this.hints = document.createElement("ul");
|
||||
hints.className = "CodeMirror-hints";
|
||||
this.selectedHint = data.selectedHint || 0;
|
||||
|
||||
var completions = data.list;
|
||||
for (var i = 0; i < completions.length; ++i) {
|
||||
var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
|
||||
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
|
||||
if (cur.className != null) className = cur.className + " " + className;
|
||||
elt.className = className;
|
||||
if (cur.render) cur.render(elt, data, cur);
|
||||
else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
|
||||
elt.hintId = i;
|
||||
}
|
||||
|
||||
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
|
||||
var left = pos.left, top = pos.bottom, below = true;
|
||||
hints.style.left = left + "px";
|
||||
hints.style.top = top + "px";
|
||||
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
|
||||
var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
|
||||
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
|
||||
(completion.options.container || document.body).appendChild(hints);
|
||||
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
|
||||
var scrolls = hints.scrollHeight > hints.clientHeight + 1
|
||||
var startScroll = cm.getScrollInfo();
|
||||
|
||||
if (overlapY > 0) {
|
||||
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
|
||||
if (curTop - height > 0) { // Fits above cursor
|
||||
hints.style.top = (top = pos.top - height) + "px";
|
||||
below = false;
|
||||
} else if (height > winH) {
|
||||
hints.style.height = (winH - 5) + "px";
|
||||
hints.style.top = (top = pos.bottom - box.top) + "px";
|
||||
var cursor = cm.getCursor();
|
||||
if (data.from.ch != cursor.ch) {
|
||||
pos = cm.cursorCoords(cursor);
|
||||
hints.style.left = (left = pos.left) + "px";
|
||||
box = hints.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
var overlapX = box.right - winW;
|
||||
if (overlapX > 0) {
|
||||
if (box.right - box.left > winW) {
|
||||
hints.style.width = (winW - 5) + "px";
|
||||
overlapX -= (box.right - box.left) - winW;
|
||||
}
|
||||
hints.style.left = (left = pos.left - overlapX) + "px";
|
||||
}
|
||||
if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
|
||||
node.style.paddingRight = cm.display.nativeBarWidth + "px"
|
||||
|
||||
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
|
||||
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
|
||||
setFocus: function(n) { widget.changeActive(n); },
|
||||
menuSize: function() { return widget.screenAmount(); },
|
||||
length: completions.length,
|
||||
close: function() { completion.close(); },
|
||||
pick: function() { widget.pick(); },
|
||||
data: data
|
||||
}));
|
||||
|
||||
if (completion.options.closeOnUnfocus) {
|
||||
var closingOnBlur;
|
||||
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
|
||||
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
|
||||
}
|
||||
|
||||
cm.on("scroll", this.onScroll = function() {
|
||||
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
|
||||
var newTop = top + startScroll.top - curScroll.top;
|
||||
var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
|
||||
if (!below) point += hints.offsetHeight;
|
||||
if (point <= editor.top || point >= editor.bottom) return completion.close();
|
||||
hints.style.top = newTop + "px";
|
||||
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "dblclick", function(e) {
|
||||
var t = getHintElement(hints, e.target || e.srcElement);
|
||||
if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "click", function(e) {
|
||||
var t = getHintElement(hints, e.target || e.srcElement);
|
||||
if (t && t.hintId != null) {
|
||||
widget.changeActive(t.hintId);
|
||||
if (completion.options.completeOnSingleClick) widget.pick();
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.on(hints, "mousedown", function() {
|
||||
setTimeout(function(){cm.focus();}, 20);
|
||||
});
|
||||
|
||||
CodeMirror.signal(data, "select", completions[0], hints.firstChild);
|
||||
return true;
|
||||
}
|
||||
|
||||
Widget.prototype = {
|
||||
close: function() {
|
||||
if (this.completion.widget != this) return;
|
||||
this.completion.widget = null;
|
||||
this.hints.parentNode.removeChild(this.hints);
|
||||
this.completion.cm.removeKeyMap(this.keyMap);
|
||||
|
||||
var cm = this.completion.cm;
|
||||
if (this.completion.options.closeOnUnfocus) {
|
||||
cm.off("blur", this.onBlur);
|
||||
cm.off("focus", this.onFocus);
|
||||
}
|
||||
cm.off("scroll", this.onScroll);
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
this.completion.cm.removeKeyMap(this.keyMap);
|
||||
var widget = this;
|
||||
this.keyMap = {Enter: function() { widget.picked = true; }};
|
||||
this.completion.cm.addKeyMap(this.keyMap);
|
||||
},
|
||||
|
||||
pick: function() {
|
||||
this.completion.pick(this.data, this.selectedHint);
|
||||
},
|
||||
|
||||
changeActive: function(i, avoidWrap) {
|
||||
if (i >= this.data.list.length)
|
||||
i = avoidWrap ? this.data.list.length - 1 : 0;
|
||||
else if (i < 0)
|
||||
i = avoidWrap ? 0 : this.data.list.length - 1;
|
||||
if (this.selectedHint == i) return;
|
||||
var node = this.hints.childNodes[this.selectedHint];
|
||||
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
|
||||
node = this.hints.childNodes[this.selectedHint = i];
|
||||
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
|
||||
if (node.offsetTop < this.hints.scrollTop)
|
||||
this.hints.scrollTop = node.offsetTop - 3;
|
||||
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
|
||||
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
|
||||
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
|
||||
},
|
||||
|
||||
screenAmount: function() {
|
||||
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
|
||||
}
|
||||
};
|
||||
|
||||
function applicableHelpers(cm, helpers) {
|
||||
if (!cm.somethingSelected()) return helpers
|
||||
var result = []
|
||||
for (var i = 0; i < helpers.length; i++)
|
||||
if (helpers[i].supportsSelection) result.push(helpers[i])
|
||||
return result
|
||||
}
|
||||
|
||||
function fetchHints(hint, cm, options, callback) {
|
||||
if (hint.async) {
|
||||
hint(cm, callback, options)
|
||||
} else {
|
||||
var result = hint(cm, options)
|
||||
if (result && result.then) result.then(callback)
|
||||
else callback(result)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAutoHints(cm, pos) {
|
||||
var helpers = cm.getHelpers(pos, "hint"), words
|
||||
if (helpers.length) {
|
||||
var resolved = function(cm, callback, options) {
|
||||
var app = applicableHelpers(cm, helpers);
|
||||
function run(i) {
|
||||
if (i == app.length) return callback(null)
|
||||
fetchHints(app[i], cm, options, function(result) {
|
||||
if (result && result.list.length > 0) callback(result)
|
||||
else run(i + 1)
|
||||
})
|
||||
}
|
||||
run(0)
|
||||
}
|
||||
resolved.async = true
|
||||
resolved.supportsSelection = true
|
||||
return resolved
|
||||
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
|
||||
return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
|
||||
} else if (CodeMirror.hint.anyword) {
|
||||
return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
|
||||
} else {
|
||||
return function() {}
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("hint", "auto", {
|
||||
resolve: resolveAutoHints
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
|
||||
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
|
||||
var to = CodeMirror.Pos(cur.line, token.end);
|
||||
if (token.string && /\w/.test(token.string[token.string.length - 1])) {
|
||||
var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
|
||||
} else {
|
||||
var term = "", from = to;
|
||||
}
|
||||
var found = [];
|
||||
for (var i = 0; i < options.words.length; i++) {
|
||||
var word = options.words[i];
|
||||
if (word.slice(0, term.length) == term)
|
||||
found.push(word);
|
||||
}
|
||||
|
||||
if (found.length) return {list: found, from: from, to: to};
|
||||
});
|
||||
|
||||
CodeMirror.commands.autocomplete = CodeMirror.showHint;
|
||||
|
||||
var defaultOptions = {
|
||||
hint: CodeMirror.hint.auto,
|
||||
completeSingle: true,
|
||||
alignWithWord: true,
|
||||
closeCharacters: /[\s()\[\]{};:>,]/,
|
||||
closeOnUnfocus: true,
|
||||
completeOnSingleClick: true,
|
||||
container: null,
|
||||
customKeys: null,
|
||||
extraKeys: null
|
||||
};
|
||||
|
||||
CodeMirror.defineOption("hintOptions", null);
|
||||
});
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var tables;
|
||||
var defaultTable;
|
||||
var keywords;
|
||||
var identifierQuote;
|
||||
var CONS = {
|
||||
QUERY_DIV: ";",
|
||||
ALIAS_KEYWORD: "AS"
|
||||
};
|
||||
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
|
||||
|
||||
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
|
||||
|
||||
function getKeywords(editor) {
|
||||
var mode = editor.doc.modeOption;
|
||||
if (mode === "sql") mode = "text/x-sql";
|
||||
return CodeMirror.resolveMode(mode).keywords;
|
||||
}
|
||||
|
||||
function getIdentifierQuote(editor) {
|
||||
var mode = editor.doc.modeOption;
|
||||
if (mode === "sql") mode = "text/x-sql";
|
||||
return CodeMirror.resolveMode(mode).identifierQuote || "`";
|
||||
}
|
||||
|
||||
function getText(item) {
|
||||
return typeof item == "string" ? item : item.text;
|
||||
}
|
||||
|
||||
function wrapTable(name, value) {
|
||||
if (isArray(value)) value = {columns: value}
|
||||
if (!value.text) value.text = name
|
||||
return value
|
||||
}
|
||||
|
||||
function parseTables(input) {
|
||||
var result = {}
|
||||
if (isArray(input)) {
|
||||
for (var i = input.length - 1; i >= 0; i--) {
|
||||
var item = input[i]
|
||||
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
|
||||
}
|
||||
} else if (input) {
|
||||
for (var name in input)
|
||||
result[name.toUpperCase()] = wrapTable(name, input[name])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getTable(name) {
|
||||
return tables[name.toUpperCase()]
|
||||
}
|
||||
|
||||
function shallowClone(object) {
|
||||
var result = {};
|
||||
for (var key in object) if (object.hasOwnProperty(key))
|
||||
result[key] = object[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
function match(string, word) {
|
||||
var len = string.length;
|
||||
var sub = getText(word).substr(0, len);
|
||||
return string.toUpperCase() === sub.toUpperCase();
|
||||
}
|
||||
|
||||
function addMatches(result, search, wordlist, formatter) {
|
||||
if (isArray(wordlist)) {
|
||||
for (var i = 0; i < wordlist.length; i++)
|
||||
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
|
||||
} else {
|
||||
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
|
||||
var val = wordlist[word]
|
||||
if (!val || val === true)
|
||||
val = word
|
||||
else
|
||||
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
|
||||
if (match(search, val)) result.push(formatter(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cleanName(name) {
|
||||
// Get rid name from identifierQuote and preceding dot(.)
|
||||
if (name.charAt(0) == ".") {
|
||||
name = name.substr(1);
|
||||
}
|
||||
// replace doublicated identifierQuotes with single identifierQuotes
|
||||
// and remove single identifierQuotes
|
||||
var nameParts = name.split(identifierQuote+identifierQuote);
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
|
||||
return nameParts.join(identifierQuote);
|
||||
}
|
||||
|
||||
function insertIdentifierQuotes(name) {
|
||||
var nameParts = getText(name).split(".");
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = identifierQuote +
|
||||
// doublicate identifierQuotes
|
||||
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
|
||||
identifierQuote;
|
||||
var escaped = nameParts.join(".");
|
||||
if (typeof name == "string") return escaped;
|
||||
name = shallowClone(name);
|
||||
name.text = escaped;
|
||||
return name;
|
||||
}
|
||||
|
||||
function nameCompletion(cur, token, result, editor) {
|
||||
// Try to complete table, column names and return start position of completion
|
||||
var useIdentifierQuotes = false;
|
||||
var nameParts = [];
|
||||
var start = token.start;
|
||||
var cont = true;
|
||||
while (cont) {
|
||||
cont = (token.string.charAt(0) == ".");
|
||||
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
|
||||
|
||||
start = token.start;
|
||||
nameParts.unshift(cleanName(token.string));
|
||||
|
||||
token = editor.getTokenAt(Pos(cur.line, token.start));
|
||||
if (token.string == ".") {
|
||||
cont = true;
|
||||
token = editor.getTokenAt(Pos(cur.line, token.start));
|
||||
}
|
||||
}
|
||||
|
||||
// Try to complete table names
|
||||
var string = nameParts.join(".");
|
||||
addMatches(result, string, tables, function(w) {
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns from defaultTable
|
||||
addMatches(result, string, defaultTable, function(w) {
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns
|
||||
string = nameParts.pop();
|
||||
var table = nameParts.join(".");
|
||||
|
||||
var alias = false;
|
||||
var aliasTable = table;
|
||||
// Check if table is available. If not, find table by Alias
|
||||
if (!getTable(table)) {
|
||||
var oldTable = table;
|
||||
table = findTableByAlias(table, editor);
|
||||
if (table !== oldTable) alias = true;
|
||||
}
|
||||
|
||||
var columns = getTable(table);
|
||||
if (columns && columns.columns)
|
||||
columns = columns.columns;
|
||||
|
||||
if (columns) {
|
||||
addMatches(result, string, columns, function(w) {
|
||||
var tableInsert = table;
|
||||
if (alias == true) tableInsert = aliasTable;
|
||||
if (typeof w == "string") {
|
||||
w = tableInsert + "." + w;
|
||||
} else {
|
||||
w = shallowClone(w);
|
||||
w.text = tableInsert + "." + w.text;
|
||||
}
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
function eachWord(lineText, f) {
|
||||
if (!lineText) return;
|
||||
var excepted = /[,;]/g;
|
||||
var words = lineText.split(" ");
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
f(words[i]?words[i].replace(excepted, '') : '');
|
||||
}
|
||||
}
|
||||
|
||||
function findTableByAlias(alias, editor) {
|
||||
var doc = editor.doc;
|
||||
var fullQuery = doc.getValue();
|
||||
var aliasUpperCase = alias.toUpperCase();
|
||||
var previousWord = "";
|
||||
var table = "";
|
||||
var separator = [];
|
||||
var validRange = {
|
||||
start: Pos(0, 0),
|
||||
end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
|
||||
};
|
||||
|
||||
//add separator
|
||||
var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
|
||||
while(indexOfSeparator != -1) {
|
||||
separator.push(doc.posFromIndex(indexOfSeparator));
|
||||
indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
|
||||
}
|
||||
separator.unshift(Pos(0, 0));
|
||||
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
|
||||
|
||||
//find valid range
|
||||
var prevItem = null;
|
||||
var current = editor.getCursor()
|
||||
for (var i = 0; i < separator.length; i++) {
|
||||
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
|
||||
validRange = {start: prevItem, end: separator[i]};
|
||||
break;
|
||||
}
|
||||
prevItem = separator[i];
|
||||
}
|
||||
|
||||
var query = doc.getRange(validRange.start, validRange.end, false);
|
||||
|
||||
for (var i = 0; i < query.length; i++) {
|
||||
var lineText = query[i];
|
||||
eachWord(lineText, function(word) {
|
||||
var wordUpperCase = word.toUpperCase();
|
||||
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
|
||||
table = previousWord;
|
||||
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
|
||||
previousWord = word;
|
||||
});
|
||||
if (table) break;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
|
||||
tables = parseTables(options && options.tables)
|
||||
var defaultTableName = options && options.defaultTable;
|
||||
var disableKeywords = options && options.disableKeywords;
|
||||
defaultTable = defaultTableName && getTable(defaultTableName);
|
||||
keywords = getKeywords(editor);
|
||||
identifierQuote = getIdentifierQuote(editor);
|
||||
|
||||
if (defaultTableName && !defaultTable)
|
||||
defaultTable = findTableByAlias(defaultTableName, editor);
|
||||
|
||||
defaultTable = defaultTable || [];
|
||||
|
||||
if (defaultTable.columns)
|
||||
defaultTable = defaultTable.columns;
|
||||
|
||||
var cur = editor.getCursor();
|
||||
var result = [];
|
||||
var token = editor.getTokenAt(cur), start, end, search;
|
||||
if (token.end > cur.ch) {
|
||||
token.end = cur.ch;
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
|
||||
if (token.string.match(/^[.`"\w@]\w*$/)) {
|
||||
search = token.string;
|
||||
start = token.start;
|
||||
end = token.end;
|
||||
} else {
|
||||
start = end = cur.ch;
|
||||
search = "";
|
||||
}
|
||||
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
|
||||
start = nameCompletion(cur, token, result, editor);
|
||||
} else {
|
||||
addMatches(result, search, tables, function(w) {return w;});
|
||||
addMatches(result, search, defaultTable, function(w) {return w;});
|
||||
if (!disableKeywords)
|
||||
addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
|
||||
}
|
||||
|
||||
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function getHints(cm, options) {
|
||||
var tags = options && options.schemaInfo;
|
||||
var quote = (options && options.quoteChar) || '"';
|
||||
if (!tags) return;
|
||||
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
|
||||
if (token.end > cur.ch) {
|
||||
token.end = cur.ch;
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
|
||||
if (inner.mode.name != "xml") return;
|
||||
var result = [], replaceToken = false, prefix;
|
||||
var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
|
||||
var tagName = tag && /^\w/.test(token.string), tagStart;
|
||||
|
||||
if (tagName) {
|
||||
var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
|
||||
var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
|
||||
if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1);
|
||||
} else if (tag && token.string == "<") {
|
||||
tagType = "open";
|
||||
} else if (tag && token.string == "</") {
|
||||
tagType = "close";
|
||||
}
|
||||
|
||||
if (!tag && !inner.state.tagName || tagType) {
|
||||
if (tagName)
|
||||
prefix = token.string;
|
||||
replaceToken = tagType;
|
||||
var cx = inner.state.context, curTag = cx && tags[cx.tagName];
|
||||
var childList = cx ? curTag && curTag.children : tags["!top"];
|
||||
if (childList && tagType != "close") {
|
||||
for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
|
||||
result.push("<" + childList[i]);
|
||||
} else if (tagType != "close") {
|
||||
for (var name in tags)
|
||||
if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
|
||||
result.push("<" + name);
|
||||
}
|
||||
if (cx && (!prefix || tagType == "close" && cx.tagName.lastIndexOf(prefix, 0) == 0))
|
||||
result.push("</" + cx.tagName + ">");
|
||||
} else {
|
||||
// Attribute completion
|
||||
var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs;
|
||||
var globalAttrs = tags["!attrs"];
|
||||
if (!attrs && !globalAttrs) return;
|
||||
if (!attrs) {
|
||||
attrs = globalAttrs;
|
||||
} else if (globalAttrs) { // Combine tag-local and global attributes
|
||||
var set = {};
|
||||
for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];
|
||||
for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];
|
||||
attrs = set;
|
||||
}
|
||||
if (token.type == "string" || token.string == "=") { // A value
|
||||
var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),
|
||||
Pos(cur.line, token.type == "string" ? token.start : token.end));
|
||||
var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues;
|
||||
if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;
|
||||
if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget
|
||||
if (token.type == "string") {
|
||||
prefix = token.string;
|
||||
var n = 0;
|
||||
if (/['"]/.test(token.string.charAt(0))) {
|
||||
quote = token.string.charAt(0);
|
||||
prefix = token.string.slice(1);
|
||||
n++;
|
||||
}
|
||||
var len = token.string.length;
|
||||
if (/['"]/.test(token.string.charAt(len - 1))) {
|
||||
quote = token.string.charAt(len - 1);
|
||||
prefix = token.string.substr(n, len - 2);
|
||||
}
|
||||
replaceToken = true;
|
||||
}
|
||||
for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0)
|
||||
result.push(quote + atValues[i] + quote);
|
||||
} else { // An attribute name
|
||||
if (token.type == "attribute") {
|
||||
prefix = token.string;
|
||||
replaceToken = true;
|
||||
}
|
||||
for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0))
|
||||
result.push(attr);
|
||||
}
|
||||
}
|
||||
return {
|
||||
list: result,
|
||||
from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
|
||||
to: replaceToken ? Pos(cur.line, token.end) : cur
|
||||
};
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("hint", "xml", getHints);
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js
|
||||
|
||||
// declare global: coffeelint
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("lint", "coffeescript", function(text) {
|
||||
var found = [];
|
||||
var parseError = function(err) {
|
||||
var loc = err.lineNumber;
|
||||
found.push({from: CodeMirror.Pos(loc-1, 0),
|
||||
to: CodeMirror.Pos(loc, 0),
|
||||
severity: err.level,
|
||||
message: err.message});
|
||||
};
|
||||
try {
|
||||
var res = coffeelint.lint(text);
|
||||
for(var i = 0; i < res.length; i++) {
|
||||
parseError(res[i]);
|
||||
}
|
||||
} catch(e) {
|
||||
found.push({from: CodeMirror.Pos(e.location.first_line, 0),
|
||||
to: CodeMirror.Pos(e.location.last_line, e.location.last_column),
|
||||
severity: 'error',
|
||||
message: e.message});
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Depends on csslint.js from https://github.com/stubbornella/csslint
|
||||
|
||||
// declare global: CSSLint
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("lint", "css", function(text) {
|
||||
var found = [];
|
||||
if (!window.CSSLint) return found;
|
||||
var results = CSSLint.verify(text), messages = results.messages, message = null;
|
||||
for ( var i = 0; i < messages.length; i++) {
|
||||
message = messages[i];
|
||||
var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
|
||||
found.push({
|
||||
from: CodeMirror.Pos(startLine, startCol),
|
||||
to: CodeMirror.Pos(endLine, endCol),
|
||||
message: message.message,
|
||||
severity : message.type
|
||||
});
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Depends on htmlhint.js from http://htmlhint.com/js/htmlhint.js
|
||||
|
||||
// declare global: HTMLHint
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("htmlhint"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "htmlhint"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var defaultRules = {
|
||||
"tagname-lowercase": true,
|
||||
"attr-lowercase": true,
|
||||
"attr-value-double-quotes": true,
|
||||
"doctype-first": false,
|
||||
"tag-pair": true,
|
||||
"spec-char-escape": true,
|
||||
"id-unique": true,
|
||||
"src-not-empty": true,
|
||||
"attr-no-duplication": true
|
||||
};
|
||||
|
||||
CodeMirror.registerHelper("lint", "html", function(text, options) {
|
||||
var found = [];
|
||||
if (!window.HTMLHint) return found;
|
||||
var messages = HTMLHint.verify(text, options && options.rules || defaultRules);
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
var message = messages[i];
|
||||
var startLine = message.line - 1, endLine = message.line - 1, startCol = message.col - 1, endCol = message.col;
|
||||
found.push({
|
||||
from: CodeMirror.Pos(startLine, startCol),
|
||||
to: CodeMirror.Pos(endLine, endCol),
|
||||
message: message.message,
|
||||
severity : message.type
|
||||
});
|
||||
}
|
||||
return found;
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
// declare global: JSHINT
|
||||
|
||||
var bogus = [ "Dangerous comment" ];
|
||||
|
||||
var warnings = [ [ "Expected '{'",
|
||||
"Statement body should be inside '{ }' braces." ] ];
|
||||
|
||||
var errors = [ "Missing semicolon", "Extra comma", "Missing property name",
|
||||
"Unmatched ", " and instead saw", " is not defined",
|
||||
"Unclosed string", "Stopping, unable to continue" ];
|
||||
|
||||
function validator(text, options) {
|
||||
if (!window.JSHINT) return [];
|
||||
JSHINT(text, options, options.globals);
|
||||
var errors = JSHINT.data().errors, result = [];
|
||||
if (errors) parseErrors(errors, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("lint", "javascript", validator);
|
||||
|
||||
function cleanup(error) {
|
||||
// All problems are warnings by default
|
||||
fixWith(error, warnings, "warning", true);
|
||||
fixWith(error, errors, "error");
|
||||
|
||||
return isBogus(error) ? null : error;
|
||||
}
|
||||
|
||||
function fixWith(error, fixes, severity, force) {
|
||||
var description, fix, find, replace, found;
|
||||
|
||||
description = error.description;
|
||||
|
||||
for ( var i = 0; i < fixes.length; i++) {
|
||||
fix = fixes[i];
|
||||
find = (typeof fix === "string" ? fix : fix[0]);
|
||||
replace = (typeof fix === "string" ? null : fix[1]);
|
||||
found = description.indexOf(find) !== -1;
|
||||
|
||||
if (force || found) {
|
||||
error.severity = severity;
|
||||
}
|
||||
if (found && replace) {
|
||||
error.description = replace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isBogus(error) {
|
||||
var description = error.description;
|
||||
for ( var i = 0; i < bogus.length; i++) {
|
||||
if (description.indexOf(bogus[i]) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseErrors(errors, output) {
|
||||
for ( var i = 0; i < errors.length; i++) {
|
||||
var error = errors[i];
|
||||
if (error) {
|
||||
var linetabpositions, index;
|
||||
|
||||
linetabpositions = [];
|
||||
|
||||
// This next block is to fix a problem in jshint. Jshint
|
||||
// replaces
|
||||
// all tabs with spaces then performs some checks. The error
|
||||
// positions (character/space) are then reported incorrectly,
|
||||
// not taking the replacement step into account. Here we look
|
||||
// at the evidence line and try to adjust the character position
|
||||
// to the correct value.
|
||||
if (error.evidence) {
|
||||
// Tab positions are computed once per line and cached
|
||||
var tabpositions = linetabpositions[error.line];
|
||||
if (!tabpositions) {
|
||||
var evidence = error.evidence;
|
||||
tabpositions = [];
|
||||
// ugggh phantomjs does not like this
|
||||
// forEachChar(evidence, function(item, index) {
|
||||
Array.prototype.forEach.call(evidence, function(item,
|
||||
index) {
|
||||
if (item === '\t') {
|
||||
// First col is 1 (not 0) to match error
|
||||
// positions
|
||||
tabpositions.push(index + 1);
|
||||
}
|
||||
});
|
||||
linetabpositions[error.line] = tabpositions;
|
||||
}
|
||||
if (tabpositions.length > 0) {
|
||||
var pos = error.character;
|
||||
tabpositions.forEach(function(tabposition) {
|
||||
if (pos > tabposition) pos -= 1;
|
||||
});
|
||||
error.character = pos;
|
||||
}
|
||||
}
|
||||
|
||||
var start = error.character - 1, end = start + 1;
|
||||
if (error.evidence) {
|
||||
index = error.evidence.substring(start).search(/.\b/);
|
||||
if (index > -1) {
|
||||
end += index;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to format expected by validation service
|
||||
error.description = error.reason;// + "(jshint)";
|
||||
error.start = error.character;
|
||||
error.end = end;
|
||||
error = cleanup(error);
|
||||
|
||||
if (error)
|
||||
output.push({message: error.description,
|
||||
severity: error.severity,
|
||||
from: CodeMirror.Pos(error.line - 1, start),
|
||||
to: CodeMirror.Pos(error.line - 1, end)});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// Depends on jsonlint.js from https://github.com/zaach/jsonlint
|
||||
|
||||
// declare global: jsonlint
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.registerHelper("lint", "json", function(text) {
|
||||
var found = [];
|
||||
jsonlint.parseError = function(str, hash) {
|
||||
var loc = hash.loc;
|
||||
found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
||||
message: str});
|
||||
};
|
||||
try { jsonlint.parse(text); }
|
||||
catch(e) {}
|
||||
return found;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/* The lint marker gutter */
|
||||
.CodeMirror-lint-markers {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-tooltip {
|
||||
background-color: #ffd;
|
||||
border: 1px solid black;
|
||||
border-radius: 4px 4px 4px 4px;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
overflow: hidden;
|
||||
padding: 2px 5px;
|
||||
position: fixed;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100;
|
||||
max-width: 600px;
|
||||
opacity: 0;
|
||||
transition: opacity .4s;
|
||||
-moz-transition: opacity .4s;
|
||||
-webkit-transition: opacity .4s;
|
||||
-o-transition: opacity .4s;
|
||||
-ms-transition: opacity .4s;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
|
||||
background-position: left bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-error {
|
||||
background-image:
|
||||
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
|
||||
;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-mark-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
|
||||
padding-left: 18px;
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.CodeMirror-lint-marker-multiple {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
width: 100%; height: 100%;
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||
|
||||
function showTooltip(e, content) {
|
||||
var tt = document.createElement("div");
|
||||
tt.className = "CodeMirror-lint-tooltip";
|
||||
tt.appendChild(content.cloneNode(true));
|
||||
document.body.appendChild(tt);
|
||||
|
||||
function position(e) {
|
||||
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||
tt.style.left = (e.clientX + 5) + "px";
|
||||
}
|
||||
CodeMirror.on(document, "mousemove", position);
|
||||
position(e);
|
||||
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||
return tt;
|
||||
}
|
||||
function rm(elt) {
|
||||
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||
}
|
||||
function hideTooltip(tt) {
|
||||
if (!tt.parentNode) return;
|
||||
if (tt.style.opacity == null) rm(tt);
|
||||
tt.style.opacity = 0;
|
||||
setTimeout(function() { rm(tt); }, 600);
|
||||
}
|
||||
|
||||
function showTooltipFor(e, content, node) {
|
||||
var tooltip = showTooltip(e, content);
|
||||
function hide() {
|
||||
CodeMirror.off(node, "mouseout", hide);
|
||||
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||
}
|
||||
var poll = setInterval(function() {
|
||||
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||
if (n && n.nodeType == 11) n = n.host;
|
||||
if (n == document.body) return;
|
||||
if (!n) { hide(); break; }
|
||||
}
|
||||
if (!tooltip) return clearInterval(poll);
|
||||
}, 400);
|
||||
CodeMirror.on(node, "mouseout", hide);
|
||||
}
|
||||
|
||||
function LintState(cm, options, hasGutter) {
|
||||
this.marked = [];
|
||||
this.options = options;
|
||||
this.timeout = null;
|
||||
this.hasGutter = hasGutter;
|
||||
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||
this.waitingFor = 0
|
||||
}
|
||||
|
||||
function parseOptions(_cm, options) {
|
||||
if (options instanceof Function) return {getAnnotations: options};
|
||||
if (!options || options === true) options = {};
|
||||
return options;
|
||||
}
|
||||
|
||||
function clearMarks(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||
for (var i = 0; i < state.marked.length; ++i)
|
||||
state.marked[i].clear();
|
||||
state.marked.length = 0;
|
||||
}
|
||||
|
||||
function makeMarker(labels, severity, multiple, tooltips) {
|
||||
var marker = document.createElement("div"), inner = marker;
|
||||
marker.className = "CodeMirror-lint-marker-" + severity;
|
||||
if (multiple) {
|
||||
inner = marker.appendChild(document.createElement("div"));
|
||||
inner.className = "CodeMirror-lint-marker-multiple";
|
||||
}
|
||||
|
||||
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||
showTooltipFor(e, labels, inner);
|
||||
});
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
function getMaxSeverity(a, b) {
|
||||
if (a == "error") return a;
|
||||
else return b;
|
||||
}
|
||||
|
||||
function groupByLine(annotations) {
|
||||
var lines = [];
|
||||
for (var i = 0; i < annotations.length; ++i) {
|
||||
var ann = annotations[i], line = ann.from.line;
|
||||
(lines[line] || (lines[line] = [])).push(ann);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function annotationTooltip(ann) {
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
var tip = document.createElement("div");
|
||||
tip.className = "CodeMirror-lint-message-" + severity;
|
||||
tip.appendChild(document.createTextNode(ann.message));
|
||||
return tip;
|
||||
}
|
||||
|
||||
function lintAsync(cm, getAnnotations, passOptions) {
|
||||
var state = cm.state.lint
|
||||
var id = ++state.waitingFor
|
||||
function abort() {
|
||||
id = -1
|
||||
cm.off("change", abort)
|
||||
}
|
||||
cm.on("change", abort)
|
||||
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||
cm.off("change", abort)
|
||||
if (state.waitingFor != id) return
|
||||
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||
updateLinting(cm, annotations)
|
||||
}, passOptions, cm);
|
||||
}
|
||||
|
||||
function startLinting(cm) {
|
||||
var state = cm.state.lint, options = state.options;
|
||||
var passOptions = options.options || options; // Support deprecated passing of `options` property in options
|
||||
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||
if (!getAnnotations) return;
|
||||
if (options.async || getAnnotations.async) {
|
||||
lintAsync(cm, getAnnotations, passOptions)
|
||||
} else {
|
||||
var annotations = getAnnotations(cm.getValue(), passOptions, cm);
|
||||
if (annotations.then) annotations.then(function(issues) {
|
||||
updateLinting(cm, issues);
|
||||
});
|
||||
else updateLinting(cm, annotations);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLinting(cm, annotationsNotSorted) {
|
||||
clearMarks(cm);
|
||||
var state = cm.state.lint, options = state.options;
|
||||
|
||||
var annotations = groupByLine(annotationsNotSorted);
|
||||
|
||||
for (var line = 0; line < annotations.length; ++line) {
|
||||
var anns = annotations[line];
|
||||
if (!anns) continue;
|
||||
|
||||
var maxSeverity = null;
|
||||
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||
|
||||
for (var i = 0; i < anns.length; ++i) {
|
||||
var ann = anns[i];
|
||||
var severity = ann.severity;
|
||||
if (!severity) severity = "error";
|
||||
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||
|
||||
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||
|
||||
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||
className: "CodeMirror-lint-mark-" + severity,
|
||||
__annotation: ann
|
||||
}));
|
||||
}
|
||||
|
||||
if (state.hasGutter)
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
|
||||
state.options.tooltips));
|
||||
}
|
||||
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.lint;
|
||||
if (!state) return;
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
|
||||
}
|
||||
|
||||
function popupTooltips(annotations, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
var tooltip = document.createDocumentFragment();
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var ann = annotations[i];
|
||||
tooltip.appendChild(annotationTooltip(ann));
|
||||
}
|
||||
showTooltipFor(e, tooltip, target);
|
||||
}
|
||||
|
||||
function onMouseOver(cm, e) {
|
||||
var target = e.target || e.srcElement;
|
||||
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||
|
||||
var annotations = [];
|
||||
for (var i = 0; i < spans.length; ++i) {
|
||||
var ann = spans[i].__annotation;
|
||||
if (ann) annotations.push(ann);
|
||||
}
|
||||
if (annotations.length) popupTooltips(annotations, e);
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||
if (old && old != CodeMirror.Init) {
|
||||
clearMarks(cm);
|
||||
if (cm.state.lint.options.lintOnChange !== false)
|
||||
cm.off("change", onChange);
|
||||
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||
clearTimeout(cm.state.lint.timeout);
|
||||
delete cm.state.lint;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
|
||||
if (state.options.lintOnChange !== false)
|
||||
cm.on("change", onChange);
|
||||
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||
|
||||
startLinting(cm);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.defineExtension("performLint", function() {
|
||||
if (this.state.lint) startLinting(this);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
// Depends on js-yaml.js from https://github.com/nodeca/js-yaml
|
||||
|
||||
// declare global: jsyaml
|
||||
|
||||
CodeMirror.registerHelper("lint", "yaml", function(text) {
|
||||
var found = [];
|
||||
try { jsyaml.load(text); }
|
||||
catch(e) {
|
||||
var loc = e.mark,
|
||||
// js-yaml YAMLException doesn't always provide an accurate lineno
|
||||
// e.g., when there are multiple yaml docs
|
||||
// ---
|
||||
// ---
|
||||
// foo:bar
|
||||
from = loc ? CodeMirror.Pos(loc.line, loc.column) : CodeMirror.Pos(0, 0),
|
||||
to = from;
|
||||
found.push({ from: from, to: to, message: e.message });
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
.CodeMirror-merge {
|
||||
position: relative;
|
||||
border: 1px solid #ddd;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.CodeMirror-merge, .CodeMirror-merge .CodeMirror {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; }
|
||||
.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; }
|
||||
.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
|
||||
.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
|
||||
|
||||
.CodeMirror-merge-pane {
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
vertical-align: top;
|
||||
}
|
||||
.CodeMirror-merge-pane-rightmost {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-gap {
|
||||
z-index: 2;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
position: relative;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-scrolllock-wrap {
|
||||
position: absolute;
|
||||
bottom: 0; left: 50%;
|
||||
}
|
||||
.CodeMirror-merge-scrolllock {
|
||||
position: relative;
|
||||
left: -50%;
|
||||
cursor: pointer;
|
||||
color: #555;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
|
||||
position: absolute;
|
||||
left: 0; top: 0;
|
||||
right: 0; bottom: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-copy {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
color: #44c;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-copy-reverse {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
color: #44c;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
|
||||
.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
|
||||
|
||||
.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
|
||||
background-position: bottom left;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom left;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.CodeMirror-merge-r-chunk { background: #ffffe0; }
|
||||
.CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; }
|
||||
.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; }
|
||||
.CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; }
|
||||
|
||||
.CodeMirror-merge-l-chunk { background: #eef; }
|
||||
.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
|
||||
.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
|
||||
.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
|
||||
|
||||
.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
|
||||
.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
|
||||
.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
|
||||
|
||||
.CodeMirror-merge-collapsed-widget:before {
|
||||
content: "(...)";
|
||||
}
|
||||
.CodeMirror-merge-collapsed-widget {
|
||||
cursor: pointer;
|
||||
color: #88b;
|
||||
background: #eef;
|
||||
border: 1px solid #ddf;
|
||||
font-size: 90%;
|
||||
padding: 0 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }
|
||||
|
|
@ -0,0 +1,997 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "diff_match_patch"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
var Pos = CodeMirror.Pos;
|
||||
var svgNS = "http://www.w3.org/2000/svg";
|
||||
|
||||
function DiffView(mv, type) {
|
||||
this.mv = mv;
|
||||
this.type = type;
|
||||
this.classes = type == "left"
|
||||
? {chunk: "CodeMirror-merge-l-chunk",
|
||||
start: "CodeMirror-merge-l-chunk-start",
|
||||
end: "CodeMirror-merge-l-chunk-end",
|
||||
insert: "CodeMirror-merge-l-inserted",
|
||||
del: "CodeMirror-merge-l-deleted",
|
||||
connect: "CodeMirror-merge-l-connect"}
|
||||
: {chunk: "CodeMirror-merge-r-chunk",
|
||||
start: "CodeMirror-merge-r-chunk-start",
|
||||
end: "CodeMirror-merge-r-chunk-end",
|
||||
insert: "CodeMirror-merge-r-inserted",
|
||||
del: "CodeMirror-merge-r-deleted",
|
||||
connect: "CodeMirror-merge-r-connect"};
|
||||
}
|
||||
|
||||
DiffView.prototype = {
|
||||
constructor: DiffView,
|
||||
init: function(pane, orig, options) {
|
||||
this.edit = this.mv.edit;
|
||||
;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
|
||||
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
|
||||
if (this.mv.options.connect == "align") {
|
||||
if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit)
|
||||
this.orig.state.trackAlignable = new TrackAlignable(this.orig)
|
||||
}
|
||||
|
||||
this.orig.state.diffViews = [this];
|
||||
var classLocation = options.chunkClassLocation || "background";
|
||||
if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation]
|
||||
this.classes.classLocation = classLocation
|
||||
|
||||
this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace);
|
||||
this.chunks = getChunks(this.diff);
|
||||
this.diffOutOfDate = this.dealigned = false;
|
||||
this.needsScrollSync = null
|
||||
|
||||
this.showDifferences = options.showDifferences !== false;
|
||||
},
|
||||
registerEvents: function(otherDv) {
|
||||
this.forceUpdate = registerUpdate(this);
|
||||
setScrollLock(this, true, false);
|
||||
registerScroll(this, otherDv);
|
||||
},
|
||||
setShowDifferences: function(val) {
|
||||
val = val !== false;
|
||||
if (val != this.showDifferences) {
|
||||
this.showDifferences = val;
|
||||
this.forceUpdate("full");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function ensureDiff(dv) {
|
||||
if (dv.diffOutOfDate) {
|
||||
dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace);
|
||||
dv.chunks = getChunks(dv.diff);
|
||||
dv.diffOutOfDate = false;
|
||||
CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
|
||||
}
|
||||
}
|
||||
|
||||
var updating = false;
|
||||
function registerUpdate(dv) {
|
||||
var edit = {from: 0, to: 0, marked: []};
|
||||
var orig = {from: 0, to: 0, marked: []};
|
||||
var debounceChange, updatingFast = false;
|
||||
function update(mode) {
|
||||
updating = true;
|
||||
updatingFast = false;
|
||||
if (mode == "full") {
|
||||
if (dv.svg) clear(dv.svg);
|
||||
if (dv.copyButtons) clear(dv.copyButtons);
|
||||
clearMarks(dv.edit, edit.marked, dv.classes);
|
||||
clearMarks(dv.orig, orig.marked, dv.classes);
|
||||
edit.from = edit.to = orig.from = orig.to = 0;
|
||||
}
|
||||
ensureDiff(dv);
|
||||
if (dv.showDifferences) {
|
||||
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
|
||||
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
|
||||
}
|
||||
|
||||
if (dv.mv.options.connect == "align")
|
||||
alignChunks(dv);
|
||||
makeConnections(dv);
|
||||
if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync)
|
||||
|
||||
updating = false;
|
||||
}
|
||||
function setDealign(fast) {
|
||||
if (updating) return;
|
||||
dv.dealigned = true;
|
||||
set(fast);
|
||||
}
|
||||
function set(fast) {
|
||||
if (updating || updatingFast) return;
|
||||
clearTimeout(debounceChange);
|
||||
if (fast === true) updatingFast = true;
|
||||
debounceChange = setTimeout(update, fast === true ? 20 : 250);
|
||||
}
|
||||
function change(_cm, change) {
|
||||
if (!dv.diffOutOfDate) {
|
||||
dv.diffOutOfDate = true;
|
||||
edit.from = edit.to = orig.from = orig.to = 0;
|
||||
}
|
||||
// Update faster when a line was added/removed
|
||||
setDealign(change.text.length - 1 != change.to.line - change.from.line);
|
||||
}
|
||||
function swapDoc() {
|
||||
dv.diffOutOfDate = true;
|
||||
dv.dealigned = true;
|
||||
update("full");
|
||||
}
|
||||
dv.edit.on("change", change);
|
||||
dv.orig.on("change", change);
|
||||
dv.edit.on("swapDoc", swapDoc);
|
||||
dv.orig.on("swapDoc", swapDoc);
|
||||
if (dv.mv.options.connect == "align") {
|
||||
CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign)
|
||||
CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign)
|
||||
}
|
||||
dv.edit.on("viewportChange", function() { set(false); });
|
||||
dv.orig.on("viewportChange", function() { set(false); });
|
||||
update();
|
||||
return update;
|
||||
}
|
||||
|
||||
function registerScroll(dv, otherDv) {
|
||||
dv.edit.on("scroll", function() {
|
||||
syncScroll(dv, true) && makeConnections(dv);
|
||||
});
|
||||
dv.orig.on("scroll", function() {
|
||||
syncScroll(dv, false) && makeConnections(dv);
|
||||
if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv);
|
||||
});
|
||||
}
|
||||
|
||||
function syncScroll(dv, toOrig) {
|
||||
// Change handler will do a refresh after a timeout when diff is out of date
|
||||
if (dv.diffOutOfDate) {
|
||||
if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig
|
||||
return false
|
||||
}
|
||||
dv.needsScrollSync = null
|
||||
if (!dv.lockScroll) return true;
|
||||
var editor, other, now = +new Date;
|
||||
if (toOrig) { editor = dv.edit; other = dv.orig; }
|
||||
else { editor = dv.orig; other = dv.edit; }
|
||||
// Don't take action if the position of this editor was recently set
|
||||
// (to prevent feedback loops)
|
||||
if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false;
|
||||
|
||||
var sInfo = editor.getScrollInfo();
|
||||
if (dv.mv.options.connect == "align") {
|
||||
targetPos = sInfo.top;
|
||||
} else {
|
||||
var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
|
||||
var mid = editor.lineAtHeight(midY, "local");
|
||||
var around = chunkBoundariesAround(dv.chunks, mid, toOrig);
|
||||
var off = getOffsets(editor, toOrig ? around.edit : around.orig);
|
||||
var offOther = getOffsets(other, toOrig ? around.orig : around.edit);
|
||||
var ratio = (midY - off.top) / (off.bot - off.top);
|
||||
var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
|
||||
|
||||
var botDist, mix;
|
||||
// Some careful tweaking to make sure no space is left out of view
|
||||
// when scrolling to top or bottom.
|
||||
if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
|
||||
targetPos = targetPos * mix + sInfo.top * (1 - mix);
|
||||
} else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
|
||||
var otherInfo = other.getScrollInfo();
|
||||
var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
|
||||
if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
|
||||
targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
|
||||
}
|
||||
}
|
||||
|
||||
other.scrollTo(sInfo.left, targetPos);
|
||||
other.state.scrollSetAt = now;
|
||||
other.state.scrollSetBy = dv;
|
||||
return true;
|
||||
}
|
||||
|
||||
function getOffsets(editor, around) {
|
||||
var bot = around.after;
|
||||
if (bot == null) bot = editor.lastLine() + 1;
|
||||
return {top: editor.heightAtLine(around.before || 0, "local"),
|
||||
bot: editor.heightAtLine(bot, "local")};
|
||||
}
|
||||
|
||||
function setScrollLock(dv, val, action) {
|
||||
dv.lockScroll = val;
|
||||
if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
|
||||
dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db \u21da";
|
||||
}
|
||||
|
||||
// Updating the marks for editor content
|
||||
|
||||
function removeClass(editor, line, classes) {
|
||||
var locs = classes.classLocation
|
||||
for (var i = 0; i < locs.length; i++) {
|
||||
editor.removeLineClass(line, locs[i], classes.chunk);
|
||||
editor.removeLineClass(line, locs[i], classes.start);
|
||||
editor.removeLineClass(line, locs[i], classes.end);
|
||||
}
|
||||
}
|
||||
|
||||
function clearMarks(editor, arr, classes) {
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
var mark = arr[i];
|
||||
if (mark instanceof CodeMirror.TextMarker)
|
||||
mark.clear();
|
||||
else if (mark.parent)
|
||||
removeClass(editor, mark, classes);
|
||||
}
|
||||
arr.length = 0;
|
||||
}
|
||||
|
||||
// FIXME maybe add a margin around viewport to prevent too many updates
|
||||
function updateMarks(editor, diff, state, type, classes) {
|
||||
var vp = editor.getViewport();
|
||||
editor.operation(function() {
|
||||
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
||||
clearMarks(editor, state.marked, classes);
|
||||
markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes);
|
||||
state.from = vp.from; state.to = vp.to;
|
||||
} else {
|
||||
if (vp.from < state.from) {
|
||||
markChanges(editor, diff, type, state.marked, vp.from, state.from, classes);
|
||||
state.from = vp.from;
|
||||
}
|
||||
if (vp.to > state.to) {
|
||||
markChanges(editor, diff, type, state.marked, state.to, vp.to, classes);
|
||||
state.to = vp.to;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addClass(editor, lineNr, classes, main, start, end) {
|
||||
var locs = classes.classLocation, line = editor.getLineHandle(lineNr);
|
||||
for (var i = 0; i < locs.length; i++) {
|
||||
if (main) editor.addLineClass(line, locs[i], classes.chunk);
|
||||
if (start) editor.addLineClass(line, locs[i], classes.start);
|
||||
if (end) editor.addLineClass(line, locs[i], classes.end);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function markChanges(editor, diff, type, marks, from, to, classes) {
|
||||
var pos = Pos(0, 0);
|
||||
var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
|
||||
var cls = type == DIFF_DELETE ? classes.del : classes.insert;
|
||||
function markChunk(start, end) {
|
||||
var bfrom = Math.max(from, start), bto = Math.min(to, end);
|
||||
for (var i = bfrom; i < bto; ++i)
|
||||
marks.push(addClass(editor, i, classes, true, i == start, i == end - 1));
|
||||
// When the chunk is empty, make sure a horizontal line shows up
|
||||
if (start == end && bfrom == end && bto == end) {
|
||||
if (bfrom)
|
||||
marks.push(addClass(editor, bfrom - 1, classes, false, false, true));
|
||||
else
|
||||
marks.push(addClass(editor, bfrom, classes, false, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
var chunkStart = 0, pending = false;
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i], tp = part[0], str = part[1];
|
||||
if (tp == DIFF_EQUAL) {
|
||||
var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1);
|
||||
moveOver(pos, str);
|
||||
var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
|
||||
if (cleanTo > cleanFrom) {
|
||||
if (pending) { markChunk(chunkStart, cleanFrom); pending = false }
|
||||
chunkStart = cleanTo;
|
||||
}
|
||||
} else {
|
||||
pending = true
|
||||
if (tp == type) {
|
||||
var end = moveOver(pos, str, true);
|
||||
var a = posMax(top, pos), b = posMin(bot, end);
|
||||
if (!posEq(a, b))
|
||||
marks.push(editor.markText(a, b, {className: cls}));
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending) markChunk(chunkStart, pos.line + 1);
|
||||
}
|
||||
|
||||
// Updating the gap between editor and original
|
||||
|
||||
function makeConnections(dv) {
|
||||
if (!dv.showDifferences) return;
|
||||
|
||||
if (dv.svg) {
|
||||
clear(dv.svg);
|
||||
var w = dv.gap.offsetWidth;
|
||||
attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
|
||||
}
|
||||
if (dv.copyButtons) clear(dv.copyButtons);
|
||||
|
||||
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
|
||||
var outerTop = dv.mv.wrap.getBoundingClientRect().top
|
||||
var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
|
||||
var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
|
||||
for (var i = 0; i < dv.chunks.length; i++) {
|
||||
var ch = dv.chunks[i];
|
||||
if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
|
||||
ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from)
|
||||
drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w);
|
||||
}
|
||||
}
|
||||
|
||||
function getMatchingOrigLine(editLine, chunks) {
|
||||
var editStart = 0, origStart = 0;
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
var chunk = chunks[i];
|
||||
if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null;
|
||||
if (chunk.editFrom > editLine) break;
|
||||
editStart = chunk.editTo;
|
||||
origStart = chunk.origTo;
|
||||
}
|
||||
return origStart + (editLine - editStart);
|
||||
}
|
||||
|
||||
// Combines information about chunks and widgets/markers to return
|
||||
// an array of lines, in a single editor, that probably need to be
|
||||
// aligned with their counterparts in the editor next to it.
|
||||
function alignableFor(cm, chunks, isOrig) {
|
||||
var tracker = cm.state.trackAlignable
|
||||
var start = cm.firstLine(), trackI = 0
|
||||
var result = []
|
||||
for (var i = 0;; i++) {
|
||||
var chunk = chunks[i]
|
||||
var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom
|
||||
for (; trackI < tracker.alignable.length; trackI += 2) {
|
||||
var n = tracker.alignable[trackI] + 1
|
||||
if (n <= start) continue
|
||||
if (n <= chunkStart) result.push(n)
|
||||
else break
|
||||
}
|
||||
if (!chunk) break
|
||||
result.push(start = isOrig ? chunk.origTo : chunk.editTo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Given information about alignable lines in two editors, fill in
|
||||
// the result (an array of three-element arrays) to reflect the
|
||||
// lines that need to be aligned with each other.
|
||||
function mergeAlignable(result, origAlignable, chunks, setIndex) {
|
||||
var rI = 0, origI = 0, chunkI = 0, diff = 0
|
||||
outer: for (;; rI++) {
|
||||
var nextR = result[rI], nextO = origAlignable[origI]
|
||||
if (!nextR && nextO == null) break
|
||||
|
||||
var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO
|
||||
while (chunkI < chunks.length) {
|
||||
var chunk = chunks[chunkI]
|
||||
if (chunk.origFrom <= oLine && chunk.origTo > oLine) {
|
||||
origI++
|
||||
rI--
|
||||
continue outer;
|
||||
}
|
||||
if (chunk.editTo > rLine) {
|
||||
if (chunk.editFrom <= rLine) continue outer;
|
||||
break
|
||||
}
|
||||
diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom)
|
||||
chunkI++
|
||||
}
|
||||
if (rLine == oLine - diff) {
|
||||
nextR[setIndex] = oLine
|
||||
origI++
|
||||
} else if (rLine < oLine - diff) {
|
||||
nextR[setIndex] = rLine + diff
|
||||
} else {
|
||||
var record = [oLine - diff, null, null]
|
||||
record[setIndex] = oLine
|
||||
result.splice(rI, 0, record)
|
||||
origI++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findAlignedLines(dv, other) {
|
||||
var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
|
||||
if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
|
||||
var n = other.chunks[i].editTo
|
||||
while (j < alignable.length && alignable[j] < n) j++
|
||||
if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n)
|
||||
}
|
||||
for (var i = 0; i < alignable.length; i++)
|
||||
result.push([alignable[i], null, null])
|
||||
|
||||
mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1)
|
||||
if (other)
|
||||
mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function alignChunks(dv, force) {
|
||||
if (!dv.dealigned && !force) return;
|
||||
if (!dv.orig.curOp) return dv.orig.operation(function() {
|
||||
alignChunks(dv, force);
|
||||
});
|
||||
|
||||
dv.dealigned = false;
|
||||
var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left;
|
||||
if (other) {
|
||||
ensureDiff(other);
|
||||
other.dealigned = false;
|
||||
}
|
||||
var linesToAlign = findAlignedLines(dv, other);
|
||||
|
||||
// Clear old aligners
|
||||
var aligners = dv.mv.aligners;
|
||||
for (var i = 0; i < aligners.length; i++)
|
||||
aligners[i].clear();
|
||||
aligners.length = 0;
|
||||
|
||||
var cm = [dv.edit, dv.orig], scroll = [];
|
||||
if (other) cm.push(other.orig);
|
||||
for (var i = 0; i < cm.length; i++)
|
||||
scroll.push(cm[i].getScrollInfo().top);
|
||||
|
||||
for (var ln = 0; ln < linesToAlign.length; ln++)
|
||||
alignLines(cm, linesToAlign[ln], aligners);
|
||||
|
||||
for (var i = 0; i < cm.length; i++)
|
||||
cm[i].scrollTo(null, scroll[i]);
|
||||
}
|
||||
|
||||
function alignLines(cm, lines, aligners) {
|
||||
var maxOffset = 0, offset = [];
|
||||
for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
|
||||
var off = cm[i].heightAtLine(lines[i], "local");
|
||||
offset[i] = off;
|
||||
maxOffset = Math.max(maxOffset, off);
|
||||
}
|
||||
for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
|
||||
var diff = maxOffset - offset[i];
|
||||
if (diff > 1)
|
||||
aligners.push(padAbove(cm[i], lines[i], diff));
|
||||
}
|
||||
}
|
||||
|
||||
function padAbove(cm, line, size) {
|
||||
var above = true;
|
||||
if (line > cm.lastLine()) {
|
||||
line--;
|
||||
above = false;
|
||||
}
|
||||
var elt = document.createElement("div");
|
||||
elt.className = "CodeMirror-merge-spacer";
|
||||
elt.style.height = size + "px"; elt.style.minWidth = "1px";
|
||||
return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true});
|
||||
}
|
||||
|
||||
function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
|
||||
var flip = dv.type == "left";
|
||||
var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig;
|
||||
if (dv.svg) {
|
||||
var topLpx = top;
|
||||
var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit;
|
||||
if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
|
||||
var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig;
|
||||
var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit;
|
||||
if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
|
||||
var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
|
||||
var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
|
||||
attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
|
||||
"d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
|
||||
"class", dv.classes.connect);
|
||||
}
|
||||
if (dv.copyButtons) {
|
||||
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
|
||||
"CodeMirror-merge-copy"));
|
||||
var editOriginals = dv.mv.options.allowEditingOriginals;
|
||||
copy.title = editOriginals ? "Push to left" : "Revert chunk";
|
||||
copy.chunk = chunk;
|
||||
copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px";
|
||||
|
||||
if (editOriginals) {
|
||||
var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
|
||||
var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
|
||||
"CodeMirror-merge-copy-reverse"));
|
||||
copyReverse.title = "Push to right";
|
||||
copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo,
|
||||
origFrom: chunk.editFrom, origTo: chunk.editTo};
|
||||
copyReverse.style.top = topReverse + "px";
|
||||
dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyChunk(dv, to, from, chunk) {
|
||||
if (dv.diffOutOfDate) return;
|
||||
var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
|
||||
var origEnd = Pos(chunk.origTo, 0)
|
||||
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
|
||||
var editEnd = Pos(chunk.editTo, 0)
|
||||
var handler = dv.mv.options.revertChunk
|
||||
if (handler)
|
||||
handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd)
|
||||
else
|
||||
to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd)
|
||||
}
|
||||
|
||||
// Merge view, containing 0, 1, or 2 diff views.
|
||||
|
||||
var MergeView = CodeMirror.MergeView = function(node, options) {
|
||||
if (!(this instanceof MergeView)) return new MergeView(node, options);
|
||||
|
||||
this.options = options;
|
||||
var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
|
||||
|
||||
var hasLeft = origLeft != null, hasRight = origRight != null;
|
||||
var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
|
||||
var wrap = [], left = this.left = null, right = this.right = null;
|
||||
var self = this;
|
||||
|
||||
if (hasLeft) {
|
||||
left = this.left = new DiffView(this, "left");
|
||||
var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left");
|
||||
wrap.push(leftPane);
|
||||
wrap.push(buildGap(left));
|
||||
}
|
||||
|
||||
var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor");
|
||||
wrap.push(editPane);
|
||||
|
||||
if (hasRight) {
|
||||
right = this.right = new DiffView(this, "right");
|
||||
wrap.push(buildGap(right));
|
||||
var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right");
|
||||
wrap.push(rightPane);
|
||||
}
|
||||
|
||||
(hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";
|
||||
|
||||
wrap.push(elt("div", null, null, "height: 0; clear: both;"));
|
||||
|
||||
var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
|
||||
this.edit = CodeMirror(editPane, copyObj(options));
|
||||
|
||||
if (left) left.init(leftPane, origLeft, options);
|
||||
if (right) right.init(rightPane, origRight, options);
|
||||
if (options.collapseIdentical)
|
||||
this.editor().operation(function() {
|
||||
collapseIdenticalStretches(self, options.collapseIdentical);
|
||||
});
|
||||
if (options.connect == "align") {
|
||||
this.aligners = [];
|
||||
alignChunks(this.left || this.right, true);
|
||||
}
|
||||
if (left) left.registerEvents(right)
|
||||
if (right) right.registerEvents(left)
|
||||
|
||||
|
||||
var onResize = function() {
|
||||
if (left) makeConnections(left);
|
||||
if (right) makeConnections(right);
|
||||
};
|
||||
CodeMirror.on(window, "resize", onResize);
|
||||
var resizeInterval = setInterval(function() {
|
||||
for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {}
|
||||
if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); }
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
function buildGap(dv) {
|
||||
var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock");
|
||||
lock.title = "Toggle locked scrolling";
|
||||
var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
|
||||
CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
|
||||
var gapElts = [lockWrap];
|
||||
if (dv.mv.options.revertButtons !== false) {
|
||||
dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
|
||||
CodeMirror.on(dv.copyButtons, "click", function(e) {
|
||||
var node = e.target || e.srcElement;
|
||||
if (!node.chunk) return;
|
||||
if (node.className == "CodeMirror-merge-copy-reverse") {
|
||||
copyChunk(dv, dv.orig, dv.edit, node.chunk);
|
||||
return;
|
||||
}
|
||||
copyChunk(dv, dv.edit, dv.orig, node.chunk);
|
||||
});
|
||||
gapElts.unshift(dv.copyButtons);
|
||||
}
|
||||
if (dv.mv.options.connect != "align") {
|
||||
var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
|
||||
if (svg && !svg.createSVGRect) svg = null;
|
||||
dv.svg = svg;
|
||||
if (svg) gapElts.push(svg);
|
||||
}
|
||||
|
||||
return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
|
||||
}
|
||||
|
||||
MergeView.prototype = {
|
||||
constructor: MergeView,
|
||||
editor: function() { return this.edit; },
|
||||
rightOriginal: function() { return this.right && this.right.orig; },
|
||||
leftOriginal: function() { return this.left && this.left.orig; },
|
||||
setShowDifferences: function(val) {
|
||||
if (this.right) this.right.setShowDifferences(val);
|
||||
if (this.left) this.left.setShowDifferences(val);
|
||||
},
|
||||
rightChunks: function() {
|
||||
if (this.right) { ensureDiff(this.right); return this.right.chunks; }
|
||||
},
|
||||
leftChunks: function() {
|
||||
if (this.left) { ensureDiff(this.left); return this.left.chunks; }
|
||||
}
|
||||
};
|
||||
|
||||
function asString(obj) {
|
||||
if (typeof obj == "string") return obj;
|
||||
else return obj.getValue();
|
||||
}
|
||||
|
||||
// Operations on diffs
|
||||
|
||||
var dmp = new diff_match_patch();
|
||||
function getDiff(a, b, ignoreWhitespace) {
|
||||
var diff = dmp.diff_main(a, b);
|
||||
// The library sometimes leaves in empty parts, which confuse the algorithm
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i];
|
||||
if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
|
||||
diff.splice(i--, 1);
|
||||
} else if (i && diff[i - 1][0] == part[0]) {
|
||||
diff.splice(i--, 1);
|
||||
diff[i][1] += part[1];
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
function getChunks(diff) {
|
||||
var chunks = [];
|
||||
var startEdit = 0, startOrig = 0;
|
||||
var edit = Pos(0, 0), orig = Pos(0, 0);
|
||||
for (var i = 0; i < diff.length; ++i) {
|
||||
var part = diff[i], tp = part[0];
|
||||
if (tp == DIFF_EQUAL) {
|
||||
var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
|
||||
var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
|
||||
moveOver(edit, part[1], null, orig);
|
||||
var endOff = endOfLineClean(diff, i) ? 1 : 0;
|
||||
var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
|
||||
if (cleanToEdit > cleanFromEdit) {
|
||||
if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
|
||||
editFrom: startEdit, editTo: cleanFromEdit});
|
||||
startEdit = cleanToEdit; startOrig = cleanToOrig;
|
||||
}
|
||||
} else {
|
||||
moveOver(tp == DIFF_INSERT ? edit : orig, part[1]);
|
||||
}
|
||||
}
|
||||
if (startEdit <= edit.line || startOrig <= orig.line)
|
||||
chunks.push({origFrom: startOrig, origTo: orig.line + 1,
|
||||
editFrom: startEdit, editTo: edit.line + 1});
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function endOfLineClean(diff, i) {
|
||||
if (i == diff.length - 1) return true;
|
||||
var next = diff[i + 1][1];
|
||||
if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
|
||||
if (i == diff.length - 2) return true;
|
||||
next = diff[i + 2][1];
|
||||
return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
|
||||
}
|
||||
|
||||
function startOfLineClean(diff, i) {
|
||||
if (i == 0) return true;
|
||||
var last = diff[i - 1][1];
|
||||
if (last.charCodeAt(last.length - 1) != 10) return false;
|
||||
if (i == 1) return true;
|
||||
last = diff[i - 2][1];
|
||||
return last.charCodeAt(last.length - 1) == 10;
|
||||
}
|
||||
|
||||
function chunkBoundariesAround(chunks, n, nInEdit) {
|
||||
var beforeE, afterE, beforeO, afterO;
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
var chunk = chunks[i];
|
||||
var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
|
||||
var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
|
||||
if (afterE == null) {
|
||||
if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
|
||||
else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
|
||||
}
|
||||
if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
|
||||
else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
|
||||
}
|
||||
return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
|
||||
}
|
||||
|
||||
function collapseSingle(cm, from, to) {
|
||||
cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
|
||||
var widget = document.createElement("span");
|
||||
widget.className = "CodeMirror-merge-collapsed-widget";
|
||||
widget.title = "Identical text collapsed. Click to expand.";
|
||||
var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
|
||||
inclusiveLeft: true,
|
||||
inclusiveRight: true,
|
||||
replacedWith: widget,
|
||||
clearOnEnter: true
|
||||
});
|
||||
function clear() {
|
||||
mark.clear();
|
||||
cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
|
||||
}
|
||||
CodeMirror.on(widget, "click", clear);
|
||||
return {mark: mark, clear: clear};
|
||||
}
|
||||
|
||||
function collapseStretch(size, editors) {
|
||||
var marks = [];
|
||||
function clear() {
|
||||
for (var i = 0; i < marks.length; i++) marks[i].clear();
|
||||
}
|
||||
for (var i = 0; i < editors.length; i++) {
|
||||
var editor = editors[i];
|
||||
var mark = collapseSingle(editor.cm, editor.line, editor.line + size);
|
||||
marks.push(mark);
|
||||
mark.mark.on("clear", clear);
|
||||
}
|
||||
return marks[0].mark;
|
||||
}
|
||||
|
||||
function unclearNearChunks(dv, margin, off, clear) {
|
||||
for (var i = 0; i < dv.chunks.length; i++) {
|
||||
var chunk = dv.chunks[i];
|
||||
for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) {
|
||||
var pos = l + off;
|
||||
if (pos >= 0 && pos < clear.length) clear[pos] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collapseIdenticalStretches(mv, margin) {
|
||||
if (typeof margin != "number") margin = 2;
|
||||
var clear = [], edit = mv.editor(), off = edit.firstLine();
|
||||
for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true);
|
||||
if (mv.left) unclearNearChunks(mv.left, margin, off, clear);
|
||||
if (mv.right) unclearNearChunks(mv.right, margin, off, clear);
|
||||
|
||||
for (var i = 0; i < clear.length; i++) {
|
||||
if (clear[i]) {
|
||||
var line = i + off;
|
||||
for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {}
|
||||
if (size > margin) {
|
||||
var editors = [{line: line, cm: edit}];
|
||||
if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig});
|
||||
if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig});
|
||||
var mark = collapseStretch(size, editors);
|
||||
if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// General utilities
|
||||
|
||||
function elt(tag, content, className, style) {
|
||||
var e = document.createElement(tag);
|
||||
if (className) e.className = className;
|
||||
if (style) e.style.cssText = style;
|
||||
if (typeof content == "string") e.appendChild(document.createTextNode(content));
|
||||
else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
|
||||
return e;
|
||||
}
|
||||
|
||||
function clear(node) {
|
||||
for (var count = node.childNodes.length; count > 0; --count)
|
||||
node.removeChild(node.firstChild);
|
||||
}
|
||||
|
||||
function attrs(elt) {
|
||||
for (var i = 1; i < arguments.length; i += 2)
|
||||
elt.setAttribute(arguments[i], arguments[i+1]);
|
||||
}
|
||||
|
||||
function copyObj(obj, target) {
|
||||
if (!target) target = {};
|
||||
for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
|
||||
return target;
|
||||
}
|
||||
|
||||
function moveOver(pos, str, copy, other) {
|
||||
var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0;
|
||||
for (;;) {
|
||||
var nl = str.indexOf("\n", at);
|
||||
if (nl == -1) break;
|
||||
++out.line;
|
||||
if (other) ++other.line;
|
||||
at = nl + 1;
|
||||
}
|
||||
out.ch = (at ? 0 : out.ch) + (str.length - at);
|
||||
if (other) other.ch = (at ? 0 : other.ch) + (str.length - at);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Tracks collapsed markers and line widgets, in order to be able to
|
||||
// accurately align the content of two editors.
|
||||
|
||||
var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
|
||||
|
||||
function TrackAlignable(cm) {
|
||||
this.cm = cm
|
||||
this.alignable = []
|
||||
this.height = cm.doc.height
|
||||
var self = this
|
||||
cm.on("markerAdded", function(_, marker) {
|
||||
if (!marker.collapsed) return
|
||||
var found = marker.find(1)
|
||||
if (found != null) self.set(found.line, F_MARKER)
|
||||
})
|
||||
cm.on("markerCleared", function(_, marker, _min, max) {
|
||||
if (max != null && marker.collapsed)
|
||||
self.check(max, F_MARKER, self.hasMarker)
|
||||
})
|
||||
cm.on("markerChanged", this.signal.bind(this))
|
||||
cm.on("lineWidgetAdded", function(_, widget, lineNo) {
|
||||
if (widget.mergeSpacer) return
|
||||
if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
|
||||
else self.set(lineNo, F_WIDGET)
|
||||
})
|
||||
cm.on("lineWidgetCleared", function(_, widget, lineNo) {
|
||||
if (widget.mergeSpacer) return
|
||||
if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
|
||||
else self.check(lineNo, F_WIDGET, self.hasWidget)
|
||||
})
|
||||
cm.on("lineWidgetChanged", this.signal.bind(this))
|
||||
cm.on("change", function(_, change) {
|
||||
var start = change.from.line, nBefore = change.to.line - change.from.line
|
||||
var nAfter = change.text.length - 1, end = start + nAfter
|
||||
if (nBefore || nAfter) self.map(start, nBefore, nAfter)
|
||||
self.check(end, F_MARKER, self.hasMarker)
|
||||
if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
|
||||
})
|
||||
cm.on("viewportChange", function() {
|
||||
if (self.cm.doc.height != self.height) self.signal()
|
||||
})
|
||||
}
|
||||
|
||||
TrackAlignable.prototype = {
|
||||
signal: function() {
|
||||
CodeMirror.signal(this, "realign")
|
||||
this.height = this.cm.doc.height
|
||||
},
|
||||
|
||||
set: function(n, flags) {
|
||||
var pos = -1
|
||||
for (; pos < this.alignable.length; pos += 2) {
|
||||
var diff = this.alignable[pos] - n
|
||||
if (diff == 0) {
|
||||
if ((this.alignable[pos + 1] & flags) == flags) return
|
||||
this.alignable[pos + 1] |= flags
|
||||
this.signal()
|
||||
return
|
||||
}
|
||||
if (diff > 0) break
|
||||
}
|
||||
this.signal()
|
||||
this.alignable.splice(pos, 0, n, flags)
|
||||
},
|
||||
|
||||
find: function(n) {
|
||||
for (var i = 0; i < this.alignable.length; i += 2)
|
||||
if (this.alignable[i] == n) return i
|
||||
return -1
|
||||
},
|
||||
|
||||
check: function(n, flag, pred) {
|
||||
var found = this.find(n)
|
||||
if (found == -1 || !(this.alignable[found + 1] & flag)) return
|
||||
if (!pred.call(this, n)) {
|
||||
this.signal()
|
||||
var flags = this.alignable[found + 1] & ~flag
|
||||
if (flags) this.alignable[found + 1] = flags
|
||||
else this.alignable.splice(found, 2)
|
||||
}
|
||||
},
|
||||
|
||||
hasMarker: function(n) {
|
||||
var handle = this.cm.getLineHandle(n)
|
||||
if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
|
||||
if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null)
|
||||
return true
|
||||
return false
|
||||
},
|
||||
|
||||
hasWidget: function(n) {
|
||||
var handle = this.cm.getLineHandle(n)
|
||||
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
|
||||
if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
|
||||
return false
|
||||
},
|
||||
|
||||
hasWidgetBelow: function(n) {
|
||||
if (n == this.cm.lastLine()) return false
|
||||
var handle = this.cm.getLineHandle(n + 1)
|
||||
if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
|
||||
if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
|
||||
return false
|
||||
},
|
||||
|
||||
map: function(from, nBefore, nAfter) {
|
||||
var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
|
||||
for (var i = 0; i < this.alignable.length; i += 2) {
|
||||
var n = this.alignable[i]
|
||||
if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
|
||||
if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
|
||||
if (n <= from) continue
|
||||
else if (n < to) this.alignable.splice(i--, 2)
|
||||
else this.alignable[i] += diff
|
||||
}
|
||||
if (widgetFrom > -1) {
|
||||
var flags = this.alignable[widgetFrom + 1]
|
||||
if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
|
||||
else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
|
||||
}
|
||||
if (widgetTo > -1 && nAfter)
|
||||
this.set(from + nAfter, F_WIDGET_BELOW)
|
||||
}
|
||||
}
|
||||
|
||||
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
|
||||
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
|
||||
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
|
||||
|
||||
function findPrevDiff(chunks, start, isOrig) {
|
||||
for (var i = chunks.length - 1; i >= 0; i--) {
|
||||
var chunk = chunks[i];
|
||||
var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
|
||||
if (to < start) return to;
|
||||
}
|
||||
}
|
||||
|
||||
function findNextDiff(chunks, start, isOrig) {
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
var chunk = chunks[i];
|
||||
var from = (isOrig ? chunk.origFrom : chunk.editFrom);
|
||||
if (from > start) return from;
|
||||
}
|
||||
}
|
||||
|
||||
function goNearbyDiff(cm, dir) {
|
||||
var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
|
||||
if (views) for (var i = 0; i < views.length; i++) {
|
||||
var dv = views[i], isOrig = cm == dv.orig;
|
||||
ensureDiff(dv);
|
||||
var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
|
||||
if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
|
||||
found = pos;
|
||||
}
|
||||
if (found != null)
|
||||
cm.setCursor(found, 0);
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
}
|
||||
|
||||
CodeMirror.commands.goNextDiff = function(cm) {
|
||||
return goNearbyDiff(cm, 1);
|
||||
};
|
||||
CodeMirror.commands.goPrevDiff = function(cm) {
|
||||
return goNearbyDiff(cm, -1);
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), "cjs");
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); });
|
||||
else // Plain browser env
|
||||
mod(CodeMirror, "plain");
|
||||
})(function(CodeMirror, env) {
|
||||
if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js";
|
||||
|
||||
var loading = {};
|
||||
function splitCallback(cont, n) {
|
||||
var countDown = n;
|
||||
return function() { if (--countDown == 0) cont(); };
|
||||
}
|
||||
function ensureDeps(mode, cont) {
|
||||
var deps = CodeMirror.modes[mode].dependencies;
|
||||
if (!deps) return cont();
|
||||
var missing = [];
|
||||
for (var i = 0; i < deps.length; ++i) {
|
||||
if (!CodeMirror.modes.hasOwnProperty(deps[i]))
|
||||
missing.push(deps[i]);
|
||||
}
|
||||
if (!missing.length) return cont();
|
||||
var split = splitCallback(cont, missing.length);
|
||||
for (var i = 0; i < missing.length; ++i)
|
||||
CodeMirror.requireMode(missing[i], split);
|
||||
}
|
||||
|
||||
CodeMirror.requireMode = function(mode, cont) {
|
||||
if (typeof mode != "string") mode = mode.name;
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
|
||||
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
|
||||
|
||||
var file = CodeMirror.modeURL.replace(/%N/g, mode);
|
||||
if (env == "plain") {
|
||||
var script = document.createElement("script");
|
||||
script.src = file;
|
||||
var others = document.getElementsByTagName("script")[0];
|
||||
var list = loading[mode] = [cont];
|
||||
CodeMirror.on(script, "load", function() {
|
||||
ensureDeps(mode, function() {
|
||||
for (var i = 0; i < list.length; ++i) list[i]();
|
||||
});
|
||||
});
|
||||
others.parentNode.insertBefore(script, others);
|
||||
} else if (env == "cjs") {
|
||||
require(file);
|
||||
cont();
|
||||
} else if (env == "amd") {
|
||||
requirejs([file], cont);
|
||||
}
|
||||
};
|
||||
|
||||
CodeMirror.autoLoadMode = function(instance, mode) {
|
||||
if (!CodeMirror.modes.hasOwnProperty(mode))
|
||||
CodeMirror.requireMode(mode, function() {
|
||||
instance.setOption("mode", instance.getOption("mode"));
|
||||
});
|
||||
};
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue