From 53e14f753fb268c93406b3621feaac9cd2d4e9ae Mon Sep 17 00:00:00 2001 From: Taka Date: Thu, 1 Sep 2022 14:15:00 +1000 Subject: [PATCH] Allow single-line textbox's to be edited as HTML. --- .../data/default-template-pack/extras/grid.j2 | 2 +- vasl_templates/webapp/main.py | 6 + vasl_templates/webapp/static/css/desktop.css | 11 + .../static/css/edit-html-textbox-dialog.css | 4 + .../webapp/static/css/edit-vo-dialog.css | 15 +- .../webapp/static/css/html-editor.css | 22 ++ vasl_templates/webapp/static/css/main.css | 2 +- .../webapp/static/css/tabs-extras.css | 1 + .../webapp/static/css/tabs-scenario.css | 21 +- vasl_templates/webapp/static/extras.js | 26 ++- vasl_templates/webapp/static/html-editor.js | 203 +++++++++++++++--- vasl_templates/webapp/static/main.js | 41 +++- .../webapp/static/scenario-upload.js | 4 +- vasl_templates/webapp/static/scenarios.js | 17 +- vasl_templates/webapp/static/simple_notes.js | 9 +- vasl_templates/webapp/static/snippets.js | 44 ++-- vasl_templates/webapp/static/utils.js | 3 + vasl_templates/webapp/static/vassal.js | 9 +- vasl_templates/webapp/static/vo2.js | 42 +++- .../templates/edit-html-textbox-dialog.html | 5 + vasl_templates/webapp/templates/index.html | 2 + .../webapp/templates/tabs-scenario.html | 12 +- .../tests/fixtures/new-default-scenario.json | 7 +- .../webapp/tests/test_capabilities.py | 71 +++--- .../webapp/tests/test_default_scenario.py | 15 +- .../tests/test_dirty_scenario_checks.py | 45 ++-- .../tests/{test_sanitize.py => test_html.py} | 58 ++++- .../webapp/tests/test_scenario_persistence.py | 11 +- .../webapp/tests/test_scenario_search.py | 9 +- vasl_templates/webapp/tests/test_snippets.py | 5 +- vasl_templates/webapp/tests/utils.py | 19 +- 31 files changed, 573 insertions(+), 168 deletions(-) create mode 100644 vasl_templates/webapp/static/css/desktop.css create mode 100644 vasl_templates/webapp/static/css/edit-html-textbox-dialog.css create mode 100644 vasl_templates/webapp/templates/edit-html-textbox-dialog.html rename vasl_templates/webapp/tests/{test_sanitize.py => test_html.py} (87%) diff --git a/vasl_templates/webapp/data/default-template-pack/extras/grid.j2 b/vasl_templates/webapp/data/default-template-pack/extras/grid.j2 index 7f5064b..f697425 100644 --- a/vasl_templates/webapp/data/default-template-pack/extras/grid.j2 +++ b/vasl_templates/webapp/data/default-template-pack/extras/grid.j2 @@ -3,7 +3,7 @@ - + diff --git a/vasl_templates/webapp/main.py b/vasl_templates/webapp/main.py index f38e076..99cb059 100644 --- a/vasl_templates/webapp/main.py +++ b/vasl_templates/webapp/main.py @@ -209,6 +209,12 @@ def get_app_config(): [ "specialChars", "flags", "emoji" ], [ "removeformat", "historyUndo", "historyRedo", "viewHTML", "fullscreen" ], ] ), + "html-textbox-dialog": get_json_val( "TRUMBOWYG_BUTTONS_HTML_TEXTBOX_DIALOG", [ + [ "strong", "em", "underline", "del", "superscript", "subscript" ], + [ "foreColor", "backColor" ], + [ "specialChars", "flags", "emoji" ], + [ "removeformat", "historyUndo", "historyRedo", "viewHTML", "fullscreen" ], + ] ), } return jsonify( vals ) diff --git a/vasl_templates/webapp/static/css/desktop.css b/vasl_templates/webapp/static/css/desktop.css new file mode 100644 index 0000000..dee549a --- /dev/null +++ b/vasl_templates/webapp/static/css/desktop.css @@ -0,0 +1,11 @@ +/* NOTE: This file contains work-arounds for the desktop app. */ + +fieldset[name='scenario'] { max-height: 251px ; } + +/* these work around vertical alignment of text in input controls */ +#panel-scenario label { margin-bottom: -1px ; } +#panel-scenario label[for="TURN_TRACK_NTURNS"], #panel-scenario label[for="PLAYER_1"], #panel-scenario label[for="PLAYER_2"] { + line-height: 20px ; +} +#panel-scenario input[name="SCENARIO_DATE"] { padding-top: 2px ; } + diff --git a/vasl_templates/webapp/static/css/edit-html-textbox-dialog.css b/vasl_templates/webapp/static/css/edit-html-textbox-dialog.css new file mode 100644 index 0000000..f0bea07 --- /dev/null +++ b/vasl_templates/webapp/static/css/edit-html-textbox-dialog.css @@ -0,0 +1,4 @@ +#edit-html_textbox-dialog { overflow: hidden ; padding-bottom: 0.75em !important ; } +.ui-dialog.edit-html_textbox .ui-dialog-buttonpane { border: none ; margin-top: 0 !important ; padding-top: 0 !important ; } + +.ui-dialog.edit-html_textbox .container { height: 100% ; display: flex ; flex-direction: row ; } diff --git a/vasl_templates/webapp/static/css/edit-vo-dialog.css b/vasl_templates/webapp/static/css/edit-vo-dialog.css index 09c4774..1747303 100644 --- a/vasl_templates/webapp/static/css/edit-vo-dialog.css +++ b/vasl_templates/webapp/static/css/edit-vo-dialog.css @@ -12,15 +12,22 @@ .ui-dialog.edit-vo .capabilities { margin-top: 0.25em ; display: flex ; flex-direction: column ; } .ui-dialog.edit-vo .comments { margin-top: 0.5em ; display: flex ; flex-direction: column ; } +.ui-dialog.edit-vo .brewup { color: #a04010 ; } +.ui-dialog.edit-vo .split-mg-red { color: #a04010 ; } + +.ui-dialog.edit-vo #vo_capabilities-sortable { overflow-x: hidden ; } +.ui-dialog.edit-vo #vo_comments-sortable { overflow-x: hidden ; } .ui-dialog.edit-vo .fieldset { flex-grow: 1 ; display: flex ; flex-direction: column ; align-items: stretch ; } .ui-dialog.edit-vo .fieldset { margin: 0 ; padding: 5px 8px 8px 8px ; border: 1px solid #aaa ; } .ui-dialog.edit-vo .fieldset-legend { color: #333 ; font-weight: bold ; } .ui-dialog.edit-vo .fieldset ul { flex-grow: 1 ; margin: 0 0 5px 0 ; list-style-type: none ; overflow-y: auto ; } -.ui-dialog.edit-vo .fieldset li { margin: 2px ; padding: 2px 0px 2px 5px ; background: #eee ; } -.ui-dialog.edit-vo .fieldset li div { display: flex ; align-items: center ; } -.ui-dialog.edit-vo .fieldset li img.dragger { height: 1em ; margin-right: 5px ; } -.ui-dialog.edit-vo .fieldset li input[type='text'] { flex-grow: 1 ; margin-right: 1em ; } +.ui-dialog.edit-vo .fieldset li { margin: 2px ; padding: 2px 0px 2px 5px ; background: #eee ; max-width: 100% ; } +.ui-dialog.edit-vo .fieldset li { height: 22px ; border: none !important ; } +.ui-dialog.edit-vo .fieldset li:hover { cursor: auto ; } +.ui-dialog.edit-vo .fieldset li > div { display: flex ; align-items: center ; padding-right: 2px ; } +.ui-dialog.edit-vo .fieldset li img.dragger { height: 1em ; margin-right: 5px ; cursor: pointer ; } +.ui-dialog.edit-vo .fieldset li div.html-textbox { flex-grow: 1 ; } .ui-dialog.edit-vo .fieldset .footer { margin-top: 0.25em ; display: flex ; align-items: center ; } .ui-dialog.edit-vo .fieldset .footer img.trash { margin: 3px 5px ; height: 24px ; } diff --git a/vasl_templates/webapp/static/css/html-editor.css b/vasl_templates/webapp/static/css/html-editor.css index ac9caeb..0689d2a 100644 --- a/vasl_templates/webapp/static/css/html-editor.css +++ b/vasl_templates/webapp/static/css/html-editor.css @@ -62,3 +62,25 @@ .Xtrumbowyg-specialChars-button { color: #555 ; } .trumbowyg-indent-button svg, .trumbowyg-outdent-button svg { width: 13px ; } .trumbowyg-indent-button svg { transform: scaleX(-1) ; } + +/* -------------------------------------------------------------------- */ + +div.html-textbox { + flex-grow: 100 ; + height: 20px ; max-height: 20px ; + /* NOTE: We hide vertical overflow when the content goes multi-line, and the h-scrollbar if very long words + * (i.e. without spaces) are present. In the latter case, the layout breaks if the control is in a flexbox, + * since it just expands out, regardless of any max-width setting :-/, so we work-around this by dynamically + * setting a max-width on the parent row. + * */ + overflow: hidden ; + line-height: 1.4em ; /* FUDGE! This works around a vertical-alignment problem in the desktop app. */ + padding: 0 12px 0 5px ; + border: 1px solid #c5c5c5 ; background: white ; + /* NOTE: This makes the control scroll horizontally (like a real textbox), but it breaks padding (which is maybe + * acceptable), and max-width (which is not). Things don't work well when these things are in a flexbox, and while + * we can sorta get things working by disabling that, and dynamically setting the width along with max-width, + * weird things happen when we resize the enclosing panel :-/ + white-space: nowrap ; overflow-x: hidden ; + */ +} diff --git a/vasl_templates/webapp/static/css/main.css b/vasl_templates/webapp/static/css/main.css index 712f40f..1db3724 100644 --- a/vasl_templates/webapp/static/css/main.css +++ b/vasl_templates/webapp/static/css/main.css @@ -7,7 +7,7 @@ body { font-family: Arial, Helvetica, sans-serif ; font-size: 16px ; } p { margin-bottom: 0.5em ; } ul, ol { margin: 0.5em 0 0 1.25em ; } -input[type="text"] { height: 20px ; border: 1px solid #c5c5c5 ; padding: 0 2px ; } +input[type="text"] { height: 20px ; border: 1px solid #c5c5c5 ; padding: 0 5px ; } label { height: 1.25em ; } /* -------------------------------------------------------------------- */ diff --git a/vasl_templates/webapp/static/css/tabs-extras.css b/vasl_templates/webapp/static/css/tabs-extras.css index ec57232..498c0d6 100644 --- a/vasl_templates/webapp/static/css/tabs-extras.css +++ b/vasl_templates/webapp/static/css/tabs-extras.css @@ -22,6 +22,7 @@ #tabs-extras .right-panel table { margin-top: 1em ; } #tabs-extras .right-panel td.caption { font-weight: bold ; padding-right: 0.25em ; } +#tabs-extras .right-panel td.value { display: flex ; align-items: center ; } #tabs-extras .right-panel .snippet-control { margin-top: 0.5em ; } #tabs-extras .right-panel .footer { margin-top: 1em ; font-size: 80% ; font-style: italic ; color: #444 ; } diff --git a/vasl_templates/webapp/static/css/tabs-scenario.css b/vasl_templates/webapp/static/css/tabs-scenario.css index 0a7c584..6c539a6 100644 --- a/vasl_templates/webapp/static/css/tabs-scenario.css +++ b/vasl_templates/webapp/static/css/tabs-scenario.css @@ -1,26 +1,33 @@ /* -------------------------------------------------------------------- */ #panel-scenario { display: flex ; flex-direction: column ; } -fieldset[name='scenario'] { min-height: 91px ; max-height: 265px ; overflow: hidden ; } +fieldset[name='scenario'] { + min-height: 91px ; overflow: hidden ; + max-height: 255px ; /* nb: overridden in desktop.css */ +} #panel-scenario .row { display: flex ; align-items: center ; margin-bottom: 2px ; } -#panel-scenario input { flex-grow: 1 ; } -#panel-scenario input[name='SCENARIO_ID'] { margin-left: 0.25em ; width: 70px ; flex-grow: 0 ; text-align: right ; } +#panel-scenario div.html-textbox[name='SCENARIO_ID'] { margin-left: 0.25em ; width: 70px ; flex-grow: 0 ; } #panel-scenario .scenario-search { width: 25px ; height: 22px ; margin: 0 0 0 0.25em ; padding: 0 ; } #panel-scenario .scenario-search img { margin-top: 2px ; width: 16px ; } -#panel-scenario input[name='SCENARIO_DATE'] { width: 6em ; flex-grow: 0 ; } +#panel-scenario input[name='SCENARIO_DATE'] { + /* nb: override in desktop.css for v-alignment */ + width: 6em ; flex-grow: 0 ; color: #333 ; +} #panel-scenario button#turn-track-settings { width: 25px ; height: 24px ; margin-right: 5px ; padding: 3px 2px 2px 2px ; } #panel-scenario .select2[name="PLAYER_1"] { flex: 1 ; } #panel-scenario .select2[name="PLAYER_2"] { flex: 1 ; } -#panel-scenario input[name="PLAYER_1_DESCRIPTION"], -#panel-scenario input[name="PLAYER_2_DESCRIPTION"] { +#panel-scenario div.html-textbox[name="PLAYER_1_DESCRIPTION"], +#panel-scenario div.html-textbox[name="PLAYER_2_DESCRIPTION"] { font-size: 75% ; + height: 14px ; max-height: 14px ; line-height: 14px ; } -#panel-scenario label { margin-top: 2px ; font-weight: bold ; width: 4.75em ; } +/* nb: see desktop.css for overrides to this panel's labels */ +#panel-scenario label { height: 18px ; font-weight: bold ; width: 4.75em ; } #panel-scenario label.header { font-weight: bold ; width: 3em ; text-align: center ; } #panel-scenario .select2-container { margin-right: 2px ; } diff --git a/vasl_templates/webapp/static/extras.js b/vasl_templates/webapp/static/extras.js index deb3b65..cf7c607 100644 --- a/vasl_templates/webapp/static/extras.js +++ b/vasl_templates/webapp/static/extras.js @@ -95,6 +95,16 @@ function _show_extra_template( template_id ) if ( template_info.params[i].description ) buf.push( " title='" + escapeHTML(template_info.params[i].description) + "'" ) ; buf.push( ">" ) ; + } else if ( template_info.params[i].type == "html_textbox" ) { + buf.push( "
" ) ; + if ( template_info.params[i].default ) + buf.push( sanitizeHTML( template_info.params[i].default ) ) ; + buf.push( "
" ) ; } else if ( template_info.params[i].type === "select" ) { buf.push( " - param.type = "input" ; + // we have an or HTML textbox + if ( param.name.substring( param.name.length-1 ) === "*" ) { + param.type = "html_textbox" ; + param.name = param.name.substring( 0, param.name.length-1 ) ; + } else + param.type = "input" ; // extract the default value and field width pos = val.indexOf( "/" ) ; if ( pos === -1 ) @@ -246,7 +266,7 @@ function _parse_extra_template( template_id, template ) function fixup_template_parameters( template ) { // identify any non-standard template parameters - var regex = /\{\{([A-Z0-9_]+?):.*?\}\}/g ; + var regex = /\{\{([A-Z0-9_]+?)\*?:.*?\}\}/g ; var matches = [] ; var match ; while( (match = regex.exec( template )) !== null ) diff --git a/vasl_templates/webapp/static/html-editor.js b/vasl_templates/webapp/static/html-editor.js index 5d1b731..b3d0d92 100644 --- a/vasl_templates/webapp/static/html-editor.js +++ b/vasl_templates/webapp/static/html-editor.js @@ -1,6 +1,8 @@ +gEditHtmlTextboxDlgState = null ; + // -------------------------------------------------------------------- -function initTrumbowyg( $elem, buttons, $parentDlg ) +function initTrumbowyg( $ctrl, buttons, $parentDlg ) { // initialize var nats = get_sorted_nats().filter( @@ -14,7 +16,7 @@ function initTrumbowyg( $elem, buttons, $parentDlg ) // from the WYSIWYG control to the raw HTML textarea, it doesn't really help, since manipulating // the content in the