Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,116 @@ const messageHandlers: Record<string, (ctx: MessageHandlerContext) => Promise<vo
});
},

toggleChapterSync: async ({ event, document, webviewPanel, provider }) => {
const typedEvent = event as Extract<EditorPostMessages, { command: "toggleChapterSync"; }>;
const { enabled, currentMilestoneIndex, currentSubsectionIndex } = typedEvent.content;

const baseFileName = path.basename(document.uri.fsPath).replace(/\.(source|codex)$/, "");

if (enabled) {
provider.chapterSyncEnabled.add(baseFileName);

// Find the paired panel's current milestone and sync THIS panel to it
const panels = provider.getWebviewPanels();
for (const [panelUri] of panels) {
if (panelUri === document.uri.toString()) continue;
const panelBaseName = path.basename(panelUri).replace(/\.(source|codex)$/, "");
if (panelBaseName !== baseFileName) continue;

const pairedMilestone = provider.currentMilestoneSubsectionMap.get(panelUri);
if (!pairedMilestone) continue;

const { milestoneIndex: targetMilestoneIndex, subsectionIndex: targetSubsectionIndex } = pairedMilestone;

const config = vscode.workspace.getConfiguration("codex-editor-extension");
const cellsPerPage = config.get("cellsPerPage", 50);
const isThisSource = document.uri.toString().includes(".source");

const cells = document.getCellsForMilestone(targetMilestoneIndex, targetSubsectionIndex, cellsPerPage);
const allCellsInMilestone = document.getAllCellsForMilestone(targetMilestoneIndex);
const processedCells = provider.mergeRangesAndProcess(cells, provider.isCorrectionEditorMode, isThisSource);
const processedAllCellsInMilestone = provider.mergeRangesAndProcess(allCellsInMilestone, provider.isCorrectionEditorMode, isThisSource);

const sourceCellMap: { [k: string]: { content: string; versions: string[]; }; } = {};
for (const cell of cells) {
const cellId = cell.cellMarkers?.[0];
if (cellId && document._sourceCellMap[cellId]) {
sourceCellMap[cellId] = document._sourceCellMap[cellId];
}
}

provider.currentMilestoneSubsectionMap.set(document.uri.toString(), {
milestoneIndex: targetMilestoneIndex,
subsectionIndex: targetSubsectionIndex,
});

safePostMessageToPanel(webviewPanel, {
type: "providerSendsCellPage",
rev: provider.getDocumentRevision(document.uri.toString()),
milestoneIndex: targetMilestoneIndex,
subsectionIndex: targetSubsectionIndex,
cells: processedCells,
allCellsInMilestone: processedAllCellsInMilestone,
sourceCellMap,
});
break;
}
} else {
provider.chapterSyncEnabled.delete(baseFileName);
}
},

syncChapterToOther: async ({ event, document, webviewPanel, provider }) => {
const typedEvent = event as Extract<EditorPostMessages, { command: "syncChapterToOther"; }>;
const { milestoneIndex, subsectionIndex } = typedEvent.content;

const baseFileName = path.basename(document.uri.fsPath).replace(/\.(source|codex)$/, "");
if (!provider.chapterSyncEnabled.has(baseFileName)) return;

const panels = provider.getWebviewPanels();
for (const [panelUri, targetPanel] of panels) {
if (panelUri === document.uri.toString()) continue;
const panelBaseName = path.basename(panelUri).replace(/\.(source|codex)$/, "");
if (panelBaseName !== baseFileName) continue;

const targetDocument = provider.getDocumentByUri(panelUri);
if (!targetDocument) continue;

const config = vscode.workspace.getConfiguration("codex-editor-extension");
const cellsPerPage = config.get("cellsPerPage", 50);
const isTargetSource = panelUri.includes(".source");

const cells = targetDocument.getCellsForMilestone(milestoneIndex, subsectionIndex, cellsPerPage);
const allCellsInMilestone = targetDocument.getAllCellsForMilestone(milestoneIndex);
const processedCells = provider.mergeRangesAndProcess(cells, provider.isCorrectionEditorMode, isTargetSource);
const processedAllCellsInMilestone = provider.mergeRangesAndProcess(allCellsInMilestone, provider.isCorrectionEditorMode, isTargetSource);

const sourceCellMap: { [k: string]: { content: string; versions: string[]; }; } = {};
for (const cell of cells) {
const cellId = cell.cellMarkers?.[0];
if (cellId && targetDocument._sourceCellMap[cellId]) {
sourceCellMap[cellId] = targetDocument._sourceCellMap[cellId];
}
}

provider.currentMilestoneSubsectionMap.set(panelUri, {
milestoneIndex,
subsectionIndex,
});

safePostMessageToPanel(targetPanel, {
type: "providerSendsCellPage",
rev: provider.getDocumentRevision(panelUri),
milestoneIndex,
subsectionIndex,
cells: processedCells,
allCellsInMilestone: processedAllCellsInMilestone,
sourceCellMap,
});
break;
}
},

