Skip to content

Commit bb2555d

Browse files
committed
Merge remote-tracking branch 'origin/master' into i18n
2 parents 8931a9d + 0ec2a12 commit bb2555d

File tree

4 files changed

+168
-14
lines changed

4 files changed

+168
-14
lines changed

src/packages/frontend/jupyter/cell-input.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ export const CellInput: React.FC<CellInputProps> = React.memo(
233233
value = "";
234234
}
235235
value = value.trim();
236+
if (props.actions?.processRenderedMarkdown != null) {
237+
value = props.actions.processRenderedMarkdown({ value, id: props.id });
238+
}
236239
return (
237240
<div
238241
onDoubleClick={handle_md_double_click}

src/packages/frontend/jupyter/cell-list.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
122122
read_only,
123123
} = props;
124124

125-
const cell_list_node = useRef<HTMLElement | null>(null);
125+
const cellListDivRef = useRef<any>(null);
126126
const is_mounted = useIsMountedRef();
127127
const frameActions = useNotebookFrameActions();
128128

@@ -140,7 +140,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
140140
frame_actions.focus(true);
141141
// setup a click handler so we can manage focus
142142
$(window).on("click", window_click);
143-
frame_actions.cell_list_div = $(cell_list_node.current);
143+
frame_actions.cell_list_div = $(cellListDivRef.current);
144144

145145
return () => {
146146
saveScroll();
@@ -172,7 +172,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
172172
}, [cur_id, scroll, scroll_seq]);
173173

174174
const handleCellListRef = useCallback((node: any) => {
175-
cell_list_node.current = node;
175+
cellListDivRef.current = node;
176176
frameActions.current?.set_cell_list_div(node);
177177
}, []);
178178

