parent
fee9f49f1c
commit
c44eb5729c
@ -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 "<q> ... </q>" can be used as a shortcut for "<span class='quote'> ... </span>", and there is CSS in the main program for this. |
||||||
|
|
||||||
|
Keyboard shortcuts: |
||||||
|
^p = <p> |
||||||
|
^i = wrap selected text in <em> |
||||||
|
^e = wrap selected text in <q> |
||||||
|
|
||||||
|
Keyboard shortcuts (answers only): |
||||||
|
^X = Yes. |
||||||
|
^c = No. |
@ -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 ; } |
@ -0,0 +1,36 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
|
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<title> Q+A Helper </title> |
||||||
|
<link rel="stylesheet" type="text/css" href="global.css" /> |
||||||
|
</head> |
||||||
|
|
||||||
|
<!-- ----------------------------------------------------------------- --> |
||||||
|
|
||||||
|
<body> |
||||||
|
|
||||||
|
<div id="left"> |
||||||
|
|
||||||
|
<div style="display:flex;align-items:center;"> |
||||||
|
<b>Rules:</b> <input class="rules" style="flex:1;"> |
||||||
|
<button id="add-qa" title="Add another Q+A"> + </button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div id="qa"></div> |
||||||
|
|
||||||
|
</div> |
||||||
|
|
||||||
|
<div id="right"> |
||||||
|
<div id="html-preview"> </div> |
||||||
|
<div id="json-preview"> </div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button id="copy-json"> Copy </button> |
||||||
|
|
||||||
|
</body> |
||||||
|
|
||||||
|
<!-- ----------------------------------------------------------------- --> |
||||||
|
|
||||||
|
<script src="../../webapp/static/jquery/jquery-3.6.0.min.js"></script> |
||||||
|
<script src="main.js"></script> |
@ -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 = $( [ "<div class='qa'>", |
||||||
|
"<b>Question:</b>", "<textarea class='question'></textarea>", |
||||||
|
"<b>Answer:</b>", "<textarea class='answer'></textarea>", |
||||||
|
"</div>" |
||||||
|
].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), "<p> " ) ; |
||||||
|
evt.preventDefault() ; |
||||||
|
} else if ( evt.ctrlKey && evt.key == "i" ) { |
||||||
|
var selText = $(this).val().substring( $(this)[0].selectionStart, $(this)[0].selectionEnd ) ; |
||||||
|
addText( $(this), "<em>"+selText+"</em>" ) ; |
||||||
|
evt.preventDefault() ; |
||||||
|
} else if ( evt.ctrlKey && evt.key == "e" ) { |
||||||
|
var selText = $(this).val().substring( $(this)[0].selectionStart, $(this)[0].selectionEnd ) ; |
||||||
|
addText( $(this), "<q>"+selText+"</q>" ) ; |
||||||
|
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( /<q>/g, "<span class='quote'>" ).replace( /<\/q>/g, "</span>" ) ; |
||||||
|
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( "<div class='question'>", "<img src='"+gQuestionImageUrl+"'>", question, "</div>" ) ; |
||||||
|
htmlBuf.push( "<div class='answer'>", "<img src='"+gAnswerImageUrl+"'>", answer, "</div>" ) ; |
||||||
|
// 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 ; |
||||||
|
} |
||||||
|
} ) ; |
Loading…
Reference in new issue