Skip to content

Commit 4e9876f

Browse files
committed
Update backend assets
1 parent 516b756 commit 4e9876f

File tree

3 files changed

+169
-34
lines changed

3 files changed

+169
-34
lines changed

src/backend/data.js

Lines changed: 150 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,44 @@
11
"use strict";
22

3-
const { dialog } = require('electron')
4-
const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.js");
53
const fs = require('fs');
64
const path = require('path');
7-
const Store = require('electron-store');
5+
const { dialog } = require('electron');
86
const { exec } = require('child_process');
9-
7+
const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.js");
8+
const { PDFDocument } = require('pdf-lib');
9+
const Store = require('electron-store');
1010
const log = require('electron-log');
11+
const fns = require('date-fns')
12+
13+
1114
log.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}] [{level}] {text}';
1215

1316
// Define the configuration data scheme. This sceheme will be store persistently.
1417
const configSchema = {
1518
config: {
1619
type: "object",
17-
required: ["ver", "directoryList", "enableRecursive", "showRelativePath", "showTitle", "trimNewline"],
20+
required: ["ver", "directoryList", "pdfAppExecutablePath", "enableRecursive", "showRelativePath", "showTitle", "trimNewline", "pageLength", "showModificationDate", "groupAnnotations", "sortGroupAsc"],
1821
properties: {
19-
ver: { type: "string", default: "0.5.2" }, // Config version -> for future update checking
22+
ver: { type: "string", default: "0.8.5" }, // Config version -> for future update checking
2023
directoryList: { type: "array", default: [] },
2124
pdfAppExecutablePath: { type: "string", default: "" },
2225
enableRecursive: { type: "boolean", default: true },
2326
showRelativePath: { type: "boolean", default: true },
2427
showTitle: { type: "boolean", default: true },
2528
trimNewline: { type: "boolean", default: true },
2629
pageLength: { type: "number", default: 10, maximum: 100, minimum: -1 },
30+
showModificationDate: { type: "boolean", default: false },
31+
groupAnnotations: { type: "boolean", default: true },
32+
sortGroupAsc: { type: "boolean", default: true }
2733
},
2834
default: {}
2935
}
3036
};
3137
const store = new Store({ schema: configSchema, clearInvalidConfig: true });
3238

39+
const standardFontDataUrl = path.join(__dirname, '../../node_modules/pdfjs-dist/standard_fonts/');
40+
41+
const errorDateValue = "N/A"; // When date parsing failed.
3342

