Skip to content

Commit f7dcf1e

Browse files
committed
feat: add edit button
1 parent 98af277 commit f7dcf1e

File tree

10 files changed

+107
-92
lines changed

10 files changed

+107
-92
lines changed

css/styles.css

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ thead {
77
border-bottom: 3px solid black;
88
}
99

10+
thead th {
11+
position: relative;
12+
}
13+
1014
tbody tr:nth-child(even) {
1115
background-color: #ededed;
1216
}
@@ -15,18 +19,18 @@ tbody tr:nth-child(odd) {
1519
background-color: #f6f6f6;
1620
}
1721

18-
thead tr th:has(+th[class="formula"]),
19-
tbody tr th:last-of-type,
20-
thead tr th[class="formula"]:has(+th),
21-
tbody tr td:first-of-type:has(+td) {
22+
thead th:has(+th[class="formula"]),
23+
tbody th:last-of-type,
24+
thead th[class="formula"]:has(+th),
25+
tbody td:first-of-type:has(+td) {
2226
border-right: 3px solid black;
2327
}
2428

2529
thead .formula-input-wrapper input {
2630
width: 8rem !important;
2731
}
2832

29-
tbody .formula-input-wrapper input {
33+
tbody input {
3034
width: 3.5rem !important;
3135
text-align: center;
3236
}
@@ -36,6 +40,20 @@ td, th {
3640
padding: 0.5rem;
3741
height: 3.5rem;
3842
text-align: center;
43+
min-width: 5rem;
44+
}
45+
46+
thead th button {
47+
position: absolute;
48+
top: 0;
49+
right: 0;
50+
border-top: none;
51+
border-right: none;
52+
border-left: 1px solid black;
53+
border-bottom: 1px solid black;
54+
background: transparent;
55+
cursor: pointer;
56+
font-size: 12px;
3957
}
4058

4159
.bit, .variable {

templates/formulation.xhtml.j2

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323

2424
<!-- Input field for the results of the formula. -->
2525
<td>
26-
<div class="formula-input-wrapper">
27-
<input type="text" name="{{ get_result_input_name(row) }}" maxlength="1" pattern="0|1" class="formula-input" style="display: none" qpy:correct-response="{{ get_correct_response(row) }}" />
28-
<span class="formula-display" style="display: none;" />
29-
</div>
26+
<input type="text" name="{{ get_result_input_name(row) }}" maxlength="1" pattern="0|1" class="formula-input" qpy:correct-response="{{ get_correct_response(row) }}" />
3027
</td>
3128
</tr>
3229
{% endfor %}

ts/.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ts/parser/generated/**/*
1+
src/parser/generated/**/*

ts/src/formula_input_element.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ export class FormulaInput {
125125
this.#inputElement.addEventListener("input", () => {
126126
this.setInputValidity("valid");
127127
});
128-
129-
this.#displayElement.addEventListener("click", () => this.viewInputField());
130128
}
131129
}
132130

ts/src/intermediate_formulas.ts

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
import { OutputFormat } from "./parser/parser";
2-
import { createFormulaInput, type FormulaInput } from "./formula_input_element";
2+
import { createFormulaInput } from "./formula_input_element";
33

44
const tableElement = document.querySelector("table")!;
55

