Skip to content

Commit 9f7a3b3

Browse files
committed
bug(nimbus): line wrap readonly codemirrors
Becuase * Long strings inside JSON values like feature values or JEXL targeting can cause the read only codemirrors to be too wide This commit * Moves all read only codemirror setup into a single file * Adds the codemirror linewrapping extension * Updates the outer element styling to account for fixed width fixes #13977
1 parent 2609881 commit 9f7a3b3

File tree

8 files changed

+95
-112
lines changed

8 files changed

+95
-112
lines changed
Lines changed: 1 addition & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,6 @@
1-
import { basicSetup } from "codemirror";
2-
import { EditorView } from "@codemirror/view";
3-
import { EditorState } from "@codemirror/state";
4-
import { json, jsonParseLinter } from "@codemirror/lang-json";
5-
import { linter } from "@codemirror/lint";
6-
import {
7-
themeCompartment,
8-
getThemeExtensions,
9-
registerView,
10-
updateAllViewThemes,
11-
observeThemeChanges,
12-
} from "./theme_utils.js";
13-
1+
import { updateAllViewThemes, observeThemeChanges } from "./theme_utils.js";
142
import $ from "jquery";
153

16-
const VISIBLE_LINE_COUNT = 5;
17-
18-
const setupCodemirroReadOnlyJSON = () => {
19-
const selector = ".readonly-json";
20-
const textareas = document.querySelectorAll(selector);
21-
22-
textareas.forEach((textarea) => {
23-
if (textarea.dataset.is_rendered) return;
24-
textarea.dataset.is_rendered = true;
25-
const extensions = [
26-
basicSetup,
27-
json(),
28-
linter(jsonParseLinter()),
29-
EditorState.readOnly.of(true),
30-
EditorView.editable.of(false),
31-
themeCompartment.of(getThemeExtensions()),
32-
];
33-
34-
const view = new EditorView({
35-
doc: textarea.value,
36-
extensions,
37-
parent: textarea.parentNode,
38-
});
39-
40-
view.dom.style.border = "1px solid #ccc";
41-
view.dom.style.maxHeight = "inherit";
42-
textarea.parentNode.insertBefore(view.dom, textarea);
43-
textarea.style.display = "none";
44-
45-
registerView(view);
46-
47-
setupCodemirrorCollapsibleDisplay(textarea);
48-
49-
return view;
50-
});
51-
};
52-
53-
const setupCodemirrorCollapsibleDisplay = (textarea) => {
54-
const lines = textarea.value.split("\n").length;
55-
56-
if (lines > VISIBLE_LINE_COUNT) {
57-
// Removes d-none from both buttons for json views that can be collapsed
58-
textarea.parentNode.nextElementSibling?.classList.remove("d-none");
59-
textarea.nextElementSibling?.classList.remove("d-none");
60-
61-
$(".show-btn").on("click", (e) => {
62-
e.target.parentNode.classList.add("d-none");
63-
e.target.parentNode.nextElementSibling.classList.remove("d-none");
64-
});
65-
66-
$(".hide-btn").on("click", (e) => {
67-
e.target.parentNode.classList.add("d-none");
68-
e.target.parentNode.previousElementSibling.classList.remove("d-none");
69-
});
70-
}
71-
};
72-
734
$(() => {
74-
setupCodemirroReadOnlyJSON();
755
observeThemeChanges(updateAllViewThemes);
76-
77-
document.body.addEventListener("htmx:afterSwap", function () {
78-
setupCodemirroReadOnlyJSON();
79-
});
806
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { basicSetup } from "codemirror";
2+
import { EditorView } from "@codemirror/view";
3+
import { EditorState } from "@codemirror/state";
4+
import { json, jsonParseLinter } from "@codemirror/lang-json";
5+
import { linter } from "@codemirror/lint";
6+
import {
7+
themeCompartment,
8+
getThemeExtensions,
9+
registerView,
10+
} from "./theme_utils.js";
11+
import $ from "jquery";
12+
13+
const VISIBLE_LINE_COUNT = 5;
14+
15+
export const createReadonlyJsonEditor = (textarea) => {
16+
if (!textarea || textarea.dataset.is_rendered) return null;
17+
18+
textarea.dataset.is_rendered = true;
19+
20+
const extensions = [
21+
basicSetup,
22+
json(),
23+
linter(jsonParseLinter()),
24+
EditorState.readOnly.of(true),
25+
EditorView.editable.of(false),
26+
EditorView.lineWrapping,
27+
themeCompartment.of(getThemeExtensions()),
28+
];
29+
30+
const view = new EditorView({
31+
doc: textarea.value,
32+
extensions,
33+
parent: textarea.parentNode,
34+
});
35+
36+
view.dom.style.border = "1px solid #ccc";
37+
view.dom.style.maxHeight = "inherit";
38+
textarea.parentNode.insertBefore(view.dom, textarea);
39+
textarea.style.display = "none";
40+
41+
registerView(view);
42+
43+
return view;
44+
};
45+
46+
export const setupCodemirrorCollapsibleDisplay = (textarea) => {
47+
const lines = textarea.value.split("\n").length;
48+
49+
if (lines > VISIBLE_LINE_COUNT) {
50+
textarea.parentNode.nextElementSibling?.classList.remove("d-none");
51+
textarea.nextElementSibling?.classList.remove("d-none");
52+
53+
$(".show-btn").on("click", (e) => {
54+
e.target.parentNode.classList.add("d-none");
55+
e.target.parentNode.nextElementSibling.classList.remove("d-none");
56+
});
57+
58+
$(".hide-btn").on("click", (e) => {
59+
e.target.parentNode.classList.add("d-none");
60+
e.target.parentNode.previousElementSibling.classList.remove("d-none");
61+
});
62+
}
63+
};
64+
65+
export const setupReadonlyJsonEditors = () => {
66+
const textareas = document.querySelectorAll(".readonly-json");
67+
textareas.forEach((textarea) => {
68+
const view = createReadonlyJsonEditor(textarea);
69+
if (view) {
70+
setupCodemirrorCollapsibleDisplay(textarea);
71+
}
72+
});
73+
};

experimenter/experimenter/nimbus_ui/static/js/edit_branches.js

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { basicSetup } from "codemirror";
22
import { EditorView } from "@codemirror/view";
3-
import { EditorState } from "@codemirror/state";
43
import { json, jsonParseLinter } from "@codemirror/lang-json";
54
import { linter } from "@codemirror/lint";
65
import { autocompletion } from "@codemirror/autocomplete";
@@ -12,6 +11,7 @@ import {
1211
updateAllViewThemes,
1312
observeThemeChanges,
1413
} from "./theme_utils.js";
14+
import { setupReadonlyJsonEditors } from "./codemirror_utils.js";
1515
import $ from "jquery";
1616

1717
const setupCodemirror = (selector, textarea, extraExtensions) => {
@@ -31,6 +31,7 @@ const setupCodemirror = (selector, textarea, extraExtensions) => {
3131
}),
3232
json(),
3333
linter(jsonParseLinter()),
34+
EditorView.lineWrapping,
3435
themeCompartment.of(getThemeExtensions()),
3536
...extraExtensions,
3637
];
@@ -105,33 +106,6 @@ const setupCodeMirrorLocalizations = () => {
105106
setupCodemirror(selector, textarea, []);
106107
};
107108

108-
const initializeSchemaCodeMirror = (textarea) => {
109-
if (!textarea || textarea.dataset.is_rendered) return;
110-
111-
textarea.dataset.is_rendered = true;
112-
113-
const extensions = [
114-
basicSetup,
115-
json(),
116-
linter(jsonParseLinter()),
117-
EditorState.readOnly.of(true),
118-
EditorView.editable.of(false),
119-
themeCompartment.of(getThemeExtensions()),
120-
];
121-
122-
const view = new EditorView({
123-
doc: textarea.value,
124-
extensions,
125-
parent: textarea.parentNode,
126-
});
127-
128-
view.dom.style.border = "1px solid #ccc";
129-
textarea.parentNode.insertBefore(view.dom, textarea);
130-
textarea.style.display = "none";
131-
132-
registerView(view);
133-
};
134-
135109
const setupSchemaToggleButtons = () => {
136110
const form = document.getElementById("branches-form");
137111
if (!form || form.dataset.schemaToggleSetup) return;
@@ -150,7 +124,7 @@ const setupSchemaToggleButtons = () => {
150124
container.querySelector(".hide-schema-btn").classList.toggle("d-none");
151125

152126
if (isHidden) {
153-
initializeSchemaCodeMirror(schemaDisplay.querySelector(".readonly-json"));
127+
setupReadonlyJsonEditors();
154128
}
155129
});
156130
};

