Skip to content

Commit 693a0fb

Browse files
authored
Merge pull request #10 from frontend-engineering/fix-pasted-img
Fix backslash path issue and attachment images sync issue
2 parents 88b26ab + d2831f9 commit 693a0fb

File tree

6 files changed

+163
-62
lines changed

6 files changed

+163
-62
lines changed

src/exporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const publishFiles = async (
118118
if (exportedFile) {
119119
externalFiles.push(...exportedFile.downloads.map((d: Downloadable) => {
120120
const afterPath = exportedFile.exportToFolder.join(d.relativeDownloadPath);
121-
const fileKey = (d.relativeDownloadPath.asString + '/' + d.filename).replace(/^\.\//, '');
121+
const fileKey = Path.localToWebPath((d.relativeDownloadPath.asString + '/' + d.filename).replace(/^\.\//, ''));
122122

123123
const mdName = (file.path.endsWith('.md') && (fileKey?.replace(/\.html$/ig, '') === file.path.replace(/\.md$/igm, ''))) ? file.path : undefined
124124
Object.assign(d, {

src/main.ts

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
} from "./localdb";
3737
import { RemoteClient, ServerDomain } from "./remote";
3838
import { InvioSettingTab, DEFAULT_SETTINGS } from "./settings";
39-
import { fetchMetadataFile, parseRemoteItems, SyncStatusType, RemoteSrcPrefix } from "./sync";
39+
import { fetchMetadataFile, parseRemoteItems, SyncStatusType, RemoteSrcPrefix, RemoteAttPrefix } from "./sync";
4040
import { doActualSync, getSyncPlan, isPasswordOk } from "./sync";
4141
import { messyConfigToNormal, normalConfigToMessy } from "./configPersist";
4242
import { ObsConfigDirFileType, listFilesInObsFolder } from "./obsFolderLister";
@@ -123,6 +123,7 @@ export default class InvioPlugin extends Plugin {
123123
contents,
124124
...file.stat
125125
}
126+
// TODO: Set max memory limit
126127
log.info('file snapshot: ', this.recentSyncedFiles);
127128
}
128129

@@ -264,14 +265,12 @@ export default class InvioPlugin extends Plugin {
264265
client.serviceType,
265266
this.settings.password
266267
);
267-
console.log('parseRemoteItems result: ', remoteStates, metadataFile);
268268
const origMetadataOnRemote = await fetchMetadataFile(
269269
metadataFile,
270270
client,
271271
this.app.vault,
272272
this.settings.password
273273
);
274-
console.log('fetchMetadataFile result: ', origMetadataOnRemote);
275274

276275
getNotice(
277276
loadingModal,
@@ -473,6 +472,72 @@ export default class InvioPlugin extends Plugin {
473472
const syncedFile = this.app.vault.getAbstractFileByPath(pathName);
474473
if (syncedFile instanceof TFile) {
475474
this.addRecentSyncedFile(syncedFile);
475+
const meta = this.app.metadataCache.getFileCache(syncedFile);
476+
if (meta?.embeds) {
477+
// @ts-ignore
478+
const attachmentFolderPath = app.vault.getConfig('attachmentFolderPath');
479+
const attachmentFolderPrefix = attachmentFolderPath?.replace(/\/$/, '');
480+
const attachmentList = await this.app.vault.adapter.list(attachmentFolderPrefix + '/');
481+
482+
const localAttachmentFiles: string[] = attachmentList.files;
483+
log.info('local dir list: ', localAttachmentFiles);
484+
485+
// TODO: For all embeding formats.
486+
const embedImages = meta.embeds
487+
.filter((em: any) => em.link?.startsWith('Pasted image '))
488+
.map(em => em.link);
489+
490+
log.info('embed list: ', embedImages);
491+
492+
const getLinkWithPrefix = (link: string) => `${attachmentFolderPrefix}/${link}`.replace(/^\//, '')
493+
// TODO: Remove deleted attachment files
494+
if (decision === 'uploadLocalToRemote') {
495+
const diff = embedImages.filter(link => {
496+
const exist = localAttachmentFiles.find(f => f === getLinkWithPrefix(link));
497+
return exist;
498+
})
499+
await Promise.all(diff.map(async link => {
500+
log.info('uploading attachment: ', link);
501+
view?.info(`uploading attachment: ${link}`);
502+
503+
return client.uploadToRemote(
504+
getLinkWithPrefix(link),
505+
RemoteAttPrefix,
506+
this.app.vault,
507+
false,
508+
'',
509+
'',
510+
null,
511+
false,
512+
null,
513+
`${RemoteAttPrefix}/${link}`
514+
)
515+
}))
516+
} else {
517+
const diff: string[] = embedImages.map(link => {
518+
const exist = localAttachmentFiles.find(f => f === getLinkWithPrefix(link));
519+
return exist ? null : link;
520+
})
521+
.filter(l => !!l);
522+
await Promise.all(diff.map(async link => {
523+
view?.info(`downloading attachment: ${link}`);
524+
log.info('downloading attachment: ', link);
525+
return client.downloadFromRemote(
526+
link,
527+
RemoteAttPrefix,
528+
this.app.vault,
529+
0,
530+
'',
531+
'',
532+
false,
533+
getLinkWithPrefix(link)
534+
)
535+
.catch(err => {
536+
log.error('sync attachment failed: ', err);
537+
})
538+
}))
539+
}
540+
}
476541
}
477542
}
478543
// TODO: Get remote link, but need remote domain first
@@ -525,13 +590,16 @@ export default class InvioPlugin extends Plugin {
525590
// Force Mode - Publish all docs
526591
if (triggerSource === 'force') {
527592
pubPathList.push(...allFiles.map(file => file.path));
528-
pubPathList = pubPathList.filter((p, idx) => pubPathList.indexOf(p) === idx);
593+
pubPathList = pubPathList
594+
.filter((p, idx) => pubPathList.indexOf(p) === idx)
529595
}
530596
if (pubPathList?.length === 0) {
531597
if (unPubList?.length > 0) {
532598
// Need to update left tree links for unpublish means link deduction
533-
const indexFile = allFiles.find(file => file.name === 'index.md') || allFiles[0];
534-
pubPathList.push(indexFile.path);
599+
const indexFile = allFiles.find(file => file.name === 'index.md') || allFiles.filter(file => !file.name.endsWith('.conflict.md'))[0];
600+
if (indexFile) {
601+
pubPathList.push(indexFile.path);
602+
}
535603
}
536604
}
537605
await publishFiles(client, this.app, pubPathList, allFiles, '', this.settings, triggerSource, view, (pathName: string, status: string, meta?: any) => {

src/remote.ts

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
THostConfig,
66
} from "./baseTypes";
77
import * as s3 from "./remoteForS3";
8-
8+
import { Path, WEB_PATH_SPLITER } from './utils/path';
99
import { log } from "./moreOnLog";
1010
import { RemoteSrcPrefix } from "./sync";
1111

@@ -74,57 +74,63 @@ export class RemoteClient {
7474
}
7575

7676
getUseHostSlugPath(key: string) {
77+
let localPath = ''
7778
if (!this.useHost) {
78-
return key;
79-
}
80-
const hasPrefix = key?.startsWith(RemoteSrcPrefix);
81-
const paths = key?.split('/');
82-
if (paths?.length > 0) {
83-
let dir = hasPrefix ? paths[1] : paths[0];
84-
if (dir === 'p') {
85-
paths.splice(0, 1);
86-
dir = hasPrefix ? paths[1] : paths[0];
87-
}
88-
89-
if (dir !== this.localWatchDir) {
90-
throw new Error('NeedSwitchProject');
91-
}
92-
if (hasPrefix) {
93-
paths[1] = this.getUseHostSlug();
94-
} else {
95-
paths[0] = this.getUseHostSlug();
79+
localPath = key;
80+
} else {
81+
const hasPrefix = key?.startsWith(RemoteSrcPrefix);
82+
const paths = Path.splitString(key);
83+
if (paths?.length > 0) {
84+
let dir = hasPrefix ? paths[1] : paths[0];
85+
if (dir === 'p') {
86+
paths.splice(0, 1);
87+
dir = hasPrefix ? paths[1] : paths[0];
88+
}
89+
90+
if (dir !== this.localWatchDir) {
91+
throw new Error('NeedSwitchProject');
92+
}
93+
if (hasPrefix) {
94+
paths[1] = this.getUseHostSlug();
95+
} else {
96+
paths[0] = this.getUseHostSlug();
97+
}
98+
localPath = Path.joinString(paths);
9699
}
97-
return paths.join('/');
98100
}
99-
return '';
101+
102+
return Path.localToWebPath(localPath);
100103
}
101104

102105
getUseHostLocalPath(slug: string) {
106+
let webPath = ''
103107
if (!this.useHost) {
104-
return slug;
105-
}
106-
const hasPrefix = slug?.startsWith(RemoteSrcPrefix);
107-
const paths = slug?.split('/');
108-
let encrypted = false;
109-
if (paths?.length > 0) {
110-
let dir = hasPrefix ? paths[1] : paths[0];
111-
if (dir === 'p') {
112-
encrypted = true;
113-
dir = hasPrefix ? paths[2] : paths[1];
114-
}
115-
const getSlug = this.getUseHostSlug();
116-
if (dir !== getSlug?.replace(/^p\//, '')) {
117-
throw new Error('NeedSwitchProject');
118-
}
119-
if (hasPrefix) {
120-
paths[encrypted ? 2 : 1] = this.getUseHostDirname();
121-
} else {
122-
paths[encrypted ? 1 : 0] = this.getUseHostDirname();
108+
webPath = slug
109+
} else {
110+
const hasPrefix = slug?.startsWith(RemoteSrcPrefix);
111+
const paths = slug?.split(WEB_PATH_SPLITER);
112+
let encrypted = false;
113+
if (paths?.length > 0) {
114+
let dir = hasPrefix ? paths[1] : paths[0];
115+
if (dir === 'p') {
116+
encrypted = true;
117+
dir = hasPrefix ? paths[2] : paths[1];
118+
}
119+
const getSlug = this.getUseHostSlug();
120+
if (dir !== getSlug?.replace(/^p\//, '')) {
121+
throw new Error('NeedSwitchProject');
122+
}
123+
if (hasPrefix) {
124+
paths[encrypted ? 2 : 1] = this.getUseHostDirname();
125+
} else {
126+
paths[encrypted ? 1 : 0] = this.getUseHostDirname();
127+
}
128+
webPath = Path.joinString(paths);
129+
log.info('get local path: ', webPath)
123130
}
124-
log.info('get local path: ', paths.join('/'));
125-
return paths.join('/');
126131
}
127-
return '';
132+
133+
return Path.webToLocalPath(webPath)
128134
}
129135

130136
uploadToRemote = async (

src/remoteForS3.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
mkdirpInVault,
4141
} from "./misc";
4242
import Utils from './utils';
43+
import { Path } from './utils/path';
4344
export { S3Client } from "@aws-sdk/client-s3";
4445

4546
import { log } from "./moreOnLog";
@@ -339,11 +340,11 @@ export const uploadToRemote = async (
339340
rawContent: string | ArrayBuffer = "",
340341
remoteKey?: string
341342
) => {
342-
let uploadFile = prefix + fileOrFolderPath;
343+
let uploadFileKey = Path.localToWebPath(prefix + fileOrFolderPath);
343344
if (password !== "") {
344-
uploadFile = prefix + remoteEncryptedKey;
345+
uploadFileKey = Path.localToWebPath(prefix + remoteEncryptedKey);
345346
}
346-
const isFolder = fileOrFolderPath.endsWith("/");
347+
const isFolder = Path.isFolderOrDir(fileOrFolderPath);
347348

348349
if (isFolder && isRecursively) {
349350
throw Error("upload function doesn't implement recursive function yet!");
@@ -356,12 +357,12 @@ export const uploadToRemote = async (
356357
await s3Client.send(
357358
new PutObjectCommand({
358359
Bucket: s3Config.s3BucketName,
359-
Key: uploadFile,
360+
Key: uploadFileKey,
360361
Body: "",
361362
ContentType: contentType,
362363
})
363364
);
364-
return await getRemoteMeta(s3Client, s3Config, uploadFile);
365+
return await getRemoteMeta(s3Client, s3Config, uploadFileKey);
365366
} else {
366367
// file
367368
// we ignore isRecursively parameter here
@@ -397,7 +398,7 @@ export const uploadToRemote = async (
397398
leavePartsOnError: false,
398399
params: {
399400
Bucket: s3Config.s3BucketName,
400-
Key: remoteKey || uploadFile,
401+
Key: remoteKey || uploadFileKey,
401402
Body: body,
402403
ContentType: contentType,
403404
},
@@ -407,7 +408,7 @@ export const uploadToRemote = async (
407408
});
408409
await upload.done();
409410

410-
return await getRemoteMeta(s3Client, s3Config, remoteKey || uploadFile);
411+
return await getRemoteMeta(s3Client, s3Config, remoteKey || uploadFileKey);
411412
}
412413
};
413414

@@ -508,7 +509,7 @@ export const downloadFromRemote = async (
508509
skipSaving: boolean = false,
509510
renamedTo?: string
510511
) => {
511-
const isFolder = fileOrFolderPath.endsWith("/");
512+
const isFolder = Path.isFolderOrDir(fileOrFolderPath);
512513

513514
if (!skipSaving) {
514515
await mkdirpInVault(fileOrFolderPath, vault);
@@ -536,7 +537,7 @@ export const downloadFromRemote = async (
536537
localContent = await decryptArrayBuffer(remoteContent, password);
537538
}
538539
if (!skipSaving) {
539-
await vault.adapter.writeBinary(renamedTo || fileOrFolderPath, localContent, {
540+
await vault.adapter.writeBinary(Path.webToLocalPath(renamedTo || fileOrFolderPath), localContent, {
540541
mtime: mtime,
541542
});
542543
}

src/sync.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { Utils } from './utils/utils';
5757
import { log } from "./moreOnLog";
5858

5959
export const RemoteSrcPrefix = 'op-remote-source-raw/'
60+
export const RemoteAttPrefix = 'op-remote-attach-p/'
6061
// 影响到pub产物变动的decision
6162
const RemoteFileTouchedDecisions = ['uploadLocalDelHistToRemote', 'uploadLocalToRemote'];
6263
const LocalFileTouchedDecisions = ['downloadRemoteToLocal', 'keepRemoteDelHist'];
@@ -1292,10 +1293,13 @@ const dispatchOperationToActual = async (
12921293
if (r.remoteUnsync && r.existLocal) {
12931294
// TODO: Add a hook to alert users the risk of data lost on the local
12941295
log.info('rename last changed version from local for backup: ', r.key)
1295-
const renamed = r.key.replace(/\.md$/ig, '.conflict.md');
1296+
const conflictKey = () => '.' + Math.random().toFixed(4).slice(2) + '.conflict.md'
1297+
let renamed = r.key.replace(/\.md$/ig, conflictKey());
12961298

1299+
if (vault.adapter.exists(renamed)) {
1300+
renamed = renamed.replace('.conflict.md', conflictKey())
1301+
}
12971302
await vault.adapter.rename(r.key, renamed);
1298-
12991303
await Utils.appendFile(vault, renamed, ConflictDescriptionTxt);
13001304
}
13011305

src/utils/path.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const isWindows: boolean = (typeof process.platform === 'string' ?
3636
process.platform :
3737
parseOSFromUA(navigator.userAgent)) === "win32";
3838

39-
const PATH_SPLITER = isWindows ? '\\' : '/';
39+
export const PATH_SPLITER = isWindows ? '\\' : '/';
40+
export const WEB_PATH_SPLITER = '/';
4041

4142
export class Path
4243
{
@@ -243,6 +244,27 @@ export class Path
243244
return path.replaceAll(" ", "-").replaceAll(/-{2,}/g, "-").replace(".-", "-").toLowerCase();
244245
}
245246

247+
static localToWebPath(path: string): string {
248+
return path.replaceAll(PATH_SPLITER, '/')
249+
}
250+
251+
static webToLocalPath(path: string): string {
252+
if (isWindows) {
253+
return path.replaceAll('/', PATH_SPLITER);
254+
}
255+
return path;
256+
}
257+
static splitString(path: string): string[] {
258+
if (!path) return [];
259+
return path.split(PATH_SPLITER);
260+
}
261+
static joinString(paths: string[]): string {
262+
return paths.join(PATH_SPLITER);
263+
}
264+
static isFolderOrDir(path: string): boolean {
265+
return path.endsWith(PATH_SPLITER) || path.endsWith(WEB_PATH_SPLITER);
266+
}
267+
246268
joinString(...paths: string[]): Path
247269
{
248270
return this.copy.reparse(Path.joinStringPaths(this.asString, ...paths));

0 commit comments

Comments
 (0)