3443
// Recursive loop to get all files and directories
3544
const getAllPdfFiles = async (parentDirectory, directoryList, enableRecursive, pdfFileList, statCounter) => {
@@ -115,7 +124,8 @@ const readPdfDocument = async (config, documentPath) => {
115124
let currentPDFannotation = [];
116125

117126
// Read PDF document
118-
let documentObject = pdfjsLib.getDocument(documentPath);
127+
// Set standardFontDataUrl to avoid warning on missing fonts
128+
let documentObject = pdfjsLib.getDocument({ url: documentPath, standardFontDataUrl: standardFontDataUrl });
119129
let pdf = await documentObject.promise;
120130

121131
// Set the PDF's title as document name if showTitle is enabled
@@ -139,15 +149,19 @@ const readPdfDocument = async (config, documentPath) => {
139149

140150
/* DataTable's array format:
141151
0 - Full PDF File Path (including PDF Filename)
142-
1 - PDF Filename / Document Title
143-
2 - Page Number
144-
3 - Annotation Type
145-
4 - Annotation Content
152+
1 - Annotation Counter - for sorting (value is assigned after the return)
153+
2 - PDF Filename / Document Title
154+
3 - Page Number
155+
4 - Annotation Type
156+
5 - Annotation Content
157+
6 - Creation Date / Modification Date
146158
*/
147159

148160
for (const item of currentPageAnnotationList) {
149-
currentPDFannotation.push([documentPath, documentName, currentPageNo, item[0], item[1]]);
161+
currentPDFannotation.push([documentPath, null, documentName, currentPageNo, item[0], item[1], parsePdfDate(item[2])]);
162+
150163
}
164+
151165
}
152166

153167
return currentPDFannotation;
@@ -169,16 +183,23 @@ const getPageAnnotation = async (pageContent, config) => {
169183
if (annotationRawList[i].subtype == "Highlight") {
170184
// Get annotate string
171185
let annotateText = annotationRawList[i].contentsObj.str;
186+
187+
// Get creationDateTime / modificationDateTime
188+
let dateTime = (config.showModificationDate === true) ? annotationRawList[i].modificationDate : annotationRawList[i].creationDate;
189+
172190
if (annotateText.length > 0) {
173191
if (config.trimNewline === true) {
174-
pageAnnotationList.push(["Comment", annotateText.replace(/(?:\r\n|\r|\n)/g, " ")]);
192+
pageAnnotationList.push([`<span class="material-icons-round table-comment">chat</span>`, annotateText.replace(/(?:\r\n|\r|\n)/g, " "), dateTime]);
175193
} else {// Convert newline char to <br> tags
176-
pageAnnotationList.push(["Comment", annotateText.replace(/(?:\r\n|\r|\n)/g, "<br>")]);
194+
pageAnnotationList.push([`<span class="material-icons-round table-comment">chat</span>`, annotateText.replace(/(?:\r\n|\r|\n)/g, "<br>"), dateTime]);
177195
}
178196

179197
} else {
198+
180199
// No annotate string, so this is highlight only without comment
181200
// Store the rectangle dimensions value
201+
// Before store, insert creationDateTime / modificationDateTime as the 5th item
202+
annotationRawList[i].rect.push(dateTime);
182203
annotationRectangleList.push(annotationRawList[i].rect);
183204
}
184205
}
@@ -214,7 +235,7 @@ const getPageAnnotation = async (pageContent, config) => {
214235
textArray.push(currentText.text);
215236
}
216237
}
217-
pageAnnotationList.push(["Highlight", textArray.join(' ')]);
238+
pageAnnotationList.push([`<span class="material-icons-round table-highlight">border_color</span>`, textArray.join(' '), rectangleDimensions[4]]); // [4] is the creationDate / modificationDate
218239
}
219240
}
220241
}
@@ -223,6 +244,27 @@ const getPageAnnotation = async (pageContent, config) => {
223244
}
224245

225246

