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 };
|