experimenter/experimenter/nimbus_ui/static/js/features_page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const setupMergeViews = () => {
2929
json(),
3030
EditorView.editable.of(false),
3131
EditorState.readOnly.of(true),
32+
EditorView.lineWrapping,
3233
],
3334
},
3435
b: {
@@ -38,6 +39,7 @@ const setupMergeViews = () => {
3839
json(),
3940
EditorView.editable.of(false),
4041
EditorState.readOnly.of(true),
42+
EditorView.lineWrapping,
4143
],
4244
},
4345
parent: container,

experimenter/experimenter/nimbus_ui/static/js/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as $ from "jquery";
77
import * as bootstrap from "bootstrap";
88
import "htmx.org";
99
import "bootstrap-select";
10+
import { setupReadonlyJsonEditors } from "./codemirror_utils.js";
1011

1112
window.bootstrap = bootstrap;
1213
const setupThemeSwitcher = () => {
@@ -96,12 +97,14 @@ $(() => {
9697
setupToasts();
9798
setupSlugCopyToast();
9899
setupHTMXLoadingOverlay();
100+
setupReadonlyJsonEditors();
99101

100102
document.body.addEventListener("htmx:afterSwap", function () {
101103
$(".selectpicker").selectpicker();
102104
setupTooltips();
103105
setupToasts();
104106
setupSlugCopyToast();
107+
setupReadonlyJsonEditors();
105108
});
106109

107110
// To support HTMX onchange updates on selectpicker, we need to

experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/detail.html

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,12 @@ <h4>Audience</h4>
345345
<i class="fa-solid fa-plus"></i> Show more
346346
</button>
347347
</td>
348-
<td colspan="3" id="preview-recipe-json" class="expanded-json d-none">
349-
<textarea class="readonly-json">{{ experiment.recipe_json }}</textarea>
348+
<td colspan="3"
349+
id="preview-recipe-json"
350+
class="expanded-json d-none mw-0">
351+
<div class="text-break">
352+
<textarea class="readonly-json w-100">{{ experiment.recipe_json }}</textarea>
353+
</div>
350354
<button class="btn btn-outline-primary btn-sm mt-2 d-block hide-btn d-none">
351355
<i class="fa-solid fa-minus"></i> Show less
352356
</button>
@@ -386,8 +390,10 @@ <h4>Localizations</h4>
386390
</tr>
387391
<tr>
388392
<th>Localizations JSON</th>
389-
<td colspan="3">
390-
<textarea class="readonly-json">{{ experiment.localizations|default:"null" }}</textarea>
393+
<td colspan="3" class="mw-0">
394+
<div class="mw-100 overflow-auto">
395+
<textarea class="readonly-json">{{ experiment.localizations|default:"null" }}</textarea>
396+
</div>
391397
</td>
392398
</tr>
393399
</tbody>

experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/detail_branch.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,9 @@ <h6>
4747
</td>
4848
<td colspan="3"
4949
id="preview-recipe-json"
50-
class="expanded-json d-none"
51-
style="max-width: 0">
50+
class="expanded-json d-none mw-0">
5251
<div class="text-break">
53-
<textarea class="readonly-json w-100" style="word-break: break-all;">{{ feature_value.value }}</textarea>
52+
<textarea class="readonly-json w-100">{{ feature_value.value }}</textarea>
5453
</div>
5554
<button class="btn btn-outline-primary btn-sm mt-2 d-block hide-btn d-none">
5655
<i class="fa-solid fa-minus"></i> Show less

experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/edit_branches.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ <h4>Branches</h4>
169169
<i class="fa-solid fa-chevron-up"></i> Hide feature schema
170170
</button>
171171
<div id="schema-{{ forloop.parentloop.counter0 }}-{{ forloop.counter0 }}"
172-
class="schema-display d-none mt-2">
172+
class="schema-display d-none mt-2 mw-100 overflow-auto">
173173
<textarea class="readonly-json">{{ branch_feature_values_form.instance.unversioned_schema }}</textarea>
174174
</div>
175175
</div>

0 commit comments

Comments
 (0)