247+
const parsePdfDate = (dateString) => {
248+
249+
// Ensure date string is non-empty
250+
if (dateString === undefined || dateString === null || typeof dateString !== "string" || dateString === "") {
251+
return errorDateValue;
252+
}
253+
254+
try {
255+
// Slice string for primary data without unrelated token
256+
// Slice based on -> D:YYYYMMDDHHmmSSOHH'mm'. Refer: https://www.verypdf.com/pdfinfoeditor/pdf-date-format.htm
257+
dateString = dateString.slice(2, 19) + dateString.slice(20, 22);
258+
let date = fns.parse(dateString, "yyyyMMddHHmmssxx", new Date()); // Refer: https://date-fns.org/v2.29.3/docs/parse
259+
260+
return date.toLocaleString().toUpperCase().replace(", ", "<br>");
261+
} catch (error) {
262+
console.error(error);
263+
return errorDateValue;
264+
}
265+
}
266+
267+
226268
module.exports = {
227269
getConfig: (event, reset) => { // Read config file
228270
// Reset config if set
@@ -270,24 +312,27 @@ module.exports = {
270312
},
271313
getAllPdfAnnotation: async (event, directoryData, config, frontend) => { // Iterate over list file to read each PDF
272314

273-
let current = 1;
274-
315+
let currentPdfCount = 1;
316+
let currentAnnotationCount = 1;
275317
let allAnotationList = []
276318
let failedFileList = []
277319
// Iterate over each PDF file
278320
for (const file of directoryData.pdfFileList) {
279321

280-
frontend.setStatus(`Reading PDF Document: ${current}/${directoryData.statCounter.file} (${(current / directoryData.statCounter.file * 100).toFixed(1)}%)`);
322+
frontend.setStatus(`Reading PDF Document: ${currentPdfCount}/${directoryData.statCounter.file} (${(currentPdfCount / directoryData.statCounter.file * 100).toFixed(1)}%)`);
281323
try {
282324
let documentAnnotationList = await readPdfDocument(config, file);
283325
for (const annotationItem of documentAnnotationList) {
326+
// Add annotation counter value
327+
annotationItem[1] = currentAnnotationCount++;
328+
284329
allAnotationList.push(annotationItem);
285330
}
286331
} catch (error) {
287332
log.error(file, error);
288333
failedFileList.push(file);
289334
}
290-
current++;
335+
currentPdfCount++;
291336
}
292337

293338
return { annotationList: allAnotationList, failedList: failedFileList };
@@ -319,6 +364,92 @@ module.exports = {
319364
log.error(error);
320365
return { success: false, reason: "Unable to open the PDF file: " + documentPath };
321366
}
367+
},
368+
getEditorInfo: async (event, documentPath) => { // Get PDF document info for editor
369+
370+
// Seperate exist check, access check and document read for clearer error info
371+
try {
372+
// Check existence
373+
if (fs.existsSync(documentPath) !== true) {
374+
return { success: false, reason: "Unable to find the PDF document file." };
375+
}
376+
377+
// Check read permission
378+
await fs.promises.access(documentPath, fs.constants.R_OK);
379+
} catch (error) {
380+
log.error(error);
381+
return { success: false, reason: "Unable to access the PDF document file." };
382+
}
383+
384+
try {
385+
// Read document
386+
const contents = await fs.promises.readFile(documentPath);
387+
const pdfDoc = await PDFDocument.load(contents);
388+
const documentTitle = pdfDoc.getTitle();
389+
390+
if (documentTitle === undefined) {
391+
return { success: true, documentTitle: "" };
392+
} else {
393+
return { success: true, documentTitle: documentTitle };
394+
}
395+
} catch (error) {
396+
log.error(error);
397+
return { success: false, reason: "Unable to read the PDF document file." };
398+
}
399+
},
400+
saveEditorInfo: async (event, documentPath, newInfo) => { // Save PDF document info from editor
401+
402+
// Seperate exist check, access check and document write for clearer error info
403+
try {
404+
// Check existence
405+
if (fs.existsSync(documentPath) !== true) {
406+
return { success: false, reason: "Failed to find the PDF document file." };
407+
}
408+
409+
// Check write permission
410+
await fs.promises.access(documentPath, fs.constants.W_OK);
411+
} catch (error) {
412+
log.error(error);
413+
return { success: false, reason: "Failed to write the PDF document file. Close any application that is open the PDF document file." };
414+
}
415+
416+
// Read document
417+
let contents = null;
418+
let pdfDoc = null;
419+
420+
try {
421+
// Read document
422+
contents = await fs.promises.readFile(documentPath);
423+
pdfDoc = await PDFDocument.load(contents);
424+
425+
// Set title
426+
pdfDoc.setTitle(newInfo.documentTitle);
427+
428+
// Set PDF producer
429+
// pdfDoc.setProducer((pdfDoc.getProducer() === undefined) ? "" : pdfDoc.getProducer());
430+
431+
} catch (error) {
432+
log.error(error);
433+
return { success: false, reason: "Failed to save the PDF document's title." };
434+
}
435+
436+
// Made backup copy first
437+
try {
438+
await fs.promises.copyFile(documentPath, documentPath + ".backup", fs.constants.COPYFILE_EXCL);
439+
} catch (error) {
440+
log.info(error);
441+
}
442+
443+
444+
try {
445+
// Save document
446+
await fs.promises.writeFile(documentPath, await pdfDoc.save());
447+
448+
return { success: true };
449+
} catch (error) {
450+
log.error(error);
451+
return { success: false, reason: "Failed to save the PDF document's title. Close any application that is open the PDF document file." };
452+
}
322453
}
323454

324455
}

src/backend/main.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
"use strict";
22

3-
const { app, BrowserWindow, ipcMain } = require('electron');
4-
const nativeImage = require('electron').nativeImage;
3+
const { app, BrowserWindow, ipcMain, nativeImage } = require('electron');
54
const path = require('path');
65
const log = require('electron-log');
76

87
const data = require('./data');
98
const frontend = { setStatus: undefined };
109

1110
const createWindow = () => {
12-
// Create the browser window.
1311
const mainWindow = new BrowserWindow({
1412
width: 1280,
1513
height: 900,
1614
minWidth: 900,
1715
minHeight: 640,
1816
webPreferences: {
19-
preload: path.join(__dirname, 'preload.js')
17+
preload: path.join(__dirname, 'preload.js'),
18+
devTools: (app.commandLine.getSwitchValue("mode") === "dev") ? true : false, // Enable chrome dev tools if arg --mode=dev
2019
},
2120
autoHideMenuBar: true,
2221
titleBarStyle: 'hidden',
2322
titleBarOverlay: {
2423
color: '#333',
25-
symbolColor: '#00bcd4',
24+
symbolColor: '#fff',
2625
},
2726

2827
icon: nativeImage.createFromPath(path.join(path.dirname(__dirname), 'frontend/assets/images/icon-1024.png')),
@@ -37,12 +36,18 @@ const createWindow = () => {
3736
// Attach setStatus for the frontend
3837
frontend.setStatus = (text) => { mainWindow.webContents.send('update:status', text); };
3938

40-
// Open the DevTools.
41-
// mainWindow.webContents.openDevTools();
39+
// Enable development evironment if --mode=dev argument exist
40+
if (app.commandLine.getSwitchValue("mode") === "dev") {
41+
// Currently, below command unable to show the devtools window properly. Need to press Ctrl+Shift+I 3 times (while focusing on the main-window)
42+
// mainWindow.webContents.openDevTools();
43+
}
44+
4245
};
4346

4447
app.whenReady().then(() => {
4548

49+
createWindow();
50+
4651
// Define ipc events
4752
ipcMain.on('log:info', (event, text) => { log.info(text); });
4853
ipcMain.on('log:error', (event, text) => { log.error(text); });
@@ -52,12 +57,12 @@ app.whenReady().then(() => {
5257
ipcMain.handle('data:checkDirectory', data.checkDirectory);
5358
ipcMain.handle('data:getAnnotation', (event, directoryData, config) => { return data.getAllPdfAnnotation(event, directoryData, config, frontend); });
5459
ipcMain.handle('action:openFile', data.openPdfFile);
55-
5660
ipcMain.on('open:appUrl', (event) => {
5761
require('electron').shell.openExternal('https://github.com/irsyadler/NoteFinder');
5862
});
59-
60-
createWindow();
63+
ipcMain.handle('editor:getInfo', data.getEditorInfo);
64+
ipcMain.handle('editor:saveInfo', data.saveEditorInfo);
65+
6166

6267
app.on('activate', () => {
6368
if (BrowserWindow.getAllWindows().length === 0) {
@@ -71,6 +76,3 @@ app.on('window-all-closed', () => {
7176
app.quit();
7277
}
7378
});
74-
75-
76-

src/backend/preload.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ contextBridge.exposeInMainWorld('backend', {
1212
getAnnotation: (directoryData, config) => ipcRenderer.invoke('data:getAnnotation', directoryData, config), // Async
1313
openFile: (config, documentPath, documentPage) => ipcRenderer.invoke('action:openFile', config, documentPath, documentPage),
1414
updateStatus: (callback) => ipcRenderer.on('update:status', callback),
15-
openAppUrl: () => ipcRenderer.send('open:appUrl')
15+
openAppUrl: () => ipcRenderer.send('open:appUrl'),
16+
getEditorInfo: (documentPath) => ipcRenderer.invoke('editor:getInfo', documentPath), // Async
17+
saveEditorInfo: (documentPath, newInfo) => ipcRenderer.invoke('editor:saveInfo', documentPath, newInfo) // Async
18+
1619
});
17-

0 commit comments

Comments
 (0)