Skip to content

Commit dab3f04

Browse files
authored
fix: structured content renders correct on hover and select (#1843)
* fix: working selection logic for inline std * fix: std block hover and border * fix: do not select all text inside block content * chore: fix types * feat: add structured content tests for inline and block selections * chore: branch cleanup * test: update mocks * feat: add applyContainerSdtDataset function to enhance table rendering * chore: resolve merge conflicts and fix lint scope for demos * fix: align SDT selection, hover, and cache behavior with SD-1584 spec * chore: fix smoke test * chore: fix vue smoke
1 parent fdf8c7c commit dab3f04

File tree

25 files changed

+945
-101
lines changed

25 files changed

+945
-101
lines changed

.github/scripts/risk-assess.mjs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,11 @@
1818
*/
1919

2020
import { execSync } from 'node:child_process';
21-
import { writeFileSync, readFileSync } from 'node:fs';
22-
import { fileURLToPath } from 'node:url';
23-
import { dirname, resolve } from 'node:path';
21+
import { writeFileSync } from 'node:fs';
2422

2523
// Allow running inside a Claude Code session
2624
delete process.env.CLAUDECODE;
2725

28-
const __dirname = dirname(fileURLToPath(import.meta.url));
2926
const REPO = process.env.REPO || 'superdoc-dev/superdoc';
3027

3128
/** Extract the first valid JSON object containing "level" from text. */
@@ -62,11 +59,6 @@ function run(cmd) {
6259
return execSync(cmd, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }).trim();
6360
}
6461

65-
function getPRInfo(pr) {
66-
const json = run(`gh pr view ${pr} --repo ${REPO} --json title,files,changedFiles`);
67-
return JSON.parse(json);
68-
}
69-
7062
function getPRDiff(pr) {
7163
return run(`gh pr diff ${pr} --repo ${REPO}`);
7264
}
@@ -353,7 +345,6 @@ async function main() {
353345
}
354346

355347
const forceDeep = flags.has('--deep');
356-
const dryRun = flags.has('--dry-run');
357348
const repoRoot = process.env.REPO_ROOT || run('git rev-parse --show-toplevel');
358349

359350
const results = [];

demos/__tests__/smoke.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ test('demo loads without errors', async ({ page }) => {
88
if (msg.type() === 'error') errors.push(msg.text());
99
});
1010

11-
// Block telemetry requests during tests
12-
await page.route('**/ingest.superdoc.dev/**', (route) => route.abort());
11+
// Disable telemetry during tests by stubbing the ingest endpoint.
12+
// Using fulfill (instead of abort) avoids browser console errors.
13+
await page.route('**/ingest.superdoc.dev/**', (route) =>
14+
route.fulfill({ status: 204, contentType: 'application/json', body: '{}' }),
15+
);
1316

1417
await page.goto('/');
1518
await expect(page.locator('body')).toBeVisible();

demos/grading-papers/app/grading/[id]/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client"
22

33
import 'superdoc/style.css'
4-
import { useState, useEffect, useRef } from "react"
4+
import { useState, useEffect, useRef, use } from "react"
55
import { useRouter } from "next/navigation"
66
import { ChevronLeft, Save, Send, Download, Printer, Menu, Bell, Search } from "lucide-react"
77
import { Button } from "@/components/ui/button"
@@ -13,7 +13,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
1313
import { Slider } from "@/components/ui/slider"
1414
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
1515
import { SuperDoc } from "superdoc"
16-
import { use } from 'react';
1716
import { docMap } from './_doc-links';
1817