@@ -186,8 +186,8 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
186186
// We don't actually need to do anything though since our virtuoso
187187
// integration automatically solves this same problem.
188188
} else {
189-
if (cell_list_node.current != null) {
190-
frameActions.current?.set_scrollTop(cell_list_node.current.scrollTop);
189+
if (cellListDivRef.current != null) {
190+
frameActions.current?.set_scrollTop(cellListDivRef.current.scrollTop);
191191
}
192192
}
193193
}, [use_windowed_list]);
@@ -206,7 +206,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
206206
let scrollHeight: number = 0;
207207
for (const tm of [0, 1, 100, 250, 500, 1000]) {
208208
if (!is_mounted.current) return;
209-
const elt = cell_list_node.current;
209+
const elt = cellListDivRef.current;
210210
if (elt != null && elt.scrollHeight !== scrollHeight) {
211211
// dynamically rendering actually changed something
212212
elt.scrollTop = scrollTop;
@@ -218,7 +218,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
218218

219219
function window_click(event: any): void {
220220
// if click in the cell list, focus the cell list; otherwise, blur it.
221-
const elt = $(cell_list_node.current);
221+
const elt = $(cellListDivRef.current);
222222
// list no longer exists, nothing left to do
223223
// Maybe elt can be null? https://github.com/sagemathinc/cocalc/issues/3580
224224
if (elt.length == 0) return;
@@ -242,8 +242,8 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
242242
}
243243
}
244244

245-
async function scroll_cell_list_not_windowed(scroll: Scroll): Promise<void> {
246-
const node = $(cell_list_node.current);
245+
async function scrollCellListNotWindowed(scroll: Scroll): Promise<void> {
246+
const node = $(cellListDivRef.current);
247247
if (node.length == 0) return;
248248
if (typeof scroll === "number") {
249249
node.scrollTop(node.scrollTop() + scroll);
@@ -307,7 +307,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
307307
align = "end";
308308
isNotVisible = true;
309309
} else {
310-
const scroller = $(cell_list_node.current);
310+
const scroller = $(cellListDivRef.current);
311311
const cell = scroller.find(`#${cur_id}`);
312312
if (scroller[0] == null) return;
313313
if (cell[0] == null) return;
@@ -384,7 +384,7 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
384384
scrollCellListVirtuoso(scroll);
385385
} else {
386386
// scroll not using windowed list
387-
scroll_cell_list_not_windowed(scroll);
387+
scrollCellListNotWindowed(scroll);
388388
}
389389
}
390390

@@ -562,7 +562,6 @@ export const CellList: React.FC<CellListProps> = (props: CellListProps) => {
562562

563563
let body;
564564

565-
const cellListDivRef = useRef<HTMLDivElement>(null);
566565
const virtuosoHeightsRef = useRef<{ [index: number]: number }>({});
567566

568567
const cellListResize = useResizeObserver({ ref: cellListDivRef });

src/packages/frontend/jupyter/output-messages/mime-types/html.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ const Html = ({
1313
index?: number;
1414
trust?: boolean;
1515
}) => {
16-
if (!trust || !requiresStableUnsafeHtml(value)) {
16+
// if id and index aren't set no way to track this as stable unsafe html.
17+
// This happens, e.g., right now with renderOutput with ipywidgets, which is probably OK, since usually
18+
// with widgets the HTML doesn't need to be stable -- you are using widgets for state, not HTML.
19+
if (
20+
id == null ||
21+
index == null ||
22+
!trust ||
23+
!requiresStableUnsafeHtml(value)
24+
) {
1725
return <HTML value={value} />;
1826
}
1927
return (

src/packages/jupyter/redux/actions.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export abstract class JupyterActions extends Actions<JupyterStoreState> {
7474
public _complete_request?: number;
7575
public store: JupyterStore;
7676
public syncdb: SyncDB;
77+
private labels?: { [label: string]: { tag: string; id: string } };
7778

7879
public _init(
7980
project_id: string,
@@ -380,7 +381,7 @@ export abstract class JupyterActions extends Actions<JupyterStoreState> {
380381
// Might throw a CellWriteProtectedException
381382
public set_cell_input(id: string, input: string, save = true): void {
382383
if (!this.store) return;
383-
if (this.store.getIn(["cells", id, "input"]) == input) {
384+
if (this.store.getIn(["this.st", id, "input"]) == input) {
384385
// nothing changed. Note, I tested doing the above check using
385386
// both this.syncdb and this.store, and this.store is orders of magnitude faster.
386387
return;
@@ -2726,6 +2727,149 @@ export abstract class JupyterActions extends Actions<JupyterStoreState> {
27262727
// [ ] TODO: we also need to do this on compute servers, but
27272728
// they don't yet have the listings table.
27282729
};
2730+
2731+
processRenderedMarkdown = ({ value, id }: { value: string; id: string }) => {
2732+
const labelRegExp = /\s*\\label\{.*?\}\s*/g;
2733+
if (this.labels == null) {
2734+
const labels = (this.labels = {});
2735+
// do initial full document scan
2736+
if (this.store == null) {
2737+
return;
2738+
}
2739+
const cells = this.store.get("cells");
2740+
if (cells == null) {
2741+
return;
2742+
}
2743+
let n = 0;
2744+
for (const id of this.store.get_cell_ids_list()) {
2745+
const cell = cells.get(id);
2746+
if (cell?.get("cell_type") == "markdown") {
2747+
const value = cell.get("input") ?? "";
2748+
value.replace(labelRegExp, (labelContent) => {
2749+
const label = extractLabel(labelContent);
2750+
n += 1;
2751+
labels[label] = { tag: `${n}`, id };
2752+
});
2753+
}
2754+
}
2755+
}
2756+
const labels = this.labels;
2757+
if (labels == null) {
2758+
throw Error("bug");
2759+
}
2760+
const noLabels = value.replace(labelRegExp, (labelContent) => {
2761+
const label = extractLabel(labelContent);
2762+
if (labels[label] == null) {
2763+
labels[label] = { tag: `${misc.len(labels) + 1}`, id };
2764+
} else {
2765+
// in case it moved to a different cell due to cut/paste
2766+
labels[label].id = id;
2767+
}
2768+
return `\\tag{${labels[label].tag}}`;
2769+
});
2770+
const refRegExp = /\\ref\{.*?\}/g;
2771+
const noRefs = noLabels.replace(refRegExp, (refContent) => {
2772+
const label = extractLabel(refContent);
2773+
if (labels[label] == null) {
2774+
// nothing to do but strip it
2775+
return "";
2776+
}
2777+
const { tag, id } = labels[label];
2778+
return `[${tag}](#id=${id})`;
2779+
});
2780+
2781+
const figures = transformFigures(noRefs);
2782+
2783+
return figures;
2784+
};
2785+
}
2786+
2787+
/*
2788+
Turn this:
2789+
2790+
---
2791+
2792+
...
2793+
2794+
\begin{figure}
2795+
\centering
2796+
\centerline{\includegraphics[width=WIDTH]{URL}}
2797+
\caption{CAPTION}
2798+
\end{figure}
2799+
2800+
...
2801+
2802+
---
2803+
2804+
into this:
2805+
2806+
---
2807+
2808+
...
2809+
2810+
<div style="text-align:center"><img src="URL" style="width:WIDTH"/></div>
2811+
\begin{equation}
2812+
\text{CAPTION}
2813+
\end{equation}
2814+
2815+
...
2816+
2817+
---
2818+
2819+
There can be lots of figures.
2820+
2821+
*/
2822+
2823+
function transformFigures(content: string): string {
2824+
while (true) {
2825+
const i = content.indexOf("\\begin{figure}");
2826+
if (i == -1) {
2827+
return content;
2828+
}
2829+
const j = content.indexOf("\\end{figure}");
2830+
if (j == -1) {
2831+
return content;
2832+
}
2833+
const k = content.indexOf("\\includegraphics");
2834+
if (k == -1) {
2835+
return content;
2836+
}
2837+
const c = content.indexOf("\\caption{");
2838+
if (c == -1) {
2839+
return content;
2840+
}
2841+
const c2 = content.lastIndexOf("}", j);
2842+
if (c2 == -1) {
2843+
return content;
2844+
}
2845+
2846+
const w = content.indexOf("width=");
2847+
const w2 = content.indexOf("{", k);
2848+
const w3 = content.indexOf("}", k);
2849+
if (w2 == -1 || w3 == -1) {
2850+
return content;
2851+
}
2852+
let style = "";
2853+
if (w != -1) {
2854+
style = `width:${content.slice(w + "width=".length, w2 - 1)}`;
2855+
}
2856+
const url = content.slice(w2 + 1, w3);
2857+
const caption = content.slice(c + "\\caption{".length, c2);
2858+
2859+
const md = `\n\n<div style="text-align:center"><img src="${url}" style="${style}"/></div>\n\n
2860+
\\begin{equation}
2861+
\\text{${caption}}
2862+
\\end{equation}\n\n`;
2863+
2864+
content =
2865+
content.slice(0, i) + md + content.slice(j + "\\end{figure}".length);
2866+
}
2867+
}
2868+
2869+
function extractLabel(content: string): string {
2870+
const i = content.indexOf("{");
2871+
const j = content.lastIndexOf("}");
2872+
return content.slice(i + 1, j);
27292873
}
27302874

27312875
function bounded_integer(n: any, min: any, max: any, def: any) {

0 commit comments

Comments
 (0)