closeCurrentDocument: async ({ event }) => {
const typedEvent = event as Extract<EditorPostMessages, { command: "closeCurrentDocument"; }>;
debug("Close document request received:", typedEvent.content);
Expand Down
15 changes: 14 additions & 1 deletion src/providers/codexCellEditorProvider/codexCellEditorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ export class CodexCellEditorProvider implements vscode.CustomEditorProvider<Code
// Track current milestone/subsection per document to preserve position during updates
public currentMilestoneSubsectionMap: Map<string, { milestoneIndex: number; subsectionIndex: number; }> = new Map();

// Track which file pairs have chapter sync enabled (keyed by base filename e.g. "GEN")
public chapterSyncEnabled: Set<string> = new Set();

public static getInstance(): CodexCellEditorProvider | undefined {
return CodexCellEditorProvider.instance;
}
Expand Down Expand Up @@ -503,6 +506,10 @@ export class CodexCellEditorProvider implements vscode.CustomEditorProvider<Code
// Track the document
this.documents.set(document.uri.toString(), document);

// Auto-enable chapter sync since webview defaults to scrollSyncEnabled=true
const baseFileNameForSync = path.basename(document.uri.fsPath).replace(/\.(source|codex)$/, "");
this.chapterSyncEnabled.add(baseFileNameForSync);

// Listen for when this editor becomes active
const viewStateDisposable: vscode.Disposable = webviewPanel.onDidChangeViewState((e) => {
debug("Webview panel state changed, active:", e.webviewPanel.active);
Expand Down Expand Up @@ -2723,7 +2730,9 @@ export class CodexCellEditorProvider implements vscode.CustomEditorProvider<Code
});

// If this is the matching source file, find the target position and jump to it
if (isMatchingSource && sourceUri) {
// Only navigate source to matching chapter if chapter sync is enabled
const baseFileNameForCellSync = path.basename(codexUri.fsPath).replace(/\.(source|codex)$/, "");
if (isMatchingSource && sourceUri && this.chapterSyncEnabled.has(baseFileNameForCellSync)) {
// Get the source document to find the matching cell and fetch cells
const sourceDoc = this.documents.get(sourceUri.toString());
if (sourceDoc) {
Expand Down Expand Up @@ -4040,6 +4049,10 @@ export class CodexCellEditorProvider implements vscode.CustomEditorProvider<Code
return this.webviewPanels;
}

public getDocumentByUri(uri: string): CodexCellDocument | undefined {
return this.documents.get(uri);
}

// Add method to load bible book map
private async loadBibleBookMap(document: CodexCellDocument): Promise<void> {
debug("Loading bible book map");
Expand Down
15 changes: 15 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,21 @@ export type EditorPostMessages =
| {
command: "refreshWebviewAfterMilestoneEdits";
content?: Record<string, never>; // Empty content
}
| {
command: "toggleChapterSync";
content: {
enabled: boolean;
currentMilestoneIndex: number;
currentSubsectionIndex: number;
};
}
| {
command: "syncChapterToOther";
content: {
milestoneIndex: number;
subsectionIndex: number;
};
};

// (revalidateMissingForCell added above in EditorPostMessages union)
Expand Down
2 changes: 1 addition & 1 deletion webviews/codex-webviews/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-virtual": "^3.13.21",
"@tanstack/react-virtual": "^3.13.22",
"@types/animejs": "^3.1.13",
"@uidotdev/usehooks": "^2.4.1",
"@vscode/webview-ui-toolkit": "^1.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,20 @@ ChapterNavigationHeaderProps) {
// requestCellsForMilestone handles state updates internally
// (both for cached pages and when loading new pages)
requestCellsForMilestone(newMilestoneIdx, newSubsectionIdx);

// If scroll sync is enabled, notify the paired panel to sync
if (scrollSyncEnabled) {
vscode.postMessage({
command: "syncChapterToOther",
content: {
milestoneIndex: newMilestoneIdx,
subsectionIndex: newSubsectionIdx,
},
});
}
}
},
[unsavedChanges, currentMilestoneIndex, currentSubsectionIndex, requestCellsForMilestone, milestoneIndex?.milestones.length]
[unsavedChanges, currentMilestoneIndex, currentSubsectionIndex, requestCellsForMilestone, milestoneIndex?.milestones.length, scrollSyncEnabled, vscode]
);

// Use dynamic responsive state variables based on content overflow
Expand Down Expand Up @@ -507,6 +518,7 @@ ChapterNavigationHeaderProps) {
shouldUseMinimalLayout ? "p-1" : "p-2"
} max-w-full items-center transition-all duration-200 ease-in-out`}
ref={headerContainerRef}
onDoubleClick={(e) => { e.preventDefault(); e.stopPropagation(); }}
>
{/* Hamburger menu positioned on the left when space is insufficient */}
{shouldShowHamburger && (
Expand Down
13 changes: 12 additions & 1 deletion webviews/codex-webviews/src/CodexCellEditor/CodexCellEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3242,7 +3242,18 @@ const CodexCellEditor: React.FC = () => {
onSaveMetadata={handleSaveMetadata}
onPickFile={handlePickFile}
onUpdateVideoUrl={handleUpdateVideoUrl}
toggleScrollSync={() => setScrollSyncEnabled(!scrollSyncEnabled)}
toggleScrollSync={() => {
const newState = !scrollSyncEnabled;
setScrollSyncEnabled(newState);
vscode.postMessage({
command: "toggleChapterSync",
content: {
enabled: newState,
currentMilestoneIndex: currentMilestoneIndex,
currentSubsectionIndex: currentSubsectionIndex,
},
} as EditorPostMessages);
}}
scrollSyncEnabled={scrollSyncEnabled}
translationUnitsForSection={translationUnitsWithCurrentEditorContent}
isTranslatingCell={translationQueue.length > 0 || isProcessingCell}
Expand Down