1918
export default function GradingPage({ params }: { params: Promise<{ id: string }> }) {

demos/slack-redlining/cloud-function/server.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import fs from "fs";
22
import { readFile, unlink } from "fs/promises";
33
import express from "express";
44
import https from "https";
5-
import path from "path";
5+
import path, { dirname } from "path";
66
import { fileURLToPath } from "url";
7-
import { dirname } from "path";
87
import {
98
getAIResponse,
109
generateUploadDownloadUrls,

demos/vue/src/components/DocumentEditor.vue

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<template>
22
<div class="document-editor">
3-
<div :key="documentKey" class="editor-container">
3+
<div class="editor-container">
44
<div id="superdoc-toolbar" class="toolbar"></div>
55
<div id="superdoc" class="editor"></div>
66
</div>
77
</div>
88
</template>
99

1010
<script setup>
11-
import { onMounted, onUnmounted, ref, watch } from 'vue';
11+
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
1212
import { SuperDoc } from 'superdoc';
1313
import 'superdoc/style.css';
1414
@@ -27,23 +27,23 @@ const emit = defineEmits(['editor-ready', 'editor-error']);
2727
2828
// Use ref to track the editor instance
2929
const editor = ref(null);
30-
const documentKey = ref(0);
3130
3231
// Function to safely destroy editor
3332
const destroyEditor = () => {
3433
if (editor.value) {
34+
editor.value.destroy();
3535
editor.value = null;
3636
}
3737
};
3838
3939
// Function to initialize editor
4040
const initializeEditor = async () => {
4141
try {
42-
// Ensure cleanup of previous instance
42+
// Ensure cleanup of previous instance before re-initializing.
4343
destroyEditor();
44-
45-
// Increment key to force re-render
46-
documentKey.value++;
44+
45+
// Wait one tick so the container refs stay stable during remount scenarios.
46+
await nextTick();
4747
4848
// Create new editor instance
4949
editor.value = new SuperDoc({
@@ -68,14 +68,14 @@ const initializeEditor = async () => {
6868
6969
// Watch for changes in props that should trigger re-initialization
7070
watch(
71-
() => [props.documentId, props.initialData, props.readOnly],
71+
() => [props.initialData, props.readOnly],
7272
() => {
73-
initializeEditor();
73+
void initializeEditor();
7474
}
7575
);
7676
7777
onMounted(() => {
78-
initializeEditor();
78+
void initializeEditor();
7979
});
8080
8181
onUnmounted(() => {
@@ -110,4 +110,4 @@ onUnmounted(() => {
110110
margin-top: 10px;
111111
min-height: 400px; /* Ensure editor has minimum height */
112112
}
113-
</style>
113+
</style>

demos/word-addin/src/taskpane/taskpane.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
/* eslint-disable prettier/prettier */
77
/* global document, Office, Word */
8-
/* eslint-disable no-use-before-define */
8+
99

1010
import SERVER_DOMAIN from '../server-domain.js';
1111

3 MB
Binary file not shown.

e2e-tests/tests/visuals/layout-engine.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,70 @@ if (!shouldRun) {
4242
});
4343
});
4444
});
45+
46+
const loadStructuredContentDocument = async (page) => {
47+
const superEditor = await goToPageAndWaitForEditor(page, { layout: 1 });
48+
const fileInput = page.locator('input[type="file"]');
49+
50+
await fileInput.setInputFiles('./test-data/structured-content/sdt-basic.docx');
51+
52+
await page.waitForFunction(() => window.superdoc !== undefined && window.editor !== undefined, null, {
53+
polling: 100,
54+
timeout: 10_000,
55+
});
56+
57+
await page.waitForFunction(() => {
58+
const toolbar = document.querySelector('#toolbar');
59+
return toolbar && toolbar.children.length > 0;
60+
});
61+
62+
return superEditor;
63+
};
64+
65+
test('structured content: inline selection (sdt-basic.docx)', async ({ page }) => {
66+
const superEditor = await loadStructuredContentDocument(page);
67+
const inlineStructuredContent = page.locator('.superdoc-structured-content-inline').first();
68+
69+
await expect(inlineStructuredContent).toBeVisible();
70+
await inlineStructuredContent.scrollIntoViewIfNeeded();
71+
await inlineStructuredContent.hover();
72+
await inlineStructuredContent.click({ force: true });
73+
await expect(inlineStructuredContent).toHaveClass(/ProseMirror-selectednode/);
74+
await page.waitForFunction(() => {
75+
return document.querySelectorAll('.superdoc-structured-content-block.sdt-group-hover').length === 0;
76+
});
77+
const inlineEditorBox = await superEditor.boundingBox();
78+
if (inlineEditorBox) {
79+
await page.mouse.move(inlineEditorBox.x - 10, inlineEditorBox.y - 10);
80+
}
81+
82+
await expect(superEditor).toHaveScreenshot();
83+
});
84+
85+
test('structured content: block selection (sdt-basic.docx)', async ({ page }) => {
86+
const superEditor = await loadStructuredContentDocument(page);
87+
const blockStructuredContent = page.locator('.superdoc-structured-content-block').first();
88+
89+
await expect(blockStructuredContent).toBeVisible();
90+
await blockStructuredContent.scrollIntoViewIfNeeded();
91+
await blockStructuredContent.hover();
92+
await blockStructuredContent.click({ force: true });
93+
await expect(blockStructuredContent).toHaveClass(/ProseMirror-selectednode/);
94+
const blockEditorBox = await superEditor.boundingBox();
95+
if (blockEditorBox) {
96+
await page.mouse.move(blockEditorBox.x - 10, blockEditorBox.y - 10);
97+
}
98+
await page.waitForFunction(
99+
() => {
100+
const block = document.querySelector('.superdoc-structured-content-block');
101+
if (block?.matches(':hover')) return false;
102+
return document.querySelectorAll('.superdoc-structured-content-block.sdt-group-hover').length === 0;
103+
},
104+
null,
105+
{ timeout: 2_000 },
106+
);
107+
108+
await expect(superEditor).toHaveScreenshot();
109+
});
45110
});
46111
}
Loading
Loading

0 commit comments

Comments
 (0)