573 行
17 KiB
JavaScript
573 行
17 KiB
JavaScript
|
|
const { ipcMain, BrowserWindow } = require('electron');
|
|||
|
|
const path = require('path');
|
|||
|
|
const fs = require('fs');
|
|||
|
|
|
|||
|
|
let winax;
|
|||
|
|
try {
|
|||
|
|
winax = require('winax');
|
|||
|
|
console.log('✅ Winax loaded successfully');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('❌ Winax not available:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class WPSHelper {
|
|||
|
|
constructor() {
|
|||
|
|
this.wpsApp = null;
|
|||
|
|
this.activeDocument = null;
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.documents = new Map();
|
|||
|
|
this.checkInterval = null;
|
|||
|
|
this.lastParagraphContent = '';
|
|||
|
|
|
|||
|
|
this.startMonitoring();
|
|||
|
|
console.log('🔄 WPSHelper initialized');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
connectToWPS() {
|
|||
|
|
if (!winax) {
|
|||
|
|
console.warn('⚠️ Winax not available, using fallback mode');
|
|||
|
|
this.setupFallbackMode();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
this.wpsApp = new winax.Object('KWPS.Application');
|
|||
|
|
this.wpsApp.Visible = true;
|
|||
|
|
this.isConnected = true;
|
|||
|
|
|
|||
|
|
console.log('✅ Connected to WPS');
|
|||
|
|
this.setupEventListeners();
|
|||
|
|
this.startMonitoring();
|
|||
|
|
|
|||
|
|
this.sendWPSStatus();
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Failed to connect to WPS:', error);
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.setupFallbackMode();
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setupEventListeners() {
|
|||
|
|
this.lastActiveDocument = null;
|
|||
|
|
this.lastDocumentCount = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
startMonitoring() {
|
|||
|
|
if (this.checkInterval) {
|
|||
|
|
clearInterval(this.checkInterval);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.checkInterval = setInterval(() => {
|
|||
|
|
this.checkWPSStatus();
|
|||
|
|
this.updateDocumentsList();
|
|||
|
|
}, 2000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
checkWPSStatus() {
|
|||
|
|
try {
|
|||
|
|
if (this.wpsApp && this.isConnected) {
|
|||
|
|
try {
|
|||
|
|
const name = this.wpsApp.Name;
|
|||
|
|
this.isConnected = true;
|
|||
|
|
} catch (error) {
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.wpsApp = null;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
this.isConnected = false;
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.wpsApp = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateDocumentsList() {
|
|||
|
|
if (!this.isConnected || !this.wpsApp) {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
this.simulateDocumentsList();
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const newDocs = new Map();
|
|||
|
|
let documents;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
documents = this.wpsApp.Documents;
|
|||
|
|
} catch (error) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (let i = 1; i <= documents.Count; i++) {
|
|||
|
|
try {
|
|||
|
|
const doc = documents.Item(i);
|
|||
|
|
const fullName = doc.FullName;
|
|||
|
|
const name = doc.Name;
|
|||
|
|
|
|||
|
|
newDocs.set(fullName, {
|
|||
|
|
name: name,
|
|||
|
|
path: fullName,
|
|||
|
|
document: doc,
|
|||
|
|
isActive: doc === this.activeDocument
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error accessing document:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.documents = newDocs;
|
|||
|
|
this.sendDocumentsList();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error updating documents list:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getFullParagraphContent() {
|
|||
|
|
if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
|
|||
|
|
return this.getFallbackParagraphContent();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const selection = this.wpsApp.Selection;
|
|||
|
|
if (!selection) {
|
|||
|
|
return this.getFallbackParagraphContent();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const originalStart = selection.Start;
|
|||
|
|
const originalEnd = selection.End;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
selection.Expand(5); // wdParagraph
|
|||
|
|
|
|||
|
|
const text = selection.Text;
|
|||
|
|
const revisions = this.getRevisionsInSelection(selection);
|
|||
|
|
const comments = this.getCommentsInSelection(selection);
|
|||
|
|
|
|||
|
|
selection.SetRange(originalStart, originalEnd);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
text: text.trim(),
|
|||
|
|
html: text.trim(),
|
|||
|
|
revisions: revisions,
|
|||
|
|
comments: comments,
|
|||
|
|
hasRevisions: revisions.length > 0,
|
|||
|
|
hasComments: comments.length > 0
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
selection.SetRange(originalStart, originalEnd);
|
|||
|
|
return this.getFallbackParagraphContent();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
return this.getFallbackParagraphContent();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getRevisionsInSelection(selection) {
|
|||
|
|
const revisions = [];
|
|||
|
|
if (!selection || !selection.Revisions) return revisions;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const revisionsCount = selection.Revisions.Count;
|
|||
|
|
for (let i = 1; i <= revisionsCount; i++) {
|
|||
|
|
try {
|
|||
|
|
const revision = selection.Revisions.Item(i);
|
|||
|
|
revisions.push({
|
|||
|
|
type: 'insert',
|
|||
|
|
author: revision.Author || '未知作者',
|
|||
|
|
date: revision.Date || new Date(),
|
|||
|
|
text: revision.Range.Text || '',
|
|||
|
|
index: i
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error getting revision:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Could not access revisions:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return revisions;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getCommentsInSelection(selection) {
|
|||
|
|
const comments = [];
|
|||
|
|
if (!selection || !selection.Comments) return comments;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const commentsCount = selection.Comments.Count;
|
|||
|
|
for (let i = 1; i <= commentsCount; i++) {
|
|||
|
|
try {
|
|||
|
|
const comment = selection.Comments.Item(i);
|
|||
|
|
comments.push({
|
|||
|
|
author: comment.Author || '未知作者',
|
|||
|
|
date: comment.Date || new Date(),
|
|||
|
|
text: comment.Range.Text || '',
|
|||
|
|
index: i
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error getting comment:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Could not access comments:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return comments;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getFallbackParagraphContent() {
|
|||
|
|
return {
|
|||
|
|
text: this.lastParagraphContent || '',
|
|||
|
|
html: this.lastParagraphContent || '',
|
|||
|
|
revisions: [],
|
|||
|
|
comments: [],
|
|||
|
|
hasRevisions: false,
|
|||
|
|
hasComments: false
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateParagraphWithRevisions(newContent) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
|
|||
|
|
return { success: false, error: 'WPS not connected' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const selection = this.wpsApp.Selection;
|
|||
|
|
if (!selection) {
|
|||
|
|
return { success: false, error: 'No selection available' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const originalStart = selection.Start;
|
|||
|
|
const originalEnd = selection.End;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
selection.Expand(5); // wdParagraph
|
|||
|
|
const originalText = selection.Text.trim();
|
|||
|
|
|
|||
|
|
if (originalText === newContent.trim()) {
|
|||
|
|
return { success: true, unchanged: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
selection.Text = newContent;
|
|||
|
|
this.lastParagraphContent = newContent;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
originalContent: originalText,
|
|||
|
|
newContent: newContent,
|
|||
|
|
hasChanges: true
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
return { success: false, error: error.message };
|
|||
|
|
} finally {
|
|||
|
|
try {
|
|||
|
|
selection.SetRange(originalStart, originalStart);
|
|||
|
|
} catch (e) {}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
return { success: false, error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
navigateParagraph(direction) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const selection = this.wpsApp.Selection;
|
|||
|
|
if (!selection) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (direction === 'prev') {
|
|||
|
|
selection.MoveUp(5, 1); // wdParagraph
|
|||
|
|
} else if (direction === 'next') {
|
|||
|
|
selection.MoveDown(5, 1); // wdParagraph
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error navigating paragraphs:', error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
openDocument(filePath) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp) {
|
|||
|
|
console.log('WPS not connected, using fallback');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(filePath)) {
|
|||
|
|
throw new Error('File does not exist: ' + filePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const doc = this.wpsApp.Documents.Open(filePath);
|
|||
|
|
this.activeDocument = doc;
|
|||
|
|
|
|||
|
|
this.documents.set(filePath, {
|
|||
|
|
name: doc.Name,
|
|||
|
|
path: filePath,
|
|||
|
|
document: doc,
|
|||
|
|
isActive: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.sendDocumentsList();
|
|||
|
|
this.sendActiveDocumentChange();
|
|||
|
|
|
|||
|
|
console.log('Document opened successfully:', path.basename(filePath));
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error opening document:', error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switchToDocument(filePath) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const docInfo = this.documents.get(filePath);
|
|||
|
|
if (docInfo && docInfo.document) {
|
|||
|
|
try {
|
|||
|
|
docInfo.document.Activate();
|
|||
|
|
this.activeDocument = docInfo.document;
|
|||
|
|
this.sendActiveDocumentChange();
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error switching document:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
handleRevision(action, revisionIndex) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
|
|||
|
|
return { success: false, error: 'WPS not connected' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (revisionIndex !== null) {
|
|||
|
|
const revision = this.activeDocument.Revisions.Item(revisionIndex);
|
|||
|
|
if (action === 'accept') {
|
|||
|
|
revision.Accept();
|
|||
|
|
} else {
|
|||
|
|
revision.Reject();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
const revisions = this.activeDocument.Revisions;
|
|||
|
|
if (action === 'accept') {
|
|||
|
|
revisions.AcceptAll();
|
|||
|
|
} else {
|
|||
|
|
revisions.RejectAll();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { success: true };
|
|||
|
|
} catch (error) {
|
|||
|
|
return { success: false, error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addComment(commentText) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp || !this.activeDocument) {
|
|||
|
|
return { success: false, error: 'WPS not connected' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const selection = this.wpsApp.Selection;
|
|||
|
|
if (!selection) {
|
|||
|
|
return { success: false, error: 'No selection available' };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const comment = selection.Comments.Add(selection.Range);
|
|||
|
|
comment.Range.Text = commentText;
|
|||
|
|
|
|||
|
|
return { success: true, comment: commentText };
|
|||
|
|
} catch (error) {
|
|||
|
|
return { success: false, error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setTrackRevisions(track) {
|
|||
|
|
if (!this.isConnected || !this.wpsApp) return false;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
this.wpsApp.ActiveDocument.TrackRevisions = track;
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Could not set track revisions:', error.message);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setupFallbackMode() {
|
|||
|
|
console.log('Setting up fallback mode');
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.startMonitoring();
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.simulateDocumentsList();
|
|||
|
|
}, 2000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
simulateDocumentsList() {
|
|||
|
|
const mockDocuments = [
|
|||
|
|
{
|
|||
|
|
name: '示例文档.docx',
|
|||
|
|
path: 'C:\\Documents\\示例文档.docx',
|
|||
|
|
isActive: true
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
this.sendDocumentsList(mockDocuments);
|
|||
|
|
|
|||
|
|
const sampleParagraph = `这是一个示例段落内容。在回退模式下,您可以查看界面功能,但实际的WPS文档操作需要安装WPS并确保Winax正常工作。
|
|||
|
|
|
|||
|
|
当前功能包括:
|
|||
|
|
• 文档列表显示
|
|||
|
|
• 段落内容显示
|
|||
|
|
• 基本的导航操作
|
|||
|
|
• 文件拖拽处理
|
|||
|
|
|
|||
|
|
要启用完整的WPS集成功能,请确保:
|
|||
|
|
1. 已安装WPS Office
|
|||
|
|
2. Winax模块正确安装
|
|||
|
|
3. 应用程序有足够的权限访问COM组件`;
|
|||
|
|
|
|||
|
|
this.sendFullParagraphContent({
|
|||
|
|
text: sampleParagraph,
|
|||
|
|
html: sampleParagraph,
|
|||
|
|
revisions: [],
|
|||
|
|
comments: [],
|
|||
|
|
hasRevisions: false,
|
|||
|
|
hasComments: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sendWPSStatus() {
|
|||
|
|
if (ipcMain) {
|
|||
|
|
const windows = BrowserWindow.getAllWindows();
|
|||
|
|
windows.forEach(window => {
|
|||
|
|
if (window && !window.isDestroyed()) {
|
|||
|
|
try {
|
|||
|
|
window.webContents.send('wps-status-changed', {
|
|||
|
|
connected: this.isConnected,
|
|||
|
|
hasActiveDocument: !!this.activeDocument,
|
|||
|
|
documentCount: this.documents.size,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error sending WPS status:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sendDocumentsList(documents = null) {
|
|||
|
|
if (ipcMain) {
|
|||
|
|
const docs = documents || Array.from(this.documents.values()).map(doc => ({
|
|||
|
|
name: doc.name,
|
|||
|
|
path: doc.path,
|
|||
|
|
isActive: doc.document === this.activeDocument
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const windows = BrowserWindow.getAllWindows();
|
|||
|
|
windows.forEach(window => {
|
|||
|
|
if (window && !window.isDestroyed()) {
|
|||
|
|
try {
|
|||
|
|
window.webContents.send('documents-list-changed', docs);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error sending documents list:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sendActiveDocumentChange() {
|
|||
|
|
if (ipcMain && this.activeDocument) {
|
|||
|
|
const windows = BrowserWindow.getAllWindows();
|
|||
|
|
windows.forEach(window => {
|
|||
|
|
if (window && !window.isDestroyed()) {
|
|||
|
|
try {
|
|||
|
|
window.webContents.send('active-document-changed', {
|
|||
|
|
name: this.activeDocument.Name,
|
|||
|
|
path: this.activeDocument.FullName
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error sending active document change:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sendFullParagraphContent(content) {
|
|||
|
|
if (ipcMain) {
|
|||
|
|
const windows = BrowserWindow.getAllWindows();
|
|||
|
|
windows.forEach(window => {
|
|||
|
|
if (window && !window.isDestroyed()) {
|
|||
|
|
try {
|
|||
|
|
window.webContents.send('full-paragraph-content-changed', content);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error sending full paragraph content:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getStatus() {
|
|||
|
|
if (!this.isConnected) this.connectToWPS();
|
|||
|
|
return {
|
|||
|
|
connected: this.isConnected,
|
|||
|
|
activeDocument: this.activeDocument ? {
|
|||
|
|
name: this.activeDocument.Name,
|
|||
|
|
path: this.activeDocument.FullName
|
|||
|
|
} : null,
|
|||
|
|
documents: Array.from(this.documents.values()).map(doc => ({
|
|||
|
|
name: doc.name,
|
|||
|
|
path: doc.path,
|
|||
|
|
isActive: doc.document === this.activeDocument
|
|||
|
|
})),
|
|||
|
|
currentParagraph: this.lastParagraphContent,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cleanup() {
|
|||
|
|
console.log('Cleaning up WPSHelper resources...');
|
|||
|
|
|
|||
|
|
if (this.checkInterval) {
|
|||
|
|
clearInterval(this.checkInterval);
|
|||
|
|
this.checkInterval = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this.wpsApp) {
|
|||
|
|
try {
|
|||
|
|
console.log('WPS application reference cleaned');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('Error cleaning WPS application:', error);
|
|||
|
|
}
|
|||
|
|
this.wpsApp = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.activeDocument = null;
|
|||
|
|
this.isConnected = false;
|
|||
|
|
this.documents.clear();
|
|||
|
|
|
|||
|
|
console.log('WPSHelper cleanup completed');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const wpsHelper = new WPSHelper();
|
|||
|
|
module.exports = { wpsHelper };
|