6+
function createIntermediateFormulaHeader(
7+
formula: string,
8+
formulaId: number,
9+
format: OutputFormat,
10+
data: Record<string, unknown>,
11+
enabled: boolean,
12+
): HTMLTableCellElement {
13+
const cellElement = document.createElement("th");
14+
15+
const formulaInput = createFormulaInput(formula, format, enabled);
16+
cellElement.appendChild(formulaInput.element);
17+
18+
if (enabled) {
19+
formulaInput.inputElement.addEventListener("change", () => {
20+
const formulas = (data["intermediate-formulas"] ?? {}) as Record<string, string>;
21+
formulas[formulaId] = formulaInput.getInput();
22+
data["intermediate-formulas"] = formulas;
23+
});
24+
25+
const editButton = document.createElement("button");
26+
editButton.tabIndex = -1;
27+
editButton.innerHTML = "✏️";
28+
29+
editButton.addEventListener("mousedown", (e) => {
30+
// This prevents the edit button to trigger the blur event of the input element.
31+
e.preventDefault();
32+
});
33+
34+
editButton.addEventListener("click", (e) => {
35+
e.preventDefault();
36+
e.stopPropagation();
37+
formulaInput.viewInputField();
38+
});
39+
cellElement.appendChild(editButton);
40+
}
41+
42+
return cellElement;
43+
}
44+
645
export function setupButtonToAddIntermediateFormula(attempt: object, total_rows: number, format: OutputFormat) {
746
// @ts-expect-error Attempt object definition is not here.
847
const data = attempt.data as Record<string, unknown>;
@@ -15,31 +54,22 @@ export function setupButtonToAddIntermediateFormula(attempt: object, total_rows:
1554
const rows = tableElement.querySelectorAll("tr");
1655

1756
for (const [index, row] of rows.entries()) {
18-
let cellElement, intermediateFormulaInput;
57+
let cellElement;
1958
if (index === 0) {
2059
// The first row of the table contains the formulas.
21-
cellElement = document.createElement("th");
22-
intermediateFormulaInput = createIntermediateFormulaInput(
60+
cellElement = createIntermediateFormulaHeader(
2361
"",
2462
intermediateFormulaId,
2563
format,
26-
true,
2764
data,
65+
// @ts-expect-error Attempt object definition is not here.
66+
!attempt.readOnly,
2867
);
2968
initValues(data, intermediateFormulaId, total_rows);
3069
} else {
31-
cellElement = document.createElement("td");
32-
intermediateFormulaInput = createIntermediateResultInput(
33-
"",
34-
intermediateFormulaId,
35-
index - 1,
36-
format,
37-
true,
38-
data,
39-
);
70+
cellElement = createIntermediateResultInput("", intermediateFormulaId, index - 1, true, data);
4071
}
4172

42-
cellElement.appendChild(intermediateFormulaInput.element);
4373
row.appendChild(cellElement);
4474
}
4575

@@ -58,7 +88,7 @@ function initValues(data: Record<string, unknown>, intermediateFormulaId: number
5888
data["intermediate-results"] = intermediateResults;
5989
}
6090

61-
export function viewExistingIntermediateFormulas(format: OutputFormat, attempt: object) {
91+
export function initIntermediateFormulas(format: OutputFormat, attempt: object) {
6292
// @ts-expect-error Attempt object definition is not here.
6393
const data = attempt.data as Record<string, unknown>;
6494
// @ts-expect-error Attempt object definition is not here.
@@ -69,83 +99,58 @@ export function viewExistingIntermediateFormulas(format: OutputFormat, attempt:
6999
const rows = tableElement.querySelectorAll("tr");
70100

71101
for (const [intermediateFormulaId, intermediateFormula] of Object.entries(intermediateFormulas)) {
72-
const cellElement = document.createElement("th");
73-
const intermediateFormulaInput = createIntermediateFormulaInput(
102+
const cellElement = createIntermediateFormulaHeader(
74103
intermediateFormula,
75104
parseInt(intermediateFormulaId),
76105
format,
77-
isActive,
78106
data,
107+
isActive,
79108
);
80-
cellElement.appendChild(intermediateFormulaInput.element);
81109
rows[0].appendChild(cellElement);
82110

83111
for (const [index, intermediateResult] of Object.entries(intermediateResults[intermediateFormulaId])) {
84-
const cellElement = document.createElement("td");
85-
const intermediateResultInput = createIntermediateResultInput(
112+
const cellElement = createIntermediateResultInput(
86113
intermediateResult,
87114
parseInt(intermediateFormulaId),
88115
parseInt(index),
89-
format,
90116
isActive,
91117
data,
92118
);
93-
cellElement.appendChild(intermediateResultInput.element);
94119
rows[parseInt(index) + 1].appendChild(cellElement);
95120
}
96121
}
97122
}
98123

99-
function createIntermediateFormulaInput(
100-
formula: string,
101-
intermediateFormulaId: number,
102-
format: OutputFormat,
103-
enabled: boolean,
104-
data: Record<string, unknown>,
105-
): FormulaInput {
106-
const formulaInputElement = createFormulaInput(formula, format, enabled);
107-
formulaInputElement.inputElement.setAttribute("data-intermediate-formula", `${intermediateFormulaId}`);
108-
109-
const intermediateFormulas = (data["intermediate-formulas"] ?? {}) as Record<string, string>;
110-
111-
if (enabled) {
112-
formulaInputElement.inputElement.addEventListener("change", () => {
113-
const formulaId = formulaInputElement.inputElement.getAttribute("data-intermediate-formula")!;
114-
intermediateFormulas[formulaId] = formulaInputElement.getInput();
115-
data["intermediate-formulas"] = intermediateFormulas;
116-
});
117-
}
118-
119-
return formulaInputElement;
120-
}
121-
122124
function createIntermediateResultInput(
123125
formula: string,
124126
intermediateFormulaId: number,
125127
row: number,
126-
format: OutputFormat,
127128
enabled: boolean,
128129
data: Record<string, unknown>,
129-
): FormulaInput {
130-
const resultInputElement = createFormulaInput(formula, format, enabled);
131-
resultInputElement.inputElement.maxLength = 1;
132-
resultInputElement.inputElement.pattern = "0|1";
133-
resultInputElement.inputElement.setAttribute("data-intermediate-result", `${row}`);
134-
resultInputElement.inputElement.setAttribute("data-intermediate-result-belongs-to", `${intermediateFormulaId}`);
130+
): HTMLTableCellElement {
131+
const cellElement = document.createElement("td");
132+
// Create the input element for the result.
133+
const resultInputElement = document.createElement("input");
134+
resultInputElement.value = formula;
135+
resultInputElement.maxLength = 1;
136+
resultInputElement.pattern = "0|1";
137+
resultInputElement.disabled = !enabled;
135138

136139
const intermediateResults = (data["intermediate-results"] ?? {}) as Record<string, string[]>;
137140

138141
if (enabled) {
139-
resultInputElement.inputElement.addEventListener("change", () => {
140-
const resultId = parseInt(resultInputElement.inputElement.getAttribute("data-intermediate-result")!);
141-
const belongsToFormulaId = resultInputElement.inputElement.getAttribute(
142-
"data-intermediate-result-belongs-to",
143-
)!;
144-
145-
intermediateResults[belongsToFormulaId][resultId] = resultInputElement.getInput();
142+
resultInputElement.addEventListener("change", () => {
143+
intermediateResults[intermediateFormulaId][row] = resultInputElement.value;
146144
data["intermediate-results"] = intermediateResults;
147145
});
146+
147+
resultInputElement.addEventListener("keydown", (event) => {
148+
if (event.key === "Enter") {
149+
event.preventDefault();
150+
}
151+
});
148152
}
149153

150-
return resultInputElement;
154+
cellElement.appendChild(resultInputElement);
155+
return cellElement;
151156
}

ts/src/main.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { type OutputFormat } from "./parser/parser.js";
22
import { renderBora } from "./utils";
3-
import { FormulaInput } from "./formula_input_element";
4-
import { setupButtonToAddIntermediateFormula, viewExistingIntermediateFormulas } from "./intermediate_formulas";
3+
import { initIntermediateFormulas, setupButtonToAddIntermediateFormula } from "./intermediate_formulas";
54

65
export function init(attempt: object, [format, total_rows]: [OutputFormat, number]) {
76
initFormulaElements(format);
8-
initFormulaInputElements(format, attempt);
9-
viewExistingIntermediateFormulas(format, attempt);
7+
initIntermediateFormulas(format, attempt);
108
// @ts-expect-error Attempt object definition is not here.
119
if (!attempt.readOnly) {
1210
setupButtonToAddIntermediateFormula(attempt, total_rows, format);
@@ -18,14 +16,13 @@ function initFormulaElements(format: OutputFormat) {
1816
for (const formulaElement of formulaElements) {
1917
renderBora(formulaElement.textContent!, formulaElement, format);
2018
}
21-
}
2219

23-
function initFormulaInputElements(format: OutputFormat, attempt: object) {
24-
const wrapperElements: NodeListOf<HTMLDivElement> = document.querySelectorAll(".formula-input-wrapper");
25-
for (const wrapperElement of wrapperElements) {
26-
const inputElement: HTMLInputElement = wrapperElement.querySelector(".formula-input")!;
27-
const displayElement: HTMLSpanElement = wrapperElement.querySelector(".formula-display")!;
28-
// @ts-expect-error Attempt object definition is not here.
29-
new FormulaInput(wrapperElement, inputElement, displayElement, format, !attempt.readOnly);
20+
const formulaInputElements = document.querySelectorAll(".formula-input") as NodeListOf<HTMLInputElement>;
21+
for (const formulaInputElement of formulaInputElements) {
22+
formulaInputElement.addEventListener("keydown", (event) => {
23+
if (event.key === "Enter") {
24+
event.preventDefault();
25+
}
26+
});
3027
}
3128
}

ts/src/mathjax/base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export abstract class BaseMathJaxHelper {
2424
return this.loadPromise;
2525
}
2626

27-
return this.loadPromise = new Promise((resolve, reject) => {
27+
return (this.loadPromise = new Promise((resolve, reject) => {
2828
const script = document.createElement("script");
2929
script.type = "text/javascript";
3030
script.src = this.cdnUrl;
@@ -40,7 +40,7 @@ export abstract class BaseMathJaxHelper {
4040
script.onerror = () => reject(new Error(`Failed to load ${this.cdnUrl}.`));
4141

4242
document.head.appendChild(script);
43-
});
43+
}));
4444
}
4545

4646
/**

ts/src/mathjax/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function renderLaTeX(element: Element, inline: boolean = true): Promise<v
2525
}
2626
} else {
2727
// We should in theory switch to MathJax 4 as the default, but we might need to change our UI.
28-
mathJaxHelper = new MathJax3Helper();
28+
mathJaxHelper = new MathJax4Helper();
2929
}
3030
}
3131

ts/src/mathjax/v3.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class MathJax3Helper extends BaseMathJaxHelper {
3737
protected _render(element: Element, inline: boolean): Promise<void> {
3838
// We need to manually chain every render call.
3939
// https://docs.mathjax.org/en/v3.2/web/typeset.html#handling-asynchronous-typesetting
40-
return this.queue = this.queue.then(() => {
40+
return (this.queue = this.queue.then(() => {
4141
// Get the delimiters.
4242
const inlineDelimiters = this.mathjax.config.tex?.inlineMath?.[0] ?? ["\\(", "\\)"];
4343
const displayDelimiters = this.mathjax.config.tex?.displayMath?.[0] ?? ["\\[", "\\]"];
@@ -48,6 +48,6 @@ export class MathJax3Helper extends BaseMathJaxHelper {
4848

4949
// Perform the rendering.
5050
return this.mathjax.typesetPromise([element]);
51-
});
51+
}));
5252
}
5353
}

ts/src/mathjax/v4.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ export class MathJax4Helper extends BaseMathJaxHelper {
4141
// Add the delimiters.
4242
element.textContent = `${openingDelimiter} ${element.textContent} ${closingDelimiter}`;
4343

44-
return this.mathjax.typesetPromise([element])
44+
return this.mathjax.typesetPromise([element]);
4545
}
4646
}

0 commit comments

Comments
 (0)