Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9f697f6
poc
garethbowen Sep 28, 2025
7ccc1a6
day 2 poc
garethbowen Sep 29, 2025
4892b83
most of markdown is working
garethbowen Sep 30, 2025
2003aed
add more test types
garethbowen Sep 30, 2025
2c0512a
more markup handled
garethbowen Oct 1, 2025
435be16
format hints too
garethbowen Oct 1, 2025
0379758
much cleanup, and supporting more cases
garethbowen Oct 3, 2025
5d2ae7f
revert temporary package hack
garethbowen Oct 3, 2025
92e5533
fix test
garethbowen Oct 3, 2025
07639b1
fix test
garethbowen Oct 3, 2025
a1108b9
fix e2e test - labels no longer have spans inside
garethbowen Oct 5, 2025
ddcbb48
label must be a block elem now, bring * inline with first elem
garethbowen Oct 5, 2025
83fe18f
remove check for asterisk which is applied via css now
garethbowen Oct 6, 2025
8ea16a3
added a heap of testing and cleaned things up
garethbowen Oct 7, 2025
8f86835
check for undefined
garethbowen Oct 7, 2025
bd5175a
fix test
garethbowen Oct 7, 2025
1e56c7e
add test for jr:choice-name
garethbowen Oct 7, 2025
b0d3c00
fixed a bug with single quoted styles, and cleanup
garethbowen Oct 8, 2025
748d0f4
add changeset
garethbowen Oct 8, 2025
511193b
update the feature matrix
garethbowen Oct 8, 2025
4ebe5f1
review feedback
garethbowen Oct 9, 2025
15e5a46
fix test name
garethbowen Oct 9, 2025
7c80160
review feedback
garethbowen Oct 12, 2025
37fe793
modify test to allow labels to have inner elements
garethbowen Oct 12, 2025
8380785
add non breaking spaces to test indentation
garethbowen Oct 13, 2025
25901c5
update xlsx with list format instead of indentation
garethbowen Oct 13, 2025
a3a27af
fix label selector
garethbowen Oct 13, 2025
1a1c11c
provide specific overrides for some central styles
garethbowen Oct 14, 2025
c114e34
use markdown for groups, repeats, and select options
garethbowen Oct 15, 2025
c9e9995
fix tests
garethbowen Oct 16, 2025
21d2df0
markdown validation messages
garethbowen Oct 16, 2025
ebb6434
review feedback
garethbowen Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/tidy-ties-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@getodk/xforms-engine': minor
'@getodk/web-forms': minor
'@getodk/scenario': minor
'@getodk/common': minor
---

Added support for markdown formatting in labels, hints, and constraints
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ This section is auto generated. Please update `feature-matrix.json` and then run
<summary>

<!-- prettier-ignore -->
##### Descriptions and Annotations<br/>🟩🟩🟩🟩⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜ 28\%
##### Descriptions and Annotations<br/>🟩🟩🟩🟩🟩🟩⬜⬜⬜⬜⬜⬜⬜⬜⬜ 42\%

