diff --git a/asl_rulebook2/bin/qa-helper/READ-ME.txt b/asl_rulebook2/bin/qa-helper/READ-ME.txt new file mode 100644 index 0000000..39034cc --- /dev/null +++ b/asl_rulebook2/bin/qa-helper/READ-ME.txt @@ -0,0 +1,12 @@ +This tool helps generate the JSON for Q+A entries. It's rough-and-ready, but it helps get the job done. + +Note that " ... " can be used as a shortcut for " ... ", and there is CSS in the main program for this. + +Keyboard shortcuts: + ^p =

+ ^i = wrap selected text in + ^e = wrap selected text in + +Keyboard shortcuts (answers only): + ^X = Yes. + ^c = No. diff --git a/asl_rulebook2/bin/qa-helper/global.css b/asl_rulebook2/bin/qa-helper/global.css new file mode 100644 index 0000000..00f691a --- /dev/null +++ b/asl_rulebook2/bin/qa-helper/global.css @@ -0,0 +1,30 @@ +body { + position: absolute ; top: 0 ; left: 0 ; bottom: 0 ; right: 0 ; + overflow: hidden ; + display: flex ; background: #f0f0f0 ; +} +ul, ol { margin-top: 0.25em ; } +.quote { font-style: italic ; color: #666 ; } + +#left { flex: 1 ; margin-right: 1em ; } +#qa { margin-top: 0.5em ; overflow: auto ; background: white ; } +#right { display: flex ; flex-direction: column ; flex: 1 ; } +#html-preview { flex: 1 ; } +#json-preview { height: 15em ; min-height: 15em ; } + +#qa { border: 1px solid #444 ; } +#qa .rules { height: 1.8em ; padding: 2px 5px ; border: 1px solid #666 ; } +#qa .qa { margin: 0.5em ; padding: 0.5em ; border: 1px dotted #666 ; background: #ffffe0 ; } +#qa .qa textarea { display: block ; width: calc(100% - 1em) ; resize: vertical ; min-height: 3em ; padding: 2px 5px ; } +#qa .qa textarea.question { height: 8em ; } + +#html-preview { border: 1px solid #444 ; margin-bottom: 1em ; padding: 0.5em ; overflow: auto ; background: white ; } +#html-preview .question { clear: both ; } +#html-preview .answer { clear: both ; margin-top: 0.5em ; } +#html-preview img { float: left ; margin: 0 0.5em 0.5em 0 ; } + +#json-preview { border: 1px solid #444 ; padding: 0.5em ; overflow: auto ; background: white ; } +#json-preview { font-family: monospace ; white-space: pre-wrap ; } + +button#copy-json { position: absolute ; bottom: 5px ; right: 5px ; z-index: 10 ; } +button#add-qa { margin-left: 0.5em ; padding: 0 ; } diff --git a/asl_rulebook2/bin/qa-helper/index.html b/asl_rulebook2/bin/qa-helper/index.html new file mode 100644 index 0000000..ee64b3a --- /dev/null +++ b/asl_rulebook2/bin/qa-helper/index.html @@ -0,0 +1,36 @@ + + + + + Q+A Helper + + + + + + + +

+ +
+ Rules:   + +
+ +
+ +
+ + + + + + + + + + + diff --git a/asl_rulebook2/bin/qa-helper/main.js b/asl_rulebook2/bin/qa-helper/main.js new file mode 100644 index 0000000..a59e03d --- /dev/null +++ b/asl_rulebook2/bin/qa-helper/main.js @@ -0,0 +1,187 @@ +var gQuestionImageUrl = "../../webapp/static/images/question.png" ; +var gAnswerImageUrl = "../../webapp/static/images/answer.png" ; + +// -------------------------------------------------------------------- + +$(document).ready( function() { + + // initialize + $( "input.rules" ).on( "change keyup paste", updatePreviews ) ; + + // initialize + $( "button#copy-json" ).on( "click", function() { + window.getSelection().selectAllChildren( document.getElementById( "json-preview" ) ) ; + document.execCommand( "copy" ) ; + window.getSelection().removeAllRanges() ; + } ) ; + + // initialize + $( "button#add-qa" ).on( "click", addQA ) ; + addQA() ; + + // initialize + $(window).on( "resize", resizeQA ) ; + + // reset + $( "input.rules" ).val( "" ).focus() ; + $( "textarea" ).val( "" ) ; +} ) ; + +// -------------------------------------------------------------------- + +function addQA() +{ + function addText( $elem, text ) { + $elem.insertAtCaret( text ) ; + } + + // resize the existing Q+A + $( "textarea.question" ).css( "height", "2em" ) ; + $( "textarea.answer" ).css( "height", "2em" ) ; + + // add the new Q+A + var $qa = $( [ "
", + "Question:", "", + "Answer:", "", + "
" + ].join( "" ) ) ; + $qa.on( "change keyup paste", updatePreviews ) ; + $qa.find( "textarea" ).on( "focus", function() { + $(this).select() ; + } ) ; + $qa.find( "textarea.answer" ).on( "keydown", function( evt ) { + if ( evt.ctrlKey && evt.key == "x" ) { + addText( $(this), "Yes. " ) ; + evt.preventDefault() ; + } else if ( evt.ctrlKey && evt.key == "c" ) { + addText( $(this), "No. " ) ; + evt.preventDefault() ; + } + } ) ; + $qa.find( "textarea" ).on( "keydown", function( evt ) { + if ( evt.ctrlKey && evt.key == "p" ) { + addText( $(this), "

" ) ; + evt.preventDefault() ; + } else if ( evt.ctrlKey && evt.key == "i" ) { + var selText = $(this).val().substring( $(this)[0].selectionStart, $(this)[0].selectionEnd ) ; + addText( $(this), ""+selText+"" ) ; + evt.preventDefault() ; + } else if ( evt.ctrlKey && evt.key == "e" ) { + var selText = $(this).val().substring( $(this)[0].selectionStart, $(this)[0].selectionEnd ) ; + addText( $(this), ""+selText+"" ) ; + evt.preventDefault() ; + } + } ) ; + $( "#qa" ).append( $qa ) ; + resizeQA() ; + updatePreviews() ; + $qa.find( ".question" ).focus() ; +} + +function resizeQA() +{ + var $jsonPreview = $( "#json-preview" ) ; + var yBottom = $jsonPreview.position().top + $jsonPreview.height() ; + var $qa = $( "#qa" ) ; + var newHeight = yBottom - $qa.position().top + 4 ; + $qa.height( newHeight ) ; +} + +// -------------------------------------------------------------------- + +function updatePreviews() +{ + function extractRuleIds( val ) { + var ruleids = [] ; + val = val.replace( /&/g, "," ) ; + for ( var ruleid of val.split( "," ) ) { + ruleid = ruleid.trim() ; + if ( ! ruleid.match( /^[A-Z][0-9.]+$/ ) ) + continue ; + ruleids.push( '"' + safeJson(ruleid) + '"' ) ; + } + return ruleids ; + } + + function cleanContent( val ) { + val = val.replace( /“/g, '"' ).replace( /”/g, '"' ) ; + val = val.replace( /‘/g, "'" ).replace( /’/g, "'" ) ; + val = val.replace( /`/g, "'" ) ; + val = val.replace( /–/g, "-" ) ; + val = val.replace( />=/g, "≥" ).replace( /<=/g, "≤" ) ; + val = val.replace( /5\/8"/g, "⅝\"" ).replace( /1\/2"/g, "½\"" ) ; + val = val.replace( //g, "" ).replace( /<\/q>/g, "" ) ; + val = val.replace( /\n/g, " " ) ; + val = val.replace( /\s+/g, " " ) ; + return val.trim() ; + } + + function safeJson( val ) { + return val.replace( /"/g, '\\"' ) ; + } + + // process each Q+A + var htmlBuf=[], jsonContent=[] ; + var caption = $( "input.rules" ).val() ; + $( ".qa" ).each( function() { + var question = cleanContent( $(this).children( ".question" ).val() ) ; + var answer = cleanContent( $(this).children( ".answer" ).val() ) ; + // update the HTML preview + htmlBuf.push( "

", "", question, "
" ) ; + htmlBuf.push( "
", "", answer, "
" ) ; + // update the JSON preview + if ( question ) { + jsonContent.push( [ + ' { "question": "' + safeJson(question) + '",', + ' "answers": [ [ "' + safeJson(answer) + '", "sr" ] ]', + ' }' + ].join( "\n" ) ) ; + } else { + jsonContent.push( [ + ' { "answers": [ [ "' + safeJson(answer) + '", "sr" ] ]', + ' }' + ].join( "\n" ) ) ; + } + } ) ; + + // update the previews + $( "#html-preview" ).html( htmlBuf.join("") ) ; + var jsonBuf = [] ; + jsonBuf.push( '{ "caption": "' + safeJson(caption) + '",' ) ; + var ruleids = extractRuleIds( caption ) ; + if ( ruleids.length > 0 ) + jsonBuf.push( ' "ruleids": [ ' + ruleids.join(", ") + ' ],' ) ; + jsonBuf.push( ' "content": [' ) ; + jsonBuf.push( jsonContent.join( ",\n" ) ) ; + jsonBuf.push( ' ]' ) ; + jsonBuf.push( '}' ) ; + $( "#json-preview" ).text( jsonBuf.join("\n") ) ; +} + +// -------------------------------------------------------------------- + +$.fn.extend( { + insertAtCaret: function( myValue ) { + this.each( function() { + if ( document.selection ) { + this.focus() ; + var sel = document.selection.createRange() ; + sel.text = myValue ; + this.focus() ; + } else if ( this.selectionStart || this.selectionStart == "0" ) { + var startPos = this.selectionStart ; + var endPos = this.selectionEnd ; + var scrollTop = this.scrollTop ; + this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos,this.value.length) ; + this.focus() ; + this.selectionStart = startPos + myValue.length ; + this.selectionEnd = startPos + myValue.length ; + this.scrollTop = scrollTop ; + } else { + this.value += myValue ; + this.focus() ; + } + } ) ; + return this ; + } +} ) ;