</summary>
<br/>
Expand All @@ -200,8 +200,8 @@ This section is auto generated. Please update `feature-matrix.json` and then run
| guidance hint | |
| form translations | ✅ |
| form translations with ref to other<br/>field | ✅ |
| Markdown | |
| Inline HTML | |
| Markdown | |
| Inline HTML | |
| Form attachments | |
| image | 🚧 |
| big-image | |
Expand Down
4 changes: 2 additions & 2 deletions feature-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@
"guidance hint": "",
"form translations": "✅",
"form translations with ref to other field": "✅",
"Markdown": "",
"Inline HTML": "",
"Markdown": "",
"Inline HTML": "",
"Form attachments": "",
"image": "🚧",
"big-image": "",
Expand Down
213 changes: 213 additions & 0 deletions packages/common/src/fixtures/notes/3-notes-with-markdown.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:jr="http://openrosa.org/javarosa" xmlns:orx="http://openrosa.org/xforms"
xmlns:odk="http://www.opendatakit.org/xforms">
<h:head>
<h:title>Notes</h:title>
<model odk:xforms-version="1.0.0">
<itext>
<translation lang="English">
<text id="q1:label">
<value>_Translated_</value>
</text>
<text id="/data/group/name:jr:constraintMsg">
<value>Must be **longer** than 3</value>
</text>
<text id="/data/group/selected:jr:requiredMsg">
<value>You must select _something_</value>
</text>
</translation>
<translation lang="Español">
<text id="q1:label">
<value>_Traducida_</value>
</text>
</translation>
</itext>
<instance>
<data id="notes" version="20240802121047">
<group>
<headings />
<name/>
<userentered/>
<translated/>
<selected/>
<selectedminimal/>
<dropdown/>
<rank/>
<note/>
</group>
<group2>
<input/>
</group2>
<rep>
<a>default value</a>
</rep>
<meta>
<instanceID />
</meta>
</data>
</instance>
<instance id="selectoptions">
<root>
<item>
<value>plain</value>
<label>plain text</label>
</item>
<item>
<value>strong</value>
<label>**strong style**</label>
</item>
<item>
<value>emphasis</value>
<label>_italics_</label>
</item>
</root>
</instance>
<bind nodeset="/data/group/headings" readonly="true()" type="string" />
<bind nodeset="/data/group/name" type="string" constraint="string-length(.) > 2"
jr:constraintMsg="jr:itext('/data/group/name:jr:constraintMsg')" required="true()" />
<bind nodeset="/data/group/userentered" readonly="true()" type="string" />
<bind nodeset="/data/group/translated" readonly="true()" type="string" />
<bind nodeset="/data/group/selected" type="string" required="true()"
jr:requiredMsg="jr:itext('/data/group/selected:jr:requiredMsg')"/>
<bind nodeset="/data/group/selectedminimal" type="string" />
<bind nodeset="/data/group/dropdown" type="string"/>
<bind nodeset="/data/group/rank" type="rank"/>
<bind nodeset="/data/group2/input" type="string"/>
<bind nodeset="/data/rep/a" type="string" />
<bind nodeset="/data/group/note" readonly="true()" type="note" />
</model>
</h:head>
<h:body>
<!-- need to test in other places, like validation, select item labels, etc -->
<group appearance="field-list" ref="/data/group">
<input ref="/data/group/headings">
<label>You _can_ try this with other &lt;span style="color:red;font-family:cursive"&gt;colours&lt;/span&gt;; **most** primary colours should work.</label>
</input>
<input ref="/data/group/name">
<label>What's your name?</label>
<hint>Type **your name** below</hint>
</input>
<input ref="/data/group/userentered">
<label>*You said:&lt;span style="color:red;font-family:cursive"&gt;<output value="/data/group/dropdown" />&lt;/span&gt;* but should it be markeddown?</label>
</input>
<input ref="/data/group/translated">
<label ref="jr:itext('q1:label')" />
</input>
<group ref="/data/group2">
<label>*Groups*</label>
<input ref="/data/group2/input">
<label>label</label>
</input>
</group>
<group ref="/data/rep">
<label>&lt;span style="color:green"&gt;Repeat group&lt;/span&gt;</label>
<repeat nodeset="/data/rep">
<input ref="/data/rep/a">
<label>Repeat input a</label>
</input>
</repeat>
</group>
<select1 ref="/data/group/selected">
<label>Select options</label>
<itemset nodeset="instance('selectoptions')/root/item">
<value ref="value" />
<label ref="label" />
</itemset>
</select1>
<select1 appearance="minimal" ref="/data/group/selectedminimal">
<label>Select options minimal</label>
<item>
<label>&lt;span style="color:green"&gt;Yes&lt;/span&gt;</label>
<value>yes</value>
</item>
<item>
<label>&lt;span style="color:red"&gt;No&lt;/span&gt;</label>
<value>no</value>
</item>
</select1>
<select appearance="minimal" ref="/data/group/dropdown">
<label>Select multiple minimal</label>
<item>
<label>&lt;span style="color:green"&gt;Yes&lt;/span&gt;</label>
<value>yes</value>
</item>
<item>
<label>&lt;span style="color:red"&gt;No&lt;/span&gt;</label>
<value>no</value>
</item>
</select>
<odk:rank ref="/data/group/rank">
<label>Rank this <em>stuff</em></label>
<item>
<label>&lt;span style="color:green"&gt;Green&lt;/span&gt;</label>
<value>A</value>
</item>
<item>
<label>&lt;span style="color:red"&gt;Red&lt;/span&gt;</label>
<value>B</value>
</item>
<item>
<label>&lt;span style="color:blue"&gt;Blue&lt;/span&gt;</label>
<value>C</value>
</item>
<item>
<label>&lt;span style="color:yellow"&gt;Yellow&lt;/span&gt;</label>
<value>D</value>
</item>
</odk:rank>
<input ref="/data/group/note">
<label>
# heading 1
## heading 2
### heading 3
#### heading 4
##### heading 5
###### heading 6

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Indented text

[get odk](http://www.getodk.org)
&lt;br/&gt;&lt;br/&gt;
Escaping: \*hello\*
&lt;br/&gt;&lt;br/&gt;
&lt;span style="color:red;font-family:cursive"&gt;signature&lt;/span&gt;
&lt;br/&gt;&lt;br/&gt;

&lt;p style="text-align:center"&gt;
centered paragraph
&lt;/p&gt;

&lt;br/&gt;&lt;br/&gt;


&lt;div style="text-align:center"&gt;centered div&lt;/div&gt;

&lt;table border="1"&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;once&lt;/td&gt;
&lt;td&gt;twice&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

- unordered list 1
- unordered list 5
- unordered list 3


1. ordered list 1
2. ordered list 2
3. ordered list 3

_styles **can &lt;span style="color:red;"&gt;be&lt;/span&gt; nested** inside_

</label>
</input>
</group>
</h:body>
</h:html>

Large diffs are not rendered by default.

Binary file not shown.
8 changes: 4 additions & 4 deletions packages/common/src/fixtures/select/1-static-selects.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
<itext>
<translation lang="French">
<text id="fruit:label">
<value>1. Sélectionnez un fruit</value>
<value>Sélectionnez un fruit</value>
</text>
<text id="fruits:label">
<value>2. Sélectionnez plusieurs fruits</value>
<value>Sélectionnez plusieurs fruits</value>
</text>
<text id="fruit:mango">
<value>mangue</value>
Expand Down Expand Up @@ -87,10 +87,10 @@
</translation>
<translation lang="English" default="true()">
<text id="fruit:label">
<value>1. Select a fruit</value>
<value>Select a fruit</value>
</text>
<text id="fruits:label">
<value>2. Select multiple fruits</value>
<value>Select multiple fruits</value>
</text>
<text id="fruit:mango">
<value>Mango</value>
Expand Down
49 changes: 29 additions & 20 deletions packages/scenario/src/jr/Scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
RestoreFormInstanceInput,
RootNode,
SelectNode,
TextRange,
} from '@getodk/xforms-engine';
import { constants as ENGINE_CONSTANTS } from '@getodk/xforms-engine';
import type { Accessor, Owner, Setter } from 'solid-js';
Expand All @@ -32,7 +33,6 @@ import { ImplementationPendingError } from '../error/ImplementationPendingError.
import { UnclearApplicabilityError } from '../error/UnclearApplicabilityError.ts';
import type { ReactiveScenario } from '../reactive/ReactiveScenario.ts';
import { SharedJRResourceService } from '../resources/SharedJRResourceService.ts';
import type { JRFormEntryCaption } from './caption/JRFormEntryCaption.ts';
import type { BeginningOfFormEvent } from './event/BeginningOfFormEvent.ts';
import type { EndOfFormEvent } from './event/EndOfFormEvent.ts';
import { PositionalEvent } from './event/PositionalEvent.ts';
Expand Down Expand Up @@ -947,26 +947,17 @@ export class Scenario {
return this.isClientControlled(node);
}

/**
* **PORTING NOTES**
*
* This method is proposed as an alternative to
* {@link JRFormEntryCaption.getQuestionText}, intended to:
*
* - intended to be roughly equivalent in semantics without reliance on that
* class, viewed as an aspect of JavaRosa internal APIs
*
* - Provide similar positional semantics to other existing {@link Scenario}
* methods/web forms extensions thereof, where the call site expresses the
* expected XPath reference of the node at the current positional state.
*/
proposed_getQuestionLabelText(options: AssertCurrentReferenceOptions): string {
getQuestionLabelText(options: AssertCurrentReferenceOptions): string {
return this.getQuestionLabel(options).asString;
}

getQuestionLabel(options: AssertCurrentReferenceOptions): TextRange<'label'> {
const event = this.getSelectedPositionalEvent();

this.assertReference(event, options.assertCurrentReference);

const { currentState } = event.node;
const label = currentState.label?.asString;
const label = currentState.label;

if (label == null) {
throw new Error(`Question node with reference ${currentState.reference} has no label`);
Expand All @@ -975,6 +966,21 @@ export class Scenario {
return label;
}

getQuestionHint(options: AssertCurrentReferenceOptions): TextRange<'hint'> {
const event = this.getSelectedPositionalEvent();

this.assertReference(event, options.assertCurrentReference);

const { currentState } = event.node;
const hint = currentState.hint;

if (hint == null) {
throw new Error(`Question node with reference ${currentState.reference} has no hint`);
}

return hint;
}

private getCurrentSelectNode(options: AssertCurrentReferenceOptions): SelectNode {
const { assertCurrentReference } = options;
const event = this.getSelectedPositionalEvent();
Expand Down Expand Up @@ -1020,14 +1026,17 @@ export class Scenario {
proposed_getSelectedOptionLabelsAsText(
options: AssertCurrentReferenceOptions
): readonly string[] {
const node = this.getCurrentSelectNode(options);
return this.getSelectedOptionLabels(options).map((option) => option.asString);
}

getSelectedOptionLabels(
options: AssertCurrentReferenceOptions
): ReadonlyArray<TextRange<'item-label'>> {
const node = this.getCurrentSelectNode(options);
return node.currentState.value.map((item) => {
const option = node.getValueOption(item);

assert(option != null);

return option.label.asString;
return option.label;
});
}

Expand Down
3 changes: 1 addition & 2 deletions packages/scenario/test/label-hint-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ describe('`<label>` and/or `<hint>` text', () => {

scenario.next('/data/question');

// String innerText = scenario.getQuestionAtIndex().getLabelInnerText();
const text = scenario.proposed_getQuestionLabelText({
const text = scenario.getQuestionLabelText({
assertCurrentReference: '/data/question',
});

Expand Down
Loading
Loading