pcloud
This commit is contained in:
parent
12e45a7f38
commit
24b839227a
@ -7,3 +7,5 @@ GOOGLEDRIVE_CLIENT_ID=xxx.apps.googleusercontent.com
|
|||||||
GOOGLEDRIVE_CLIENT_SECRET=GOCSPX-sss
|
GOOGLEDRIVE_CLIENT_SECRET=GOCSPX-sss
|
||||||
BOX_CLIENT_ID=
|
BOX_CLIENT_ID=
|
||||||
BOX_CLIENT_SECRET=
|
BOX_CLIENT_SECRET=
|
||||||
|
PCLOUD_CLIENT_ID=
|
||||||
|
PCLOUD_CLIENT_SECRET=
|
||||||
|
|||||||
2
.github/workflows/auto-build.yml
vendored
2
.github/workflows/auto-build.yml
vendored
@ -25,6 +25,8 @@ jobs:
|
|||||||
GOOGLEDRIVE_CLIENT_SECRET: ${{secrets.GOOGLEDRIVE_CLIENT_SECRET}}
|
GOOGLEDRIVE_CLIENT_SECRET: ${{secrets.GOOGLEDRIVE_CLIENT_SECRET}}
|
||||||
BOX_CLIENT_ID: ${{secrets.BOX_CLIENT_ID}}
|
BOX_CLIENT_ID: ${{secrets.BOX_CLIENT_ID}}
|
||||||
BOX_CLIENT_SECRET: ${{secrets.BOX_CLIENT_SECRET}}
|
BOX_CLIENT_SECRET: ${{secrets.BOX_CLIENT_SECRET}}
|
||||||
|
PCLOUD_CLIENT_ID: ${{secrets.PCLOUD_CLIENT_ID}}
|
||||||
|
PCLOUD_CLIENT_SECRET: ${{secrets.PCLOUD_CLIENT_SECRET}}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -29,6 +29,8 @@ jobs:
|
|||||||
GOOGLEDRIVE_CLIENT_SECRET: ${{secrets.GOOGLEDRIVE_CLIENT_SECRET}}
|
GOOGLEDRIVE_CLIENT_SECRET: ${{secrets.GOOGLEDRIVE_CLIENT_SECRET}}
|
||||||
BOX_CLIENT_ID: ${{secrets.BOX_CLIENT_ID}}
|
BOX_CLIENT_ID: ${{secrets.BOX_CLIENT_ID}}
|
||||||
BOX_CLIENT_SECRET: ${{secrets.BOX_CLIENT_SECRET}}
|
BOX_CLIENT_SECRET: ${{secrets.BOX_CLIENT_SECRET}}
|
||||||
|
PCLOUD_CLIENT_ID: ${{secrets.PCLOUD_CLIENT_ID}}
|
||||||
|
PCLOUD_CLIENT_SECRET: ${{secrets.PCLOUD_CLIENT_SECRET}}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
@ -24,6 +24,8 @@ const DEFAULT_GOOGLEDRIVE_CLIENT_SECRET =
|
|||||||
process.env.GOOGLEDRIVE_CLIENT_SECRET || "";
|
process.env.GOOGLEDRIVE_CLIENT_SECRET || "";
|
||||||
const DEFAULT_BOX_CLIENT_ID = process.env.BOX_CLIENT_ID || "";
|
const DEFAULT_BOX_CLIENT_ID = process.env.BOX_CLIENT_ID || "";
|
||||||
const DEFAULT_BOX_CLIENT_SECRET = process.env.BOX_CLIENT_SECRET || "";
|
const DEFAULT_BOX_CLIENT_SECRET = process.env.BOX_CLIENT_SECRET || "";
|
||||||
|
const DEFAULT_PCLOUD_CLIENT_ID = process.env.PCLOUD_CLIENT_ID || "";
|
||||||
|
const DEFAULT_PCLOUD_CLIENT_SECRET = process.env.PCLOUD_CLIENT_SECRET || "";
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.context({
|
.context({
|
||||||
@ -65,6 +67,8 @@ esbuild
|
|||||||
"process.env.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET": `"${DEFAULT_GOOGLEDRIVE_CLIENT_SECRET}"`,
|
"process.env.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET": `"${DEFAULT_GOOGLEDRIVE_CLIENT_SECRET}"`,
|
||||||
"process.env.DEFAULT_BOX_CLIENT_ID": `"${DEFAULT_BOX_CLIENT_ID}"`,
|
"process.env.DEFAULT_BOX_CLIENT_ID": `"${DEFAULT_BOX_CLIENT_ID}"`,
|
||||||
"process.env.DEFAULT_BOX_CLIENT_SECRET": `"${DEFAULT_BOX_CLIENT_SECRET}"`,
|
"process.env.DEFAULT_BOX_CLIENT_SECRET": `"${DEFAULT_BOX_CLIENT_SECRET}"`,
|
||||||
|
"process.env.DEFAULT_PCLOUD_CLIENT_ID": `"${DEFAULT_PCLOUD_CLIENT_ID}"`,
|
||||||
|
"process.env.DEFAULT_PCLOUD_CLIENT_SECRET": `"${DEFAULT_PCLOUD_CLIENT_SECRET}"`,
|
||||||
global: "window",
|
global: "window",
|
||||||
"process.env.NODE_DEBUG": `undefined`, // ugly fix
|
"process.env.NODE_DEBUG": `undefined`, // ugly fix
|
||||||
"process.env.DEBUG": `undefined`, // ugly fix
|
"process.env.DEBUG": `undefined`, // ugly fix
|
||||||
|
|||||||
@ -88,6 +88,7 @@
|
|||||||
"node-diff3": "^3.1.2",
|
"node-diff3": "^3.1.2",
|
||||||
"p-queue": "^8.0.1",
|
"p-queue": "^8.0.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
"pcloud-sdk-js": "^2.0.0",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"rfc4648": "^1.5.3",
|
"rfc4648": "^1.5.3",
|
||||||
|
|||||||
@ -9,7 +9,8 @@ export const PRO_WEBSITE = process.env.DEFAULT_REMOTELYSAVE_WEBSITE;
|
|||||||
export type PRO_FEATURE_TYPE =
|
export type PRO_FEATURE_TYPE =
|
||||||
| "feature-smart_conflict"
|
| "feature-smart_conflict"
|
||||||
| "feature-google_drive"
|
| "feature-google_drive"
|
||||||
| "feature-box";
|
| "feature-box"
|
||||||
|
| "feature-pcloud";
|
||||||
|
|
||||||
export interface FeatureInfo {
|
export interface FeatureInfo {
|
||||||
featureName: PRO_FEATURE_TYPE;
|
featureName: PRO_FEATURE_TYPE;
|
||||||
@ -70,3 +71,21 @@ export interface BoxConfig {
|
|||||||
credentialsShouldBeDeletedAtTimeMs?: number;
|
credentialsShouldBeDeletedAtTimeMs?: number;
|
||||||
kind: "box";
|
kind: "box";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
// pCloud
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export const COMMAND_CALLBACK_PCLOUD = "remotely-save-cb-pcloud";
|
||||||
|
export const PCLOUD_CLIENT_ID = process.env.DEFAULT_PCLOUD_CLIENT_ID;
|
||||||
|
export const PCLOUD_CLIENT_SECRET = process.env.DEFAULT_PCLOUD_CLIENT_SECRET;
|
||||||
|
|
||||||
|
export interface PCloudConfig {
|
||||||
|
accessToken: string;
|
||||||
|
hostname: "eapi.pcloud.com" | "api.pcloud.com";
|
||||||
|
locationid: 1 | 2;
|
||||||
|
remoteBaseDir?: string;
|
||||||
|
credentialsShouldBeDeletedAtTimeMs?: number;
|
||||||
|
emptyFile: "skip" | "error";
|
||||||
|
kind: "pcloud";
|
||||||
|
}
|
||||||
|
|||||||
607
pro/src/fsPCloud.ts
Normal file
607
pro/src/fsPCloud.ts
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { requestUrl } from "obsidian";
|
||||||
|
import pcloudSdk from "pcloud-sdk-js";
|
||||||
|
import {
|
||||||
|
type Entity,
|
||||||
|
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
||||||
|
} from "../../src/baseTypes";
|
||||||
|
import { FakeFs } from "../../src/fsAll";
|
||||||
|
import { getFolderLevels } from "../../src/misc";
|
||||||
|
import {
|
||||||
|
COMMAND_CALLBACK_PCLOUD,
|
||||||
|
PCLOUD_CLIENT_ID,
|
||||||
|
PCLOUD_CLIENT_SECRET,
|
||||||
|
type PCloudConfig,
|
||||||
|
} from "./baseTypesPro";
|
||||||
|
|
||||||
|
export const DEFAULT_PCLOUD_CONFIG: PCloudConfig = {
|
||||||
|
accessToken: "",
|
||||||
|
hostname: "eapi.pcloud.com",
|
||||||
|
locationid: 2,
|
||||||
|
credentialsShouldBeDeletedAtTimeMs: 0,
|
||||||
|
emptyFile: "skip",
|
||||||
|
kind: "pcloud",
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AuthAllowFirstRes {
|
||||||
|
code: string;
|
||||||
|
state?: string;
|
||||||
|
locationid: 1 | 2;
|
||||||
|
hostname: "api.pcloud.com" | "eapi.pcloud.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://docs.pcloud.com/methods/oauth_2.0/authorize.html
|
||||||
|
*/
|
||||||
|
export const generateAuthUrl = async (hasCallback: boolean) => {
|
||||||
|
const clientID = PCLOUD_CLIENT_ID;
|
||||||
|
const state = nanoid();
|
||||||
|
let authUrl = `https://my.pcloud.com/oauth2/authorize?response_type=code&client_id=${clientID}&state=${state}`;
|
||||||
|
if (hasCallback) {
|
||||||
|
authUrl += `&redirect_uri=obsidian://${COMMAND_CALLBACK_PCLOUD}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
authUrl,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AuthResSucc {
|
||||||
|
result: number;
|
||||||
|
access_token: string;
|
||||||
|
token_type: "bearer";
|
||||||
|
uid: number;
|
||||||
|
locationid: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://docs.pcloud.com/methods/oauth_2.0/oauth2_token.html
|
||||||
|
*/
|
||||||
|
export const sendAuthReq = async (
|
||||||
|
hostname: string,
|
||||||
|
authCode: string,
|
||||||
|
errorCallBack: any
|
||||||
|
) => {
|
||||||
|
const clientID = PCLOUD_CLIENT_ID ?? "";
|
||||||
|
const clientSecret = PCLOUD_CLIENT_SECRET ?? "";
|
||||||
|
try {
|
||||||
|
const k = {
|
||||||
|
code: authCode,
|
||||||
|
client_id: clientID,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
};
|
||||||
|
// console.debug(k);
|
||||||
|
const resp1 = await fetch(`https://${hostname}/oauth2_token`, {
|
||||||
|
method: "POST",
|
||||||
|
body: new URLSearchParams(k),
|
||||||
|
});
|
||||||
|
const resp2: AuthResSucc = await resp1.json();
|
||||||
|
// console.debug(resp2);
|
||||||
|
if (resp2?.result !== 0) {
|
||||||
|
throw Error(`result is not 0 (success) in the end`);
|
||||||
|
}
|
||||||
|
if (!("access_token" in resp2)) {
|
||||||
|
throw Error(`no access_token in the end`);
|
||||||
|
}
|
||||||
|
return resp2;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (errorCallBack !== undefined) {
|
||||||
|
await errorCallBack(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setConfigBySuccessfullAuthInplace = async (
|
||||||
|
config: PCloudConfig,
|
||||||
|
authAllowFirstRes: AuthAllowFirstRes,
|
||||||
|
authRes: AuthResSucc | undefined,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any> | undefined
|
||||||
|
) => {
|
||||||
|
if (authRes === undefined) {
|
||||||
|
throw Error(`you should not save the setting for undefined result`);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.accessToken = authRes.access_token;
|
||||||
|
config.hostname = authAllowFirstRes.hostname;
|
||||||
|
config.locationid = authAllowFirstRes.locationid;
|
||||||
|
|
||||||
|
// manually set it expired after 80 days;
|
||||||
|
config.credentialsShouldBeDeletedAtTimeMs =
|
||||||
|
Date.now() + OAUTH2_FORCE_EXPIRE_MILLISECONDS;
|
||||||
|
|
||||||
|
await saveUpdatedConfigFunc?.();
|
||||||
|
|
||||||
|
console.info("finish updating local info of pCloud token");
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PCloudEntity extends Entity {
|
||||||
|
id: number;
|
||||||
|
parentID: number | undefined;
|
||||||
|
parentIDPath: string | undefined;
|
||||||
|
isFolder: boolean;
|
||||||
|
hashPCloud: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface File {
|
||||||
|
contenttype: string;
|
||||||
|
created: string;
|
||||||
|
fileid: number;
|
||||||
|
hash: number;
|
||||||
|
id: string; // "f" + fileid
|
||||||
|
isfolder: false;
|
||||||
|
modified: string;
|
||||||
|
name: string;
|
||||||
|
parentfolderid: number;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Folder {
|
||||||
|
contents: (Folder | File)[] | undefined;
|
||||||
|
id: string; // "d"+folderid
|
||||||
|
folderid: number;
|
||||||
|
isfolder: true;
|
||||||
|
created: string;
|
||||||
|
modified: string;
|
||||||
|
name: string;
|
||||||
|
parentfolderid: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StatRawResponse {
|
||||||
|
result: number;
|
||||||
|
fileids: number[];
|
||||||
|
metadata: (File | Folder)[];
|
||||||
|
checksums: { sha1: string; sha256?: string; md5?: string }[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromRawResponseToEntity = (
|
||||||
|
item: Folder | File,
|
||||||
|
parentFolderPath: string | undefined /* for bfs */
|
||||||
|
): PCloudEntity => {
|
||||||
|
if (item.parentfolderid === undefined || item.parentfolderid === 0) {
|
||||||
|
throw Error(
|
||||||
|
`parentfolderid=${item.parentfolderid} should not be in fromRawResponseToEntity`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyRaw = item.name;
|
||||||
|
let size = 0;
|
||||||
|
let hashPCloud: number | undefined = undefined;
|
||||||
|
let hash: string | undefined = undefined;
|
||||||
|
let id: number | undefined = undefined;
|
||||||
|
if (
|
||||||
|
parentFolderPath !== undefined &&
|
||||||
|
parentFolderPath !== "" &&
|
||||||
|
parentFolderPath !== "/"
|
||||||
|
) {
|
||||||
|
if (!parentFolderPath.endsWith("/")) {
|
||||||
|
throw Error(
|
||||||
|
`parentFolderPath=${parentFolderPath} should not be in fromRawResponseToEntity`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
keyRaw = `${parentFolderPath}${item.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.isfolder) {
|
||||||
|
keyRaw = `${keyRaw}/`;
|
||||||
|
id = item.folderid;
|
||||||
|
} else {
|
||||||
|
size = item.size;
|
||||||
|
hashPCloud = item.hash;
|
||||||
|
hash = `${item.hash}`;
|
||||||
|
id = item.fileid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mtime = new Date(item.modified).valueOf();
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: keyRaw,
|
||||||
|
keyRaw: keyRaw,
|
||||||
|
mtimeCli: mtime,
|
||||||
|
mtimeSvr: mtime,
|
||||||
|
id: id,
|
||||||
|
parentID: item.parentfolderid,
|
||||||
|
isFolder: item.isfolder,
|
||||||
|
size: size,
|
||||||
|
sizeRaw: size,
|
||||||
|
hash: hash,
|
||||||
|
hashPCloud: hashPCloud,
|
||||||
|
parentIDPath: parentFolderPath,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromNestedFolderToEntityListAndCache = (
|
||||||
|
root: Folder
|
||||||
|
): { entities: PCloudEntity[]; key2Entity: Record<string, PCloudEntity> } => {
|
||||||
|
// console.debug("root:");
|
||||||
|
// console.debug(root);
|
||||||
|
|
||||||
|
const entities: PCloudEntity[] = [];
|
||||||
|
const key2Entity: Record<string, PCloudEntity> = {};
|
||||||
|
|
||||||
|
if (root.contents === undefined || root.contents.length === 0) {
|
||||||
|
// console.debug(`early return`);
|
||||||
|
return {
|
||||||
|
entities,
|
||||||
|
key2Entity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let parents: {
|
||||||
|
folderPath: string;
|
||||||
|
itself: Folder | File;
|
||||||
|
}[] = [];
|
||||||
|
for (const f of root.contents ?? []) {
|
||||||
|
parents.push({
|
||||||
|
folderPath: "",
|
||||||
|
itself: f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while (parents.length !== 0) {
|
||||||
|
const children: typeof parents = [];
|
||||||
|
for (const { folderPath, itself } of parents) {
|
||||||
|
if (itself.isfolder && itself.folderid === root.folderid) {
|
||||||
|
// special, ignore root folder itself
|
||||||
|
} else {
|
||||||
|
const entity = fromRawResponseToEntity(itself, folderPath);
|
||||||
|
entities.push(entity);
|
||||||
|
key2Entity[entity.keyRaw] = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
itself.isfolder &&
|
||||||
|
itself.contents !== undefined &&
|
||||||
|
itself.contents.length > 0
|
||||||
|
) {
|
||||||
|
for (const f of itself.contents) {
|
||||||
|
if (folderPath === "" || folderPath === "/") {
|
||||||
|
const child = {
|
||||||
|
itself: f,
|
||||||
|
folderPath: `${itself.name}/`,
|
||||||
|
};
|
||||||
|
children.push(child);
|
||||||
|
} else {
|
||||||
|
const child = {
|
||||||
|
itself: f,
|
||||||
|
folderPath: `${folderPath}${itself.name}/`,
|
||||||
|
};
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parents = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.debug("entities:");
|
||||||
|
// console.debug(entities);
|
||||||
|
// console.debug("key2Entity:");
|
||||||
|
// console.debug(key2Entity);
|
||||||
|
|
||||||
|
return {
|
||||||
|
entities,
|
||||||
|
key2Entity,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export class FakeFsPCloud extends FakeFs {
|
||||||
|
kind: string;
|
||||||
|
|
||||||
|
pCloudConfig: PCloudConfig;
|
||||||
|
remoteBaseDir: string;
|
||||||
|
vaultFolderExists: boolean;
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
|
|
||||||
|
keyToPCloudEntity: Record<string, PCloudEntity>;
|
||||||
|
baseDirID: number;
|
||||||
|
|
||||||
|
client: pcloudSdk.Client;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
pCloudConfig: PCloudConfig,
|
||||||
|
vaultName: string,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.kind = "pcloud";
|
||||||
|
this.pCloudConfig = pCloudConfig;
|
||||||
|
this.remoteBaseDir = this.pCloudConfig.remoteBaseDir || vaultName || "";
|
||||||
|
this.vaultFolderExists = false;
|
||||||
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
|
this.keyToPCloudEntity = {};
|
||||||
|
this.baseDirID = 0;
|
||||||
|
|
||||||
|
(global as any).locationid = pCloudConfig.locationid; // why?? pcloud, why??
|
||||||
|
this.client = pcloudSdk.createClient(pCloudConfig.accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _init() {
|
||||||
|
if (this.vaultFolderExists) {
|
||||||
|
// pass
|
||||||
|
} else {
|
||||||
|
const root = (await this.client.listfolder(0, {
|
||||||
|
recursive: false,
|
||||||
|
})) as Folder;
|
||||||
|
|
||||||
|
// find?
|
||||||
|
if (root.contents === undefined) {
|
||||||
|
throw Error(`cannot listfolder of root!`);
|
||||||
|
}
|
||||||
|
const found = root.contents.filter(
|
||||||
|
(x) => x.isfolder && x.name === this.remoteBaseDir
|
||||||
|
);
|
||||||
|
if (found.length > 0) {
|
||||||
|
// we find it!
|
||||||
|
const f = found[0] as Folder;
|
||||||
|
this.baseDirID = f.folderid;
|
||||||
|
this.vaultFolderExists = true;
|
||||||
|
} else {
|
||||||
|
// not found, let's create it!
|
||||||
|
const f: Folder = await this.client.createfolder(this.remoteBaseDir, 0);
|
||||||
|
// console.debug(f);
|
||||||
|
this.baseDirID = f.folderid;
|
||||||
|
this.vaultFolderExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getAccessToken() {
|
||||||
|
if (this.pCloudConfig.accessToken === "") {
|
||||||
|
throw Error("The user has not manually auth yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.pCloudConfig.accessToken;
|
||||||
|
|
||||||
|
// TODO: no expire date?
|
||||||
|
// https://docs.pcloud.com/methods/intro/authentication.html
|
||||||
|
}
|
||||||
|
|
||||||
|
async walk(): Promise<Entity[]> {
|
||||||
|
await this._init();
|
||||||
|
const rsp = (await this.client.listfolder(this.baseDirID, {
|
||||||
|
recursive: true,
|
||||||
|
})) as Folder;
|
||||||
|
|
||||||
|
const { entities, key2Entity } = fromNestedFolderToEntityListAndCache(rsp);
|
||||||
|
|
||||||
|
this.keyToPCloudEntity = Object.assign(this.keyToPCloudEntity, key2Entity);
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
async walkPartial(): Promise<Entity[]> {
|
||||||
|
await this._init();
|
||||||
|
const rsp = (await this.client.listfolder(this.baseDirID, {
|
||||||
|
recursive: false,
|
||||||
|
})) as Folder;
|
||||||
|
const { entities, key2Entity } = fromNestedFolderToEntityListAndCache(rsp);
|
||||||
|
|
||||||
|
this.keyToPCloudEntity = Object.assign(this.keyToPCloudEntity, key2Entity);
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stat(key: string): Promise<Entity> {
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
// TODO: we already have a cache, should we call again?
|
||||||
|
const cachedEntity = this.keyToPCloudEntity[key];
|
||||||
|
const fileID = cachedEntity?.id;
|
||||||
|
if (cachedEntity === undefined || fileID === undefined) {
|
||||||
|
throw Error(`no fileID found for key=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// why? pcloud doesn't have stat api??
|
||||||
|
return cachedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mkdir(
|
||||||
|
key: string,
|
||||||
|
mtime?: number | undefined,
|
||||||
|
ctime?: number | undefined
|
||||||
|
): Promise<Entity> {
|
||||||
|
if (!key.endsWith("/")) {
|
||||||
|
throw Error(`you should not mkdir on key=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
const cachedEntity = this.keyToPCloudEntity[key];
|
||||||
|
const fileID = cachedEntity?.id;
|
||||||
|
if (cachedEntity !== undefined && fileID !== undefined) {
|
||||||
|
return cachedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// xxx/ => ["xxx"]
|
||||||
|
// xxx/yyy/zzz/ => ["xxx", "xxx/yyy", "xxx/yyy/zzz"]
|
||||||
|
const folderLevels = getFolderLevels(key);
|
||||||
|
let parentFolderPath: string | undefined = undefined;
|
||||||
|
let parentID: number | undefined = undefined;
|
||||||
|
if (folderLevels.length === 0) {
|
||||||
|
throw Error(`cannot getFolderLevels of ${key}`);
|
||||||
|
} else if (folderLevels.length === 1) {
|
||||||
|
parentID = this.baseDirID;
|
||||||
|
parentFolderPath = ""; // ignore base dir
|
||||||
|
} else {
|
||||||
|
// length > 1
|
||||||
|
parentFolderPath = `${folderLevels[folderLevels.length - 2]}/`;
|
||||||
|
if (!(parentFolderPath in this.keyToPCloudEntity)) {
|
||||||
|
throw Error(
|
||||||
|
`parent of ${key}: ${parentFolderPath} is not created before??`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
parentID = this.keyToPCloudEntity[parentFolderPath].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// xxx/yyy/zzz/ => ["xxx", "xxx/yyy", "xxx/yyy/zzz"] => "xxx/yyy/zzz" => "zzz"
|
||||||
|
let folderItselfWithoutSlash = folderLevels[folderLevels.length - 1];
|
||||||
|
folderItselfWithoutSlash = folderItselfWithoutSlash.split("/").pop()!;
|
||||||
|
|
||||||
|
const f = await this.client.createfolder(
|
||||||
|
folderItselfWithoutSlash,
|
||||||
|
parentID
|
||||||
|
);
|
||||||
|
|
||||||
|
const entity = fromRawResponseToEntity(f, parentFolderPath);
|
||||||
|
// insert into cache
|
||||||
|
this.keyToPCloudEntity[key] = entity;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://docs.pcloud.com/methods/file/uploadfile.html
|
||||||
|
*/
|
||||||
|
async writeFile(
|
||||||
|
key: string,
|
||||||
|
content: ArrayBuffer,
|
||||||
|
mtime: number,
|
||||||
|
ctime: number
|
||||||
|
): Promise<Entity> {
|
||||||
|
if (key.endsWith("/")) {
|
||||||
|
throw Error(`should not call writeFile on ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
if (content.byteLength === 0) {
|
||||||
|
if (this.pCloudConfig.emptyFile === "error") {
|
||||||
|
throw Error(
|
||||||
|
`${key}: Empty file is not allowed in pCloud, and please write something in it.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
keyRaw: key,
|
||||||
|
mtimeSvr: mtime,
|
||||||
|
mtimeCli: mtime,
|
||||||
|
size: 0,
|
||||||
|
sizeRaw: 0,
|
||||||
|
synthesizedFile: true,
|
||||||
|
// hash: ?? // TODO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevCachedEntity: PCloudEntity | undefined =
|
||||||
|
this.keyToPCloudEntity[key];
|
||||||
|
const prevFileID: number | undefined = prevCachedEntity?.id;
|
||||||
|
|
||||||
|
let parentID: number | undefined = undefined;
|
||||||
|
let parentFolderPath: string | undefined = undefined;
|
||||||
|
|
||||||
|
// "xxx" => []
|
||||||
|
// "xxx/yyy/zzz.md" => ["xxx", "xxx/yyy"]
|
||||||
|
const folderLevels = getFolderLevels(key);
|
||||||
|
if (folderLevels.length === 0) {
|
||||||
|
// root
|
||||||
|
parentID = this.baseDirID;
|
||||||
|
parentFolderPath = "";
|
||||||
|
} else {
|
||||||
|
parentFolderPath = `${folderLevels[folderLevels.length - 1]}/`;
|
||||||
|
if (!(parentFolderPath in this.keyToPCloudEntity)) {
|
||||||
|
throw Error(
|
||||||
|
`parent of ${key}: ${parentFolderPath} is not created before??`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
parentID = this.keyToPCloudEntity[parentFolderPath].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileItself = key.split("/").pop()!;
|
||||||
|
|
||||||
|
// no idea how to use the sdk, let's use https here
|
||||||
|
// https://docs.pcloud.com/methods/file/uploadfile.html
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
access_token: await this._getAccessToken(),
|
||||||
|
folderid: `${parentID}`,
|
||||||
|
filename: fileItself,
|
||||||
|
nopartial: `1`,
|
||||||
|
renameifexists: `0`,
|
||||||
|
mtime: `${Math.floor(mtime / 1000.0)}`,
|
||||||
|
ctime: `${Math.floor(ctime / 1000.0)}`,
|
||||||
|
});
|
||||||
|
const apiUrl = `https://${this.pCloudConfig.hostname}/uploadfile?${params}`;
|
||||||
|
|
||||||
|
const rsp = await fetch(apiUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
body: content,
|
||||||
|
});
|
||||||
|
const f: StatRawResponse = await rsp.json();
|
||||||
|
const entity = fromRawResponseToEntity(f.metadata[0], parentFolderPath);
|
||||||
|
// console.debug(entity);
|
||||||
|
this.keyToPCloudEntity[key] = entity;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(key: string): Promise<ArrayBuffer> {
|
||||||
|
await this._init();
|
||||||
|
const cachedEntity = this.keyToPCloudEntity[key];
|
||||||
|
const fileID = cachedEntity?.id;
|
||||||
|
if (cachedEntity === undefined || fileID === undefined) {
|
||||||
|
throw Error(`no fileID found for key=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
access_token: await this._getAccessToken(),
|
||||||
|
forcedownload: `1`,
|
||||||
|
fileid: `${fileID}`,
|
||||||
|
});
|
||||||
|
const urlMeta = `https://${this.pCloudConfig.hostname}/getfilelink?${params}`;
|
||||||
|
|
||||||
|
// Referrer is restricted to pcloud.com.
|
||||||
|
// we need to bypass it
|
||||||
|
const meta = (await requestUrl(urlMeta)).json;
|
||||||
|
// console.debug(meta);
|
||||||
|
const link: string = `https://${meta.hosts[0]}${meta.path}`;
|
||||||
|
const rsp = await requestUrl(link);
|
||||||
|
const content = rsp.arrayBuffer;
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rename(key1: string, key2: string): Promise<void> {
|
||||||
|
await this._init();
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async rm(key: string): Promise<void> {
|
||||||
|
await this._init();
|
||||||
|
|
||||||
|
const cachedEntity = this.keyToPCloudEntity[key];
|
||||||
|
const fileID = cachedEntity?.id;
|
||||||
|
if (cachedEntity === undefined || fileID === undefined) {
|
||||||
|
throw Error(`no fileID found for key=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.endsWith("/")) {
|
||||||
|
await this.client.deletefolder(fileID);
|
||||||
|
} else {
|
||||||
|
await this.client.deletefile(fileID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkConnect(callbackFunc?: any): Promise<boolean> {
|
||||||
|
// if we can init, we can connect
|
||||||
|
try {
|
||||||
|
await this._init();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.debug(err);
|
||||||
|
callbackFunc?.(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserDisplayName(): Promise<string> {
|
||||||
|
await this._init();
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeAuth(): Promise<any> {
|
||||||
|
await this._init();
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
allowEmptyFile(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,11 @@
|
|||||||
"protocol_box_connect_fail": "Something went wrong from response from Box official website. Maybe the network connection is not good. Maybe you rejected the auth?",
|
"protocol_box_connect_fail": "Something went wrong from response from Box official website. Maybe the network connection is not good. Maybe you rejected the auth?",
|
||||||
"protocol_box_connect_succ_revoke": "You've connected. If you want to disconnect, click this button.",
|
"protocol_box_connect_succ_revoke": "You've connected. If you want to disconnect, click this button.",
|
||||||
|
|
||||||
|
"protocol_pcloud_connecting": "Connectting",
|
||||||
|
"protocol_pcloud_connect_manualinput_succ": "You've connected",
|
||||||
|
"protocol_pcloud_connect_fail": "Something went wrong from response from pCloud website. Maybe the network connection is not good. Maybe you rejected the auth?",
|
||||||
|
"protocol_pcloud_connect_succ_revoke": "You've connected. If you want to disconnect, click this button.",
|
||||||
|
|
||||||
"modal_googledriveauth_tutorial": "<p>Please firstly go to the address, then go on the auth flow. In the end, you will see a code, please paste that code here and submit.</p>",
|
"modal_googledriveauth_tutorial": "<p>Please firstly go to the address, then go on the auth flow. In the end, you will see a code, please paste that code here and submit.</p>",
|
||||||
"modal_googledriveauth_copybutton": "Click to copy the auth url",
|
"modal_googledriveauth_copybutton": "Click to copy the auth url",
|
||||||
"modal_googledriveauth_copynotice": "The auth url is copied to the clipboard!",
|
"modal_googledriveauth_copynotice": "The auth url is copied to the clipboard!",
|
||||||
@ -44,6 +49,17 @@
|
|||||||
"modal_boxrevokeauth_clean_notice": "Cleaned!",
|
"modal_boxrevokeauth_clean_notice": "Cleaned!",
|
||||||
"modal_boxrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
"modal_boxrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
||||||
|
|
||||||
|
"modal_pcloudauth_tutorial": "<p>Please firstly go to the address, then go on the auth flow. In the end, you will be redirected to here.</p>",
|
||||||
|
"modal_pcloudauth_copybutton": "Click to copy the auth url",
|
||||||
|
"modal_pcloudauth_copynotice": "The auth url is copied to the clipboard!",
|
||||||
|
"modal_pcloudrevokeauth_step1": "Step 1: Go to the following address, you can remove the connection there.",
|
||||||
|
"modal_pcloudrevokeauth_step2": "Step 2: Click the button below, to clean the locally-saved login credentials.",
|
||||||
|
"modal_pcloudrevokeauth_clean": "Clean Locally-Saved Login Credentials",
|
||||||
|
"modal_pcloudrevokeauth_clean_desc": "You need to click the button.",
|
||||||
|
"modal_pcloudrevokeauth_clean_button": "Clean",
|
||||||
|
"modal_pcloudrevokeauth_clean_notice": "Cleaned!",
|
||||||
|
"modal_pcloudrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
||||||
|
|
||||||
"modal_prorevokeauth": "Revoke auth by clicking here and follow the steps.",
|
"modal_prorevokeauth": "Revoke auth by clicking here and follow the steps.",
|
||||||
"modal_prorevokeauth_clean": "Clean",
|
"modal_prorevokeauth_clean": "Clean",
|
||||||
"modal_prorevokeauth_clean_desc": "Clean local auth record",
|
"modal_prorevokeauth_clean_desc": "Clean local auth record",
|
||||||
@ -93,8 +109,31 @@
|
|||||||
"settings_box_connect_succ": "Great! We can connect to Box!",
|
"settings_box_connect_succ": "Great! We can connect to Box!",
|
||||||
"settings_box_connect_fail": "We cannot connect to Box.",
|
"settings_box_connect_fail": "We cannot connect to Box.",
|
||||||
|
|
||||||
|
"settings_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_chooseservice_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_pcloud_disclaimer1": "Disclaimer: This app is NOT an official pCloud product. The app just uses pCloud's public api.",
|
||||||
|
"settings_pcloud_disclaimer2": "Disclaimer: The information is stored locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your pCloud, please immediately disconnect this app on https://my.pcloud.com/#page=settings&settings=tab-apps .",
|
||||||
|
"settings_pcloud_pro_desc": "<p><strong>!!It's a PRO feature of Remotely Save! You need a Remotely Save online account for this feature!!</strong>(<a href=\"#settings-pro\">scroll down</a> for more info about PRO account.)</p>",
|
||||||
|
"settings_pcloud_notshowuphint": "pCloud Settings Not Available",
|
||||||
|
"settings_pcloud_notshowuphint_desc": "pCloud settings are not available, because you haven't subscribed to the PRO feature in your Remotely Save account.",
|
||||||
|
"settings_pcloud_notshowuphint_view_pro": "View PRO Settings",
|
||||||
|
"settings_pcloud_folder": "We will create and sync inside the folder {{remoteBaseDir}} on your pCloud. DO NOT create this folder by yourself manually.",
|
||||||
|
"settings_pcloud_revoke": "Revoke Auth",
|
||||||
|
"settings_pcloud_revoke_desc": "You've connected. If you want to disconnect, click this button.",
|
||||||
|
"settings_pcloud_revoke_button": "Revoke Auth",
|
||||||
|
"settings_pcloud_auth": "Auth",
|
||||||
|
"settings_pcloud_auth_desc": "Auth.",
|
||||||
|
"settings_pcloud_auth_button": "Auth",
|
||||||
|
"settings_pcloud_emptyfile": "Empty File",
|
||||||
|
"settings_pcloud_emptyfile_desc": "pCloud API doesn't work with empty file. How to deal with it?",
|
||||||
|
"settings_pcloud_emptyfile_skip": "Skip",
|
||||||
|
"settings_pcloud_emptyfile_error": "Error",
|
||||||
|
"settings_pcloud_connect_succ": "Great! We can connect to pCloud!",
|
||||||
|
"settings_pcloud_connect_fail": "We cannot connect to pCloud.",
|
||||||
|
|
||||||
"settings_export_googledrive_button": "Export Google Drive Part",
|
"settings_export_googledrive_button": "Export Google Drive Part",
|
||||||
"settings_export_box_button": "Export Box Part",
|
"settings_export_box_button": "Export Box Part",
|
||||||
|
"settings_export_pcloud_button": "Export pCloud Part",
|
||||||
|
|
||||||
"settings_pro": "Account (for PRO features)",
|
"settings_pro": "Account (for PRO features)",
|
||||||
"settings_pro_tutorial": "<p>Using <stong>basic</strong> features of Remotely Save is <strong>FREE</strong> and do <strong>NOT</strong> need an account.</p><p>However, you will <strong>need</strong> an online account and <strong>PAY</strong> for the <strong>PRO</strong> features such as smart conflict.</p><p>Firstly please click the button to sign up and sign in to the website: <a href=\"https://remotelysave.com\">https://remotelysave.com</a>. Notice: It's different from, and NOT affiliated with Obsidian account.</p><p>Secondly please \"connect\" your local device to your online account.",
|
"settings_pro_tutorial": "<p>Using <stong>basic</strong> features of Remotely Save is <strong>FREE</strong> and do <strong>NOT</strong> need an account.</p><p>However, you will <strong>need</strong> an online account and <strong>PAY</strong> for the <strong>PRO</strong> features such as smart conflict.</p><p>Firstly please click the button to sign up and sign in to the website: <a href=\"https://remotelysave.com\">https://remotelysave.com</a>. Notice: It's different from, and NOT affiliated with Obsidian account.</p><p>Secondly please \"connect\" your local device to your online account.",
|
||||||
|
|||||||
@ -12,6 +12,11 @@
|
|||||||
"protocol_box_connect_fail": "Box 官网返回错误。可能是网络连接不稳定。也可能是您拒绝了授权?",
|
"protocol_box_connect_fail": "Box 官网返回错误。可能是网络连接不稳定。也可能是您拒绝了授权?",
|
||||||
"protocol_box_connect_succ_revoke": "您已连接上账号。如果要取消连接,请点击此按钮。",
|
"protocol_box_connect_succ_revoke": "您已连接上账号。如果要取消连接,请点击此按钮。",
|
||||||
|
|
||||||
|
"protocol_pcloud_connecting": "正在连接",
|
||||||
|
"protocol_pcloud_connect_manualinput_succ": "连接成功",
|
||||||
|
"protocol_pcloud_connect_fail": "pCloud 官网返回错误。可能是网络连接不稳定。也可能是您拒绝了授权?",
|
||||||
|
"protocol_pcloud_connect_succ_revoke": "您已连接上账号。如果要取消连接,请点击此按钮。",
|
||||||
|
|
||||||
"modal_googledriveauth_tutorial": "<p>请访问此网址,然后会进入授权流程。最后,您会看到一个码,请复制粘贴到这里然后提交。</p>",
|
"modal_googledriveauth_tutorial": "<p>请访问此网址,然后会进入授权流程。最后,您会看到一个码,请复制粘贴到这里然后提交。</p>",
|
||||||
"modal_googledriveauth_copybutton": "点击以复制网址",
|
"modal_googledriveauth_copybutton": "点击以复制网址",
|
||||||
"modal_googledriveauth_copynotice": "网址已复制!",
|
"modal_googledriveauth_copynotice": "网址已复制!",
|
||||||
@ -44,6 +49,17 @@
|
|||||||
"modal_boxrevokeauth_clean_notice": "已清理!",
|
"modal_boxrevokeauth_clean_notice": "已清理!",
|
||||||
"modal_boxrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
"modal_boxrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
||||||
|
|
||||||
|
"modal_pcloudauth_tutorial": "<p>请访问此网址,然后会进入授权流程。最后,您会被重定向回来。</p>",
|
||||||
|
"modal_pcloudauth_copybutton": "点击以复制网址",
|
||||||
|
"modal_pcloudauth_copynotice": "网址已复制!",
|
||||||
|
"modal_pcloudrevokeauth_step1": "第 1 步:访问以下网址,可以删除连接。",
|
||||||
|
"modal_pcloudrevokeauth_step2": "第 2 步:点击以下按钮,从而清理本地的登录信息。",
|
||||||
|
"modal_pcloudrevokeauth_clean": "清理本地登录信息",
|
||||||
|
"modal_pcloudrevokeauth_clean_desc": "您需要点击此按钮。",
|
||||||
|
"modal_pcloudrevokeauth_clean_button": "清理",
|
||||||
|
"modal_pcloudrevokeauth_clean_notice": "已清理!",
|
||||||
|
"modal_pcloudrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
||||||
|
|
||||||
"modal_prorevokeauth": "点击这里和按照步骤取消授权。",
|
"modal_prorevokeauth": "点击这里和按照步骤取消授权。",
|
||||||
"modal_prorevokeauth_clean": "清理",
|
"modal_prorevokeauth_clean": "清理",
|
||||||
"modal_prorevokeauth_clean_desc": "清理本地授权记录",
|
"modal_prorevokeauth_clean_desc": "清理本地授权记录",
|
||||||
@ -93,8 +109,31 @@
|
|||||||
"settings_box_connect_succ": "很好!我们可连接上 Box!",
|
"settings_box_connect_succ": "很好!我们可连接上 Box!",
|
||||||
"settings_box_connect_fail": "我们未能连接上 Box。",
|
"settings_box_connect_fail": "我们未能连接上 Box。",
|
||||||
|
|
||||||
|
"settings_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_chooseservice_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_pcloud_disclaimer1": "声明:本插件不是 pCloud 的官方产品。只是用到了它的公开 API。",
|
||||||
|
"settings_pcloud_disclaimer2": "声明:您所输入的信息存储于本地。其它有害的或者出错的插件,是有可能读取到这些信息的。如果您发现任何不符合预期的 pCloud 访问,请立刻在以下网站操作断开连接: https://my.pcloud.com/#page=settings&settings=tab-apps 。",
|
||||||
|
"settings_pcloud_pro_desc": "<p><strong>!!这是 PRO(付费)功能! 您需要在线账号来使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 账号的更多信息。)</p>",
|
||||||
|
"settings_pcloud_notshowuphint": "pCloud 设置不可用",
|
||||||
|
"settings_pcloud_notshowuphint_desc": "pCloud 设置不可用,因为您没有在 Remotely Save 账号里开启这个 PRO 功能。",
|
||||||
|
"settings_pcloud_notshowuphint_view_pro": "查看 PRO 相关设置",
|
||||||
|
"settings_pcloud_folder": "我们会在 pCloud 创建此文件夹并同步内容进去: {{remoteBaseDir}} 。请不要手动在网站上创建。",
|
||||||
|
"settings_pcloud_revoke": "撤回鉴权",
|
||||||
|
"settings_pcloud_revoke_desc": "您现在已连接。如果想取消连接,请点击此按钮。",
|
||||||
|
"settings_pcloud_revoke_button": "撤回鉴权",
|
||||||
|
"settings_pcloud_auth": "鉴权",
|
||||||
|
"settings_pcloud_auth_desc": "鉴权.",
|
||||||
|
"settings_pcloud_auth_button": "鉴权",
|
||||||
|
"settings_pcloud_emptyfile": "空文件",
|
||||||
|
"settings_pcloud_emptyfile_desc": "pCloud API 不支持上传空文件,怎么处理?",
|
||||||
|
"settings_pcloud_emptyfile_skip": "跳过",
|
||||||
|
"settings_pcloud_emptyfile_error": "提示错误",
|
||||||
|
"settings_pcloud_connect_succ": "很好!我们可连接上 pCloud!",
|
||||||
|
"settings_pcloud_connect_fail": "我们未能连接上 pCloud。",
|
||||||
|
|
||||||
"settings_export_googledrive_button": "导出 Google Drive 部分",
|
"settings_export_googledrive_button": "导出 Google Drive 部分",
|
||||||
"settings_export_box_button": "导出 Box 部分",
|
"settings_export_box_button": "导出 Box 部分",
|
||||||
|
"settings_export_pcloud_button": "导出 pCloud 部分",
|
||||||
|
|
||||||
"settings_pro": "账号(PRO 付费功能)",
|
"settings_pro": "账号(PRO 付费功能)",
|
||||||
"settings_pro_tutorial": "<p>使用 Remotely Save 的<stong>基本</strong>功能是<strong>免费的</strong>,而且<strong>不</strong>需要注册对应账号。</p><p>但是,您<strong>需要</strong>注册账号和对<strong>PRO</strong>功能<strong>付费</strong>使用,如智能处理冲突功能。</p><p>第一步:点击按钮从而注册和登录网站:<a href=\"https://remotelysave.com\">https://remotelysave.com</a>。注意:这和 Obsidian 官方账号无关,是不同的账号。</p><p>第二部:点击“连接”按钮,从而连接本设备和在线账号。",
|
"settings_pro_tutorial": "<p>使用 Remotely Save 的<stong>基本</strong>功能是<strong>免费的</strong>,而且<strong>不</strong>需要注册对应账号。</p><p>但是,您<strong>需要</strong>注册账号和对<strong>PRO</strong>功能<strong>付费</strong>使用,如智能处理冲突功能。</p><p>第一步:点击按钮从而注册和登录网站:<a href=\"https://remotelysave.com\">https://remotelysave.com</a>。注意:这和 Obsidian 官方账号无关,是不同的账号。</p><p>第二部:点击“连接”按钮,从而连接本设备和在线账号。",
|
||||||
|
|||||||
@ -12,6 +12,11 @@
|
|||||||
"protocol_box_connect_fail": "Box 官網返回錯誤。可能是網路連線不穩定。也可能是您拒絕了授權?",
|
"protocol_box_connect_fail": "Box 官網返回錯誤。可能是網路連線不穩定。也可能是您拒絕了授權?",
|
||||||
"protocol_box_connect_succ_revoke": "您已連線上賬號。如果要取消連線,請點選此按鈕。",
|
"protocol_box_connect_succ_revoke": "您已連線上賬號。如果要取消連線,請點選此按鈕。",
|
||||||
|
|
||||||
|
"protocol_pcloud_connecting": "正在連線",
|
||||||
|
"protocol_pcloud_connect_manualinput_succ": "連線成功",
|
||||||
|
"protocol_pcloud_connect_fail": "pCloud 官網返回錯誤。可能是網路連線不穩定。也可能是您拒絕了授權?",
|
||||||
|
"protocol_pcloud_connect_succ_revoke": "您已連線上賬號。如果要取消連線,請點選此按鈕。",
|
||||||
|
|
||||||
"modal_googledriveauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會看到一個碼,請複製貼上到這裡然後提交。</p>",
|
"modal_googledriveauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會看到一個碼,請複製貼上到這裡然後提交。</p>",
|
||||||
"modal_googledriveauth_copybutton": "點選以複製網址",
|
"modal_googledriveauth_copybutton": "點選以複製網址",
|
||||||
"modal_googledriveauth_copynotice": "網址已複製!",
|
"modal_googledriveauth_copynotice": "網址已複製!",
|
||||||
@ -28,6 +33,33 @@
|
|||||||
"modal_googledriverevokeauth_clean_notice": "已清理!",
|
"modal_googledriverevokeauth_clean_notice": "已清理!",
|
||||||
"modal_googledriverevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
"modal_googledriverevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||||
|
|
||||||
|
"modal_boxauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會被重定向回來。</p>",
|
||||||
|
"modal_boxauth_copybutton": "點選以複製網址",
|
||||||
|
"modal_boxauth_copynotice": "網址已複製!",
|
||||||
|
"modal_box_maualinput": "網站上的碼",
|
||||||
|
"modal_box_maualinput_desc": "請貼上授權流程最後的那個碼,然後點選確認。",
|
||||||
|
"modal_box_maualinput_notice": "正在嘗試連線 Box 並更新授權資訊......",
|
||||||
|
"modal_box_maualinput_succ_notice": "很好!授權資訊已更新!",
|
||||||
|
"modal_box_maualinput_fail_notice": "更新授權資訊失敗。請稍後重試。",
|
||||||
|
"modal_boxrevokeauth_step1": "第 1 步:訪問以下網址,可以刪除連線。",
|
||||||
|
"modal_boxrevokeauth_step2": "第 2 步:點選以下按鈕,從而清理本地的登入資訊。",
|
||||||
|
"modal_boxrevokeauth_clean": "清理本地登入資訊",
|
||||||
|
"modal_boxrevokeauth_clean_desc": "您需要點選此按鈕。",
|
||||||
|
"modal_boxrevokeauth_clean_button": "清理",
|
||||||
|
"modal_boxrevokeauth_clean_notice": "已清理!",
|
||||||
|
"modal_boxrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||||
|
|
||||||
|
"modal_pcloudauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會被重定向回來。</p>",
|
||||||
|
"modal_pcloudauth_copybutton": "點選以複製網址",
|
||||||
|
"modal_pcloudauth_copynotice": "網址已複製!",
|
||||||
|
"modal_pcloudrevokeauth_step1": "第 1 步:訪問以下網址,可以刪除連線。",
|
||||||
|
"modal_pcloudrevokeauth_step2": "第 2 步:點選以下按鈕,從而清理本地的登入資訊。",
|
||||||
|
"modal_pcloudrevokeauth_clean": "清理本地登入資訊",
|
||||||
|
"modal_pcloudrevokeauth_clean_desc": "您需要點選此按鈕。",
|
||||||
|
"modal_pcloudrevokeauth_clean_button": "清理",
|
||||||
|
"modal_pcloudrevokeauth_clean_notice": "已清理!",
|
||||||
|
"modal_pcloudrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||||
|
|
||||||
"modal_prorevokeauth": "點選這裡和按照步驟取消授權。",
|
"modal_prorevokeauth": "點選這裡和按照步驟取消授權。",
|
||||||
"modal_prorevokeauth_clean": "清理",
|
"modal_prorevokeauth_clean": "清理",
|
||||||
"modal_prorevokeauth_clean_desc": "清理本地授權記錄",
|
"modal_prorevokeauth_clean_desc": "清理本地授權記錄",
|
||||||
@ -59,22 +91,6 @@
|
|||||||
"settings_googledrive_connect_succ": "很好!我們可連線上 Google Drive!",
|
"settings_googledrive_connect_succ": "很好!我們可連線上 Google Drive!",
|
||||||
"settings_googledrive_connect_fail": "我們未能連線上 Google Drive。",
|
"settings_googledrive_connect_fail": "我們未能連線上 Google Drive。",
|
||||||
|
|
||||||
"modal_boxauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會被重定向回來。</p>",
|
|
||||||
"modal_boxauth_copybutton": "點選以複製網址",
|
|
||||||
"modal_boxauth_copynotice": "網址已複製!",
|
|
||||||
"modal_box_maualinput": "網站上的碼",
|
|
||||||
"modal_box_maualinput_desc": "請貼上授權流程最後的那個碼,然後點選確認。",
|
|
||||||
"modal_box_maualinput_notice": "正在嘗試連線 Box 並更新授權資訊......",
|
|
||||||
"modal_box_maualinput_succ_notice": "很好!授權資訊已更新!",
|
|
||||||
"modal_box_maualinput_fail_notice": "更新授權資訊失敗。請稍後重試。",
|
|
||||||
"modal_boxrevokeauth_step1": "第 1 步:訪問以下網址,可以刪除連線。",
|
|
||||||
"modal_boxrevokeauth_step2": "第 2 步:點選以下按鈕,從而清理本地的登入資訊。",
|
|
||||||
"modal_boxrevokeauth_clean": "清理本地登入資訊",
|
|
||||||
"modal_boxrevokeauth_clean_desc": "您需要點選此按鈕。",
|
|
||||||
"modal_boxrevokeauth_clean_button": "清理",
|
|
||||||
"modal_boxrevokeauth_clean_notice": "已清理!",
|
|
||||||
"modal_boxrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
|
||||||
|
|
||||||
"settings_box": "Box (PRO) (beta)",
|
"settings_box": "Box (PRO) (beta)",
|
||||||
"settings_chooseservice_box": "Box (PRO) (beta)",
|
"settings_chooseservice_box": "Box (PRO) (beta)",
|
||||||
"settings_box_disclaimer1": "宣告:本外掛不是 Box 的官方產品。只是用到了它的公開 API。",
|
"settings_box_disclaimer1": "宣告:本外掛不是 Box 的官方產品。只是用到了它的公開 API。",
|
||||||
@ -93,8 +109,31 @@
|
|||||||
"settings_box_connect_succ": "很好!我們可連線上 Box!",
|
"settings_box_connect_succ": "很好!我們可連線上 Box!",
|
||||||
"settings_box_connect_fail": "我們未能連線上 Box。",
|
"settings_box_connect_fail": "我們未能連線上 Box。",
|
||||||
|
|
||||||
|
"settings_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_chooseservice_pcloud": "pCloud (PRO) (beta)",
|
||||||
|
"settings_pcloud_disclaimer1": "宣告:本外掛不是 pCloud 的官方產品。只是用到了它的公開 API。",
|
||||||
|
"settings_pcloud_disclaimer2": "宣告:您所輸入的資訊儲存於本地。其它有害的或者出錯的外掛,是有可能讀取到這些資訊的。如果您發現任何不符合預期的 pCloud 訪問,請立刻在以下網站操作斷開連線: https://my.pcloud.com/#page=settings&settings=tab-apps 。",
|
||||||
|
"settings_pcloud_pro_desc": "<p><strong>!!這是 PRO(付費)功能! 您需要線上賬號來使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 賬號的更多資訊。)</p>",
|
||||||
|
"settings_pcloud_notshowuphint": "pCloud 設定不可用",
|
||||||
|
"settings_pcloud_notshowuphint_desc": "pCloud 設定不可用,因為您沒有在 Remotely Save 賬號裡開啟這個 PRO 功能。",
|
||||||
|
"settings_pcloud_notshowuphint_view_pro": "檢視 PRO 相關設定",
|
||||||
|
"settings_pcloud_folder": "我們會在 pCloud 建立此資料夾並同步內容進去: {{remoteBaseDir}} 。請不要手動在網站上建立。",
|
||||||
|
"settings_pcloud_revoke": "撤回鑑權",
|
||||||
|
"settings_pcloud_revoke_desc": "您現在已連線。如果想取消連線,請點選此按鈕。",
|
||||||
|
"settings_pcloud_revoke_button": "撤回鑑權",
|
||||||
|
"settings_pcloud_auth": "鑑權",
|
||||||
|
"settings_pcloud_auth_desc": "鑑權.",
|
||||||
|
"settings_pcloud_auth_button": "鑑權",
|
||||||
|
"settings_pcloud_emptyfile": "空檔案",
|
||||||
|
"settings_pcloud_emptyfile_desc": "pCloud API 不支援上傳空檔案,怎麼處理?",
|
||||||
|
"settings_pcloud_emptyfile_skip": "跳過",
|
||||||
|
"settings_pcloud_emptyfile_error": "提示錯誤",
|
||||||
|
"settings_pcloud_connect_succ": "很好!我們可連線上 pCloud!",
|
||||||
|
"settings_pcloud_connect_fail": "我們未能連線上 pCloud。",
|
||||||
|
|
||||||
"settings_export_googledrive_button": "匯出 Google Drive 部分",
|
"settings_export_googledrive_button": "匯出 Google Drive 部分",
|
||||||
"settings_export_box_button": "匯出 Box 部分",
|
"settings_export_box_button": "匯出 Box 部分",
|
||||||
|
"settings_export_pcloud_button": "匯出 pCloud 部分",
|
||||||
|
|
||||||
"settings_pro": "賬號(PRO 付費功能)",
|
"settings_pro": "賬號(PRO 付費功能)",
|
||||||
"settings_pro_tutorial": "<p>使用 Remotely Save 的<stong>基本</strong>功能是<strong>免費的</strong>,而且<strong>不</strong>需要註冊對應賬號。</p><p>但是,您<strong>需要</strong>註冊賬號和對<strong>PRO</strong>功能<strong>付費</strong>使用,如智慧處理衝突功能。</p><p>第一步:點選按鈕從而註冊和登入網站:<a href=\"https://remotelysave.com\">https://remotelysave.com</a>。注意:這和 Obsidian 官方賬號無關,是不同的賬號。</p><p>第二部:點選“連線”按鈕,從而連線本裝置和線上賬號。",
|
"settings_pro_tutorial": "<p>使用 Remotely Save 的<stong>基本</strong>功能是<strong>免費的</strong>,而且<strong>不</strong>需要註冊對應賬號。</p><p>但是,您<strong>需要</strong>註冊賬號和對<strong>PRO</strong>功能<strong>付費</strong>使用,如智慧處理衝突功能。</p><p>第一步:點選按鈕從而註冊和登入網站:<a href=\"https://remotelysave.com\">https://remotelysave.com</a>。注意:這和 Obsidian 官方賬號無關,是不同的賬號。</p><p>第二部:點選“連線”按鈕,從而連線本裝置和線上賬號。",
|
||||||
|
|||||||
325
pro/src/settingsPCloud.ts
Normal file
325
pro/src/settingsPCloud.ts
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import { type App, Modal, Notice, Setting } from "obsidian";
|
||||||
|
import { getClient } from "../../src/fsGetter";
|
||||||
|
import type { TransItemType } from "../../src/i18n";
|
||||||
|
import type RemotelySavePlugin from "../../src/main";
|
||||||
|
import { stringToFragment } from "../../src/misc";
|
||||||
|
import { ChangeRemoteBaseDirModal } from "../../src/settings";
|
||||||
|
import { DEFAULT_PCLOUD_CONFIG, generateAuthUrl } from "./fsPCloud";
|
||||||
|
|
||||||
|
class PCloudAuthModal extends Modal {
|
||||||
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
readonly authDiv: HTMLDivElement;
|
||||||
|
readonly revokeAuthDiv: HTMLDivElement;
|
||||||
|
readonly revokeAuthSetting: Setting;
|
||||||
|
readonly t: (x: TransItemType, vars?: any) => string;
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
authDiv: HTMLDivElement,
|
||||||
|
revokeAuthDiv: HTMLDivElement,
|
||||||
|
revokeAuthSetting: Setting,
|
||||||
|
t: (x: TransItemType, vars?: any) => string
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.authDiv = authDiv;
|
||||||
|
this.revokeAuthDiv = revokeAuthDiv;
|
||||||
|
this.revokeAuthSetting = revokeAuthSetting;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onOpen() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
const t = this.t;
|
||||||
|
|
||||||
|
const { authUrl } = await generateAuthUrl(true);
|
||||||
|
const div2 = contentEl.createDiv();
|
||||||
|
div2.createDiv({
|
||||||
|
text: stringToFragment(t("modal_pcloudauth_tutorial")),
|
||||||
|
});
|
||||||
|
div2.createEl(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
text: t("modal_pcloudauth_copybutton"),
|
||||||
|
},
|
||||||
|
(el) => {
|
||||||
|
el.onclick = async () => {
|
||||||
|
await navigator.clipboard.writeText(authUrl);
|
||||||
|
new Notice(t("modal_pcloudauth_copynotice"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
contentEl.createEl("p").createEl("a", {
|
||||||
|
href: authUrl,
|
||||||
|
text: authUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PCloudRevokeAuthModal extends Modal {
|
||||||
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
readonly authDiv: HTMLDivElement;
|
||||||
|
readonly revokeAuthDiv: HTMLDivElement;
|
||||||
|
readonly t: (x: TransItemType, vars?: any) => string;
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
authDiv: HTMLDivElement,
|
||||||
|
revokeAuthDiv: HTMLDivElement,
|
||||||
|
t: (x: TransItemType, vars?: any) => string
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.authDiv = authDiv;
|
||||||
|
this.revokeAuthDiv = revokeAuthDiv;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onOpen() {
|
||||||
|
const t = this.t;
|
||||||
|
const { contentEl } = this;
|
||||||
|
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: t("modal_pcloudrevokeauth_step1"),
|
||||||
|
});
|
||||||
|
const consentUrl = "https://my.pcloud.com/#page=settings&settings=tab-apps";
|
||||||
|
contentEl.createEl("p").createEl("a", {
|
||||||
|
href: consentUrl,
|
||||||
|
text: consentUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: t("modal_pcloudrevokeauth_step2"),
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName(t("modal_pcloudrevokeauth_clean"))
|
||||||
|
.setDesc(t("modal_pcloudrevokeauth_clean_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("modal_pcloudrevokeauth_clean_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
try {
|
||||||
|
this.plugin.settings.pcloud = cloneDeep(DEFAULT_PCLOUD_CONFIG);
|
||||||
|
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
this.authDiv.toggleClass(
|
||||||
|
"pcloud-auth-button-hide",
|
||||||
|
this.plugin.settings.pcloud.accessToken !== ""
|
||||||
|
);
|
||||||
|
this.revokeAuthDiv.toggleClass(
|
||||||
|
"pcloud-revoke-auth-button-hide",
|
||||||
|
this.plugin.settings.pcloud.accessToken === ""
|
||||||
|
);
|
||||||
|
new Notice(t("modal_pcloudrevokeauth_clean_notice"));
|
||||||
|
this.close();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
new Notice(t("modal_pcloudrevokeauth_clean_fail"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generatePCloudSettingsPart = (
|
||||||
|
containerEl: HTMLElement,
|
||||||
|
t: (x: TransItemType, vars?: any) => string,
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any> | undefined
|
||||||
|
) => {
|
||||||
|
const pCloudDiv = containerEl.createEl("div", {
|
||||||
|
cls: "pcloud-hide",
|
||||||
|
});
|
||||||
|
pCloudDiv.toggleClass(
|
||||||
|
"pcloud-hide",
|
||||||
|
plugin.settings.serviceType !== "pcloud"
|
||||||
|
);
|
||||||
|
pCloudDiv.createEl("h2", { text: t("settings_pcloud") });
|
||||||
|
|
||||||
|
const pcloudLongDescDiv = pCloudDiv.createEl("div", {
|
||||||
|
cls: "settings-long-desc",
|
||||||
|
});
|
||||||
|
for (const c of [
|
||||||
|
t("settings_pcloud_disclaimer1"),
|
||||||
|
t("settings_pcloud_disclaimer2"),
|
||||||
|
]) {
|
||||||
|
pcloudLongDescDiv.createEl("p", {
|
||||||
|
text: c,
|
||||||
|
cls: "pcloud-disclaimer",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pcloudLongDescDiv.createEl("p", {
|
||||||
|
text: t("settings_pcloud_folder", {
|
||||||
|
remoteBaseDir:
|
||||||
|
plugin.settings.pcloud.remoteBaseDir || app.vault.getName(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
pcloudLongDescDiv.createDiv({
|
||||||
|
text: stringToFragment(t("settings_pcloud_pro_desc")),
|
||||||
|
cls: "pcloud-disclaimer",
|
||||||
|
});
|
||||||
|
|
||||||
|
const pCloudNotShowUpHintSetting = new Setting(pCloudDiv)
|
||||||
|
.setName(t("settings_pcloud_notshowuphint"))
|
||||||
|
.setDesc(t("settings_pcloud_notshowuphint_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_pcloud_notshowuphint_view_pro"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
window.location.href = "#settings-pro";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pCloudAllowedToUsedDiv = pCloudDiv.createDiv();
|
||||||
|
// if pro enabled, show up; otherwise hide.
|
||||||
|
const allowPCloud =
|
||||||
|
plugin.settings.pro?.enabledProFeatures.filter(
|
||||||
|
(x) => x.featureName === "feature-pcloud"
|
||||||
|
).length === 1;
|
||||||
|
console.debug(`allow to show up pcloud settings? ${allowPCloud}`);
|
||||||
|
if (allowPCloud) {
|
||||||
|
pCloudAllowedToUsedDiv.removeClass("pcloud-allow-to-use-hide");
|
||||||
|
pCloudNotShowUpHintSetting.settingEl.addClass("pcloud-allow-to-use-hide");
|
||||||
|
} else {
|
||||||
|
pCloudAllowedToUsedDiv.addClass("pcloud-allow-to-use-hide");
|
||||||
|
pCloudNotShowUpHintSetting.settingEl.removeClass(
|
||||||
|
"pcloud-allow-to-use-hide"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pcloudSelectAuthDiv = pCloudAllowedToUsedDiv.createDiv();
|
||||||
|
const pcloudAuthDiv = pcloudSelectAuthDiv.createDiv({
|
||||||
|
cls: "pcloud-auth-button-hide settings-auth-related",
|
||||||
|
});
|
||||||
|
const pcloudRevokeAuthDiv = pcloudSelectAuthDiv.createDiv({
|
||||||
|
cls: "pcloud-revoke-auth-button-hide settings-auth-related",
|
||||||
|
});
|
||||||
|
|
||||||
|
const pcloudRevokeAuthSetting = new Setting(pcloudRevokeAuthDiv)
|
||||||
|
.setName(t("settings_pcloud_revoke"))
|
||||||
|
.setDesc(t("settings_pcloud_revoke_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_pcloud_revoke_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new PCloudRevokeAuthModal(
|
||||||
|
app,
|
||||||
|
plugin,
|
||||||
|
pcloudAuthDiv,
|
||||||
|
pcloudRevokeAuthDiv,
|
||||||
|
t
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(pcloudAuthDiv)
|
||||||
|
.setName(t("settings_pcloud_auth"))
|
||||||
|
.setDesc(t("settings_pcloud_auth_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_pcloud_auth_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
const modal = new PCloudAuthModal(
|
||||||
|
app,
|
||||||
|
plugin,
|
||||||
|
pcloudAuthDiv,
|
||||||
|
pcloudRevokeAuthDiv,
|
||||||
|
pcloudRevokeAuthSetting,
|
||||||
|
t
|
||||||
|
);
|
||||||
|
plugin.oauth2Info.helperModal = modal;
|
||||||
|
plugin.oauth2Info.authDiv = pcloudAuthDiv;
|
||||||
|
plugin.oauth2Info.revokeDiv = pcloudRevokeAuthDiv;
|
||||||
|
plugin.oauth2Info.revokeAuthSetting = pcloudRevokeAuthSetting;
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pcloudAuthDiv.toggleClass(
|
||||||
|
"pcloud-auth-button-hide",
|
||||||
|
plugin.settings.pcloud.accessToken !== ""
|
||||||
|
);
|
||||||
|
pcloudRevokeAuthDiv.toggleClass(
|
||||||
|
"pcloud-revoke-auth-button-hide",
|
||||||
|
plugin.settings.pcloud.accessToken === ""
|
||||||
|
);
|
||||||
|
|
||||||
|
let newpcloudRemoteBaseDir = plugin.settings.pcloud.remoteBaseDir || "";
|
||||||
|
new Setting(pCloudAllowedToUsedDiv)
|
||||||
|
.setName(t("settings_remotebasedir"))
|
||||||
|
.setDesc(t("settings_remotebasedir_desc"))
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(app.vault.getName())
|
||||||
|
.setValue(newpcloudRemoteBaseDir)
|
||||||
|
.onChange((value) => {
|
||||||
|
newpcloudRemoteBaseDir = value.trim();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(() => {
|
||||||
|
new ChangeRemoteBaseDirModal(
|
||||||
|
app,
|
||||||
|
plugin,
|
||||||
|
newpcloudRemoteBaseDir,
|
||||||
|
"pcloud"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(pCloudAllowedToUsedDiv)
|
||||||
|
.setName(t("settings_pcloud_emptyfile"))
|
||||||
|
.setDesc(t("settings_pcloud_emptyfile_desc"))
|
||||||
|
.addDropdown(async (dropdown) => {
|
||||||
|
dropdown
|
||||||
|
.addOption("skip", t("settings_pcloud_emptyfile_skip"))
|
||||||
|
.addOption("error", t("settings_pcloud_emptyfile_error"))
|
||||||
|
.setValue(plugin.settings.pcloud.emptyFile)
|
||||||
|
.onChange(async (val) => {
|
||||||
|
plugin.settings.pcloud.emptyFile = val as any;
|
||||||
|
await saveUpdatedConfigFunc();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(pCloudAllowedToUsedDiv)
|
||||||
|
.setName(t("settings_checkonnectivity"))
|
||||||
|
.setDesc(t("settings_checkonnectivity_desc"))
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_checkonnectivity_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new Notice(t("settings_checkonnectivity_checking"));
|
||||||
|
const client = getClient(plugin.settings, app.vault.getName(), () =>
|
||||||
|
plugin.saveSettings()
|
||||||
|
);
|
||||||
|
const errors = { msg: "" };
|
||||||
|
const res = await client.checkConnect((err: any) => {
|
||||||
|
errors.msg = `${err}`;
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
new Notice(t("settings_pcloud_connect_succ"));
|
||||||
|
} else {
|
||||||
|
new Notice(t("settings_pcloud_connect_fail"));
|
||||||
|
new Notice(errors.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pCloudDiv: pCloudDiv,
|
||||||
|
pCloudAllowedToUsedDiv: pCloudAllowedToUsedDiv,
|
||||||
|
pCloudNotShowUpHintSetting: pCloudNotShowUpHintSetting,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -249,7 +249,11 @@ export const generateProSettingsPart = (
|
|||||||
plugin: RemotelySavePlugin,
|
plugin: RemotelySavePlugin,
|
||||||
saveUpdatedConfigFunc: () => Promise<any> | undefined,
|
saveUpdatedConfigFunc: () => Promise<any> | undefined,
|
||||||
googleDriveAllowedToUsedDiv: HTMLDivElement,
|
googleDriveAllowedToUsedDiv: HTMLDivElement,
|
||||||
googleDriveNotShowUpHintSetting: Setting
|
googleDriveNotShowUpHintSetting: Setting,
|
||||||
|
boxAllowedToUsedDiv: HTMLDivElement,
|
||||||
|
boxNotShowUpHintSetting: Setting,
|
||||||
|
pCloudAllowedToUsedDiv: HTMLDivElement,
|
||||||
|
pCloudNotShowUpHintSetting: Setting
|
||||||
) => {
|
) => {
|
||||||
proDiv
|
proDiv
|
||||||
.createEl("h2", { text: t("settings_pro") })
|
.createEl("h2", { text: t("settings_pro") })
|
||||||
@ -317,6 +321,36 @@ export const generateProSettingsPart = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowBox =
|
||||||
|
plugin.settings.pro?.enabledProFeatures.filter(
|
||||||
|
(x) => x.featureName === "feature-box"
|
||||||
|
).length === 1;
|
||||||
|
console.debug(`allow to show up Box settings? ${allowBox}`);
|
||||||
|
if (allowBox) {
|
||||||
|
boxAllowedToUsedDiv.removeClass("box-allow-to-use-hide");
|
||||||
|
boxNotShowUpHintSetting.settingEl.addClass("box-allow-to-use-hide");
|
||||||
|
} else {
|
||||||
|
boxAllowedToUsedDiv.addClass("box-allow-to-use-hide");
|
||||||
|
boxNotShowUpHintSetting.settingEl.removeClass("box-allow-to-use-hide");
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowPCloud =
|
||||||
|
plugin.settings.pro?.enabledProFeatures.filter(
|
||||||
|
(x) => x.featureName === "feature-pcloud"
|
||||||
|
).length === 1;
|
||||||
|
console.debug(`allow to show up pCloud settings? ${allowPCloud}`);
|
||||||
|
if (allowPCloud) {
|
||||||
|
pCloudAllowedToUsedDiv.removeClass("pcloud-allow-to-use-hide");
|
||||||
|
pCloudNotShowUpHintSetting.settingEl.addClass(
|
||||||
|
"pcloud-allow-to-use-hide"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pCloudAllowedToUsedDiv.addClass("pcloud-allow-to-use-hide");
|
||||||
|
pCloudNotShowUpHintSetting.settingEl.removeClass(
|
||||||
|
"pcloud-allow-to-use-hide"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
new Notice(t("settings_pro_features_refresh_succ"));
|
new Notice(t("settings_pro_features_refresh_succ"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
import type {
|
import type {
|
||||||
BoxConfig,
|
BoxConfig,
|
||||||
GoogleDriveConfig,
|
GoogleDriveConfig,
|
||||||
|
PCloudConfig,
|
||||||
ProConfig,
|
ProConfig,
|
||||||
} from "../pro/src/baseTypesPro";
|
} from "../pro/src/baseTypesPro";
|
||||||
import type { LangTypeAndAuto } from "./i18n";
|
import type { LangTypeAndAuto } from "./i18n";
|
||||||
@ -19,7 +20,8 @@ export type SUPPORTED_SERVICES_TYPE =
|
|||||||
| "onedrive"
|
| "onedrive"
|
||||||
| "webdis"
|
| "webdis"
|
||||||
| "googledrive"
|
| "googledrive"
|
||||||
| "box";
|
| "box"
|
||||||
|
| "pcloud";
|
||||||
|
|
||||||
export type SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR =
|
export type SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR =
|
||||||
| "webdav"
|
| "webdav"
|
||||||
@ -27,7 +29,8 @@ export type SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR =
|
|||||||
| "onedrive"
|
| "onedrive"
|
||||||
| "webdis"
|
| "webdis"
|
||||||
| "googledrive"
|
| "googledrive"
|
||||||
| "box";
|
| "box"
|
||||||
|
| "pcloud";
|
||||||
|
|
||||||
export interface S3Config {
|
export interface S3Config {
|
||||||
s3Endpoint: string;
|
s3Endpoint: string;
|
||||||
@ -123,7 +126,8 @@ export type QRExportType =
|
|||||||
| "webdav"
|
| "webdav"
|
||||||
| "webdis"
|
| "webdis"
|
||||||
| "googledrive"
|
| "googledrive"
|
||||||
| "box";
|
| "box"
|
||||||
|
| "pcloud";
|
||||||
|
|
||||||
export interface ProfilerConfig {
|
export interface ProfilerConfig {
|
||||||
enablePrinting?: boolean;
|
enablePrinting?: boolean;
|
||||||
@ -138,6 +142,7 @@ export interface RemotelySavePluginSettings {
|
|||||||
webdis: WebdisConfig;
|
webdis: WebdisConfig;
|
||||||
googledrive: GoogleDriveConfig;
|
googledrive: GoogleDriveConfig;
|
||||||
box: BoxConfig;
|
box: BoxConfig;
|
||||||
|
pcloud: PCloudConfig;
|
||||||
password: string;
|
password: string;
|
||||||
serviceType: SUPPORTED_SERVICES_TYPE;
|
serviceType: SUPPORTED_SERVICES_TYPE;
|
||||||
currLogLevel?: string;
|
currLogLevel?: string;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { FakeFsBox } from "../pro/src/fsBox";
|
import { FakeFsBox } from "../pro/src/fsBox";
|
||||||
import { FakeFsGoogleDrive } from "../pro/src/fsGoogleDrive";
|
import { FakeFsGoogleDrive } from "../pro/src/fsGoogleDrive";
|
||||||
|
import { FakeFsPCloud } from "../pro/src/fsPCloud";
|
||||||
import type { RemotelySavePluginSettings } from "./baseTypes";
|
import type { RemotelySavePluginSettings } from "./baseTypes";
|
||||||
import type { FakeFs } from "./fsAll";
|
import type { FakeFs } from "./fsAll";
|
||||||
import { FakeFsDropbox } from "./fsDropbox";
|
import { FakeFsDropbox } from "./fsDropbox";
|
||||||
@ -51,6 +52,12 @@ export function getClient(
|
|||||||
);
|
);
|
||||||
case "box":
|
case "box":
|
||||||
return new FakeFsBox(settings.box, vaultName, saveUpdatedConfigFunc);
|
return new FakeFsBox(settings.box, vaultName, saveUpdatedConfigFunc);
|
||||||
|
case "pcloud":
|
||||||
|
return new FakeFsPCloud(
|
||||||
|
settings.pcloud,
|
||||||
|
vaultName,
|
||||||
|
saveUpdatedConfigFunc
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
throw Error(`cannot init client for serviceType=${settings.serviceType}`);
|
throw Error(`cannot init client for serviceType=${settings.serviceType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export const exportQrCodeUri = async (
|
|||||||
delete settings2.webdis;
|
delete settings2.webdis;
|
||||||
delete settings2.googledrive;
|
delete settings2.googledrive;
|
||||||
delete settings2.box;
|
delete settings2.box;
|
||||||
|
delete settings2.pcloud;
|
||||||
delete settings2.pro;
|
delete settings2.pro;
|
||||||
} else if (exportFields === "s3") {
|
} else if (exportFields === "s3") {
|
||||||
settings2 = { s3: cloneDeep(settings.s3) };
|
settings2 = { s3: cloneDeep(settings.s3) };
|
||||||
@ -41,6 +42,8 @@ export const exportQrCodeUri = async (
|
|||||||
settings2 = { googledrive: cloneDeep(settings.googledrive) };
|
settings2 = { googledrive: cloneDeep(settings.googledrive) };
|
||||||
} else if (exportFields === "box") {
|
} else if (exportFields === "box") {
|
||||||
settings2 = { box: cloneDeep(settings.box) };
|
settings2 = { box: cloneDeep(settings.box) };
|
||||||
|
} else if (exportFields === "pcloud") {
|
||||||
|
settings2 = { pcloud: cloneDeep(settings.pcloud) };
|
||||||
}
|
}
|
||||||
|
|
||||||
delete settings2.vaultRandomID;
|
delete settings2.vaultRandomID;
|
||||||
|
|||||||
150
src/main.ts
150
src/main.ts
@ -1,4 +1,7 @@
|
|||||||
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||||
|
import AggregateError from "aggregate-error";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import throttle from "lodash/throttle";
|
||||||
import { FileText, RefreshCcw, RotateCcw, createElement } from "lucide";
|
import { FileText, RefreshCcw, RotateCcw, createElement } from "lucide";
|
||||||
import {
|
import {
|
||||||
Events,
|
Events,
|
||||||
@ -20,6 +23,25 @@ import {
|
|||||||
sendAuthReq as sendAuthReqPro,
|
sendAuthReq as sendAuthReqPro,
|
||||||
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplacePro,
|
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplacePro,
|
||||||
} from "../pro/src/account";
|
} from "../pro/src/account";
|
||||||
|
import {
|
||||||
|
COMMAND_CALLBACK_BOX,
|
||||||
|
COMMAND_CALLBACK_PCLOUD,
|
||||||
|
COMMAND_CALLBACK_PRO,
|
||||||
|
} from "../pro/src/baseTypesPro";
|
||||||
|
import {
|
||||||
|
DEFAULT_BOX_CONFIG,
|
||||||
|
FakeFsBox,
|
||||||
|
sendAuthReq as sendAuthReqBox,
|
||||||
|
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplaceBox,
|
||||||
|
} from "../pro/src/fsBox";
|
||||||
|
import { DEFAULT_GOOGLEDRIVE_CONFIG } from "../pro/src/fsGoogleDrive";
|
||||||
|
import {
|
||||||
|
type AuthAllowFirstRes as AuthAllowFirstResPCloud,
|
||||||
|
DEFAULT_PCLOUD_CONFIG,
|
||||||
|
generateAuthUrl as generateAuthUrlPCloud,
|
||||||
|
sendAuthReq as sendAuthReqPCloud,
|
||||||
|
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplacePCloud,
|
||||||
|
} from "../pro/src/fsPCloud";
|
||||||
import type {
|
import type {
|
||||||
RemotelySavePluginSettings,
|
RemotelySavePluginSettings,
|
||||||
SyncTriggerSourceType,
|
SyncTriggerSourceType,
|
||||||
@ -32,11 +54,15 @@ import {
|
|||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
import { API_VER_ENSURE_REQURL_OK } from "./baseTypesObs";
|
import { API_VER_ENSURE_REQURL_OK } from "./baseTypesObs";
|
||||||
import { messyConfigToNormal, normalConfigToMessy } from "./configPersist";
|
import { messyConfigToNormal, normalConfigToMessy } from "./configPersist";
|
||||||
|
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
||||||
import {
|
import {
|
||||||
DEFAULT_DROPBOX_CONFIG,
|
DEFAULT_DROPBOX_CONFIG,
|
||||||
sendAuthReq as sendAuthReqDropbox,
|
sendAuthReq as sendAuthReqDropbox,
|
||||||
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplaceDropbox,
|
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplaceDropbox,
|
||||||
} from "./fsDropbox";
|
} from "./fsDropbox";
|
||||||
|
import { FakeFsEncrypt } from "./fsEncrypt";
|
||||||
|
import { getClient } from "./fsGetter";
|
||||||
|
import { FakeFsLocal } from "./fsLocal";
|
||||||
import {
|
import {
|
||||||
type AccessCodeResponseSuccessfulType,
|
type AccessCodeResponseSuccessfulType,
|
||||||
DEFAULT_ONEDRIVE_CONFIG,
|
DEFAULT_ONEDRIVE_CONFIG,
|
||||||
@ -45,6 +71,7 @@ import {
|
|||||||
} from "./fsOnedrive";
|
} from "./fsOnedrive";
|
||||||
import { DEFAULT_S3_CONFIG } from "./fsS3";
|
import { DEFAULT_S3_CONFIG } from "./fsS3";
|
||||||
import { DEFAULT_WEBDAV_CONFIG } from "./fsWebdav";
|
import { DEFAULT_WEBDAV_CONFIG } from "./fsWebdav";
|
||||||
|
import { DEFAULT_WEBDIS_CONFIG } from "./fsWebdis";
|
||||||
import { I18n } from "./i18n";
|
import { I18n } from "./i18n";
|
||||||
import type { LangTypeAndAuto, TransItemType } from "./i18n";
|
import type { LangTypeAndAuto, TransItemType } from "./i18n";
|
||||||
import { importQrCodeUri } from "./importExport";
|
import { importQrCodeUri } from "./importExport";
|
||||||
@ -57,31 +84,11 @@ import {
|
|||||||
upsertLastSuccessSyncTimeByVault,
|
upsertLastSuccessSyncTimeByVault,
|
||||||
upsertPluginVersionByVault,
|
upsertPluginVersionByVault,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
import { RemotelySaveSettingTab } from "./settings";
|
|
||||||
import { SyncAlgoV3Modal } from "./syncAlgoV3Notice";
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
|
||||||
import AggregateError from "aggregate-error";
|
|
||||||
import throttle from "lodash/throttle";
|
|
||||||
import {
|
|
||||||
COMMAND_CALLBACK_BOX,
|
|
||||||
COMMAND_CALLBACK_PRO,
|
|
||||||
} from "../pro/src/baseTypesPro";
|
|
||||||
import {
|
|
||||||
DEFAULT_BOX_CONFIG,
|
|
||||||
FakeFsBox,
|
|
||||||
sendAuthReq as sendAuthReqBox,
|
|
||||||
setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplaceBox,
|
|
||||||
} from "../pro/src/fsBox";
|
|
||||||
import { DEFAULT_GOOGLEDRIVE_CONFIG } from "../pro/src/fsGoogleDrive";
|
|
||||||
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
|
||||||
import { FakeFsEncrypt } from "./fsEncrypt";
|
|
||||||
import { getClient } from "./fsGetter";
|
|
||||||
import { FakeFsLocal } from "./fsLocal";
|
|
||||||
import { DEFAULT_WEBDIS_CONFIG } from "./fsWebdis";
|
|
||||||
import { changeMobileStatusBar } from "./misc";
|
import { changeMobileStatusBar } from "./misc";
|
||||||
import { DEFAULT_PROFILER_CONFIG, type Profiler } from "./profiler";
|
import { DEFAULT_PROFILER_CONFIG, type Profiler } from "./profiler";
|
||||||
|
import { RemotelySaveSettingTab } from "./settings";
|
||||||
import { syncer } from "./sync";
|
import { syncer } from "./sync";
|
||||||
|
import { SyncAlgoV3Modal } from "./syncAlgoV3Notice";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||||
s3: DEFAULT_S3_CONFIG,
|
s3: DEFAULT_S3_CONFIG,
|
||||||
@ -91,6 +98,7 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
|||||||
webdis: DEFAULT_WEBDIS_CONFIG,
|
webdis: DEFAULT_WEBDIS_CONFIG,
|
||||||
googledrive: DEFAULT_GOOGLEDRIVE_CONFIG,
|
googledrive: DEFAULT_GOOGLEDRIVE_CONFIG,
|
||||||
box: DEFAULT_BOX_CONFIG,
|
box: DEFAULT_BOX_CONFIG,
|
||||||
|
pcloud: DEFAULT_PCLOUD_CONFIG,
|
||||||
password: "",
|
password: "",
|
||||||
serviceType: "s3",
|
serviceType: "s3",
|
||||||
currLogLevel: "info",
|
currLogLevel: "info",
|
||||||
@ -848,6 +856,64 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.registerObsidianProtocolHandler(
|
||||||
|
COMMAND_CALLBACK_PCLOUD,
|
||||||
|
async (inputParams) => {
|
||||||
|
if (this.oauth2Info.helperModal !== undefined) {
|
||||||
|
const k = this.oauth2Info.helperModal.contentEl;
|
||||||
|
k.empty();
|
||||||
|
|
||||||
|
t("protocol_pcloud_connecting")
|
||||||
|
.split("\n")
|
||||||
|
.forEach((val) => {
|
||||||
|
k.createEl("p", {
|
||||||
|
text: val,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(inputParams);
|
||||||
|
const authRes = await sendAuthReqPCloud(
|
||||||
|
inputParams.hostname,
|
||||||
|
inputParams.code,
|
||||||
|
async (e: any) => {
|
||||||
|
new Notice(t("protocol_pcloud_connect_fail"));
|
||||||
|
new Notice(`${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.debug(authRes);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
await setConfigBySuccessfullAuthInplacePCloud(
|
||||||
|
this.settings.pcloud!,
|
||||||
|
inputParams as unknown as AuthAllowFirstResPCloud,
|
||||||
|
authRes,
|
||||||
|
() => self.saveSettings()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.oauth2Info.verifier = ""; // reset it
|
||||||
|
this.oauth2Info.helperModal?.close(); // close it
|
||||||
|
this.oauth2Info.helperModal = undefined;
|
||||||
|
|
||||||
|
this.oauth2Info.authDiv?.toggleClass(
|
||||||
|
"pcloud-auth-button-hide",
|
||||||
|
this.settings.pcloud?.accessToken !== ""
|
||||||
|
);
|
||||||
|
this.oauth2Info.authDiv = undefined;
|
||||||
|
|
||||||
|
this.oauth2Info.revokeAuthSetting?.setDesc(
|
||||||
|
t("protocol_pcloud_connect_succ_revoke")
|
||||||
|
);
|
||||||
|
this.oauth2Info.revokeAuthSetting = undefined;
|
||||||
|
this.oauth2Info.revokeDiv?.toggleClass(
|
||||||
|
"pcloud-revoke-auth-button-hide",
|
||||||
|
this.settings.pcloud?.accessToken === ""
|
||||||
|
);
|
||||||
|
this.oauth2Info.revokeDiv = undefined;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.syncRibbon = this.addRibbonIcon(
|
this.syncRibbon = this.addRibbonIcon(
|
||||||
iconNameSyncWait,
|
iconNameSyncWait,
|
||||||
`${this.manifest.name}`,
|
`${this.manifest.name}`,
|
||||||
@ -1138,6 +1204,10 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.box = DEFAULT_BOX_CONFIG;
|
this.settings.box = DEFAULT_BOX_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.settings.pcloud === undefined) {
|
||||||
|
this.settings.pcloud = DEFAULT_PCLOUD_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1226,6 +1296,16 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
needSave = true;
|
needSave = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pCloudExpired = false;
|
||||||
|
if (
|
||||||
|
this.settings.pcloud.accessToken !== "" &&
|
||||||
|
current >= this.settings!.pcloud!.credentialsShouldBeDeletedAtTimeMs!
|
||||||
|
) {
|
||||||
|
pCloudExpired = true;
|
||||||
|
this.settings.pcloud = cloneDeep(DEFAULT_PCLOUD_CONFIG);
|
||||||
|
needSave = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.settings.pro === undefined) {
|
if (this.settings.pro === undefined) {
|
||||||
this.settings.pro = cloneDeep(DEFAULT_PRO_CONFIG);
|
this.settings.pro = cloneDeep(DEFAULT_PRO_CONFIG);
|
||||||
}
|
}
|
||||||
@ -1236,19 +1316,33 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send notice
|
// send notice
|
||||||
if (dropboxExpired && onedriveExpired) {
|
if (dropboxExpired) {
|
||||||
new Notice(
|
new Notice(
|
||||||
`${this.manifest.name}: You haven't manually auth Dropbox and OneDrive for a while, you need to re-auth them again.`,
|
`${this.manifest.name}: You haven't manually auth Dropbox for many days, you need to re-auth it again.`,
|
||||||
6000
|
6000
|
||||||
);
|
);
|
||||||
} else if (dropboxExpired) {
|
}
|
||||||
|
if (onedriveExpired) {
|
||||||
new Notice(
|
new Notice(
|
||||||
`${this.manifest.name}: You haven't manually auth Dropbox for a while, you need to re-auth it again.`,
|
`${this.manifest.name}: You haven't manually auth OneDrive for many days, you need to re-auth it again.`,
|
||||||
6000
|
6000
|
||||||
);
|
);
|
||||||
} else if (onedriveExpired) {
|
}
|
||||||
|
if (googleDriveExpired) {
|
||||||
new Notice(
|
new Notice(
|
||||||
`${this.manifest.name}: You haven't manually auth OneDrive for a while, you need to re-auth it again.`,
|
`${this.manifest.name}: You haven't manually auth Google Drive for many days, you need to re-auth it again.`,
|
||||||
|
6000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (boxExpired) {
|
||||||
|
new Notice(
|
||||||
|
`${this.manifest.name}: You haven't manually auth Box for many days, you need to re-auth it again.`,
|
||||||
|
6000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pCloudExpired) {
|
||||||
|
new Notice(
|
||||||
|
`${this.manifest.name}: You haven't manually auth pCloud for many days, you need to re-auth it again.`,
|
||||||
6000
|
6000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import type {
|
|||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { generateBoxSettingsPart } from "../pro/src/settingsBox";
|
import { generateBoxSettingsPart } from "../pro/src/settingsBox";
|
||||||
import { generateGoogleDriveSettingsPart } from "../pro/src/settingsGoogleDrive";
|
import { generateGoogleDriveSettingsPart } from "../pro/src/settingsGoogleDrive";
|
||||||
|
import { generatePCloudSettingsPart } from "../pro/src/settingsPCloud";
|
||||||
import { generateProSettingsPart } from "../pro/src/settingsPro";
|
import { generateProSettingsPart } from "../pro/src/settingsPro";
|
||||||
import { API_VER_ENSURE_REQURL_OK, VALID_REQURL } from "./baseTypesObs";
|
import { API_VER_ENSURE_REQURL_OK, VALID_REQURL } from "./baseTypesObs";
|
||||||
import { messyConfigToNormal } from "./configPersist";
|
import { messyConfigToNormal } from "./configPersist";
|
||||||
@ -1818,6 +1819,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.saveSettings()
|
this.plugin.saveSettings()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// below for box
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const { pCloudDiv, pCloudAllowedToUsedDiv, pCloudNotShowUpHintSetting } =
|
||||||
|
generatePCloudSettingsPart(containerEl, t, this.app, this.plugin, () =>
|
||||||
|
this.plugin.saveSettings()
|
||||||
|
);
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// below for general chooser (part 2/2)
|
// below for general chooser (part 2/2)
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
@ -1838,6 +1848,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
t("settings_chooseservice_googledrive")
|
t("settings_chooseservice_googledrive")
|
||||||
);
|
);
|
||||||
dropdown.addOption("box", t("settings_chooseservice_box"));
|
dropdown.addOption("box", t("settings_chooseservice_box"));
|
||||||
|
dropdown.addOption("pcloud", t("settings_chooseservice_pcloud"));
|
||||||
|
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.serviceType)
|
.setValue(this.plugin.settings.serviceType)
|
||||||
@ -1871,6 +1882,10 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
"box-hide",
|
"box-hide",
|
||||||
this.plugin.settings.serviceType !== "box"
|
this.plugin.settings.serviceType !== "box"
|
||||||
);
|
);
|
||||||
|
pCloudDiv.toggleClass(
|
||||||
|
"pcloud-hide",
|
||||||
|
this.plugin.settings.serviceType !== "pcloud"
|
||||||
|
);
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2439,6 +2454,12 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
new ExportSettingsQrCodeModal(this.app, this.plugin, "box").open();
|
new ExportSettingsQrCodeModal(this.app, this.plugin, "box").open();
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_export_pcloud_button"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
new ExportSettingsQrCodeModal(this.app, this.plugin, "pcloud").open();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let importSettingVal = "";
|
let importSettingVal = "";
|
||||||
@ -2505,7 +2526,11 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin,
|
this.plugin,
|
||||||
() => this.plugin.saveSettings(),
|
() => this.plugin.saveSettings(),
|
||||||
googleDriveAllowedToUsedDiv,
|
googleDriveAllowedToUsedDiv,
|
||||||
googleDriveNotShowUpHintSetting
|
googleDriveNotShowUpHintSetting,
|
||||||
|
boxAllowedToUsedDiv,
|
||||||
|
boxNotShowUpHintSetting,
|
||||||
|
pCloudAllowedToUsedDiv,
|
||||||
|
pCloudNotShowUpHintSetting
|
||||||
);
|
);
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|||||||
19
styles.css
19
styles.css
@ -110,6 +110,25 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pcloud-disclaimer {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.pcloud-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcloud-allow-to-use-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcloud-auth-button-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcloud-revoke-auth-button-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.qrcode-img {
|
.qrcode-img {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
|
|||||||
@ -25,6 +25,9 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
|||||||
box: {
|
box: {
|
||||||
refreshToken: "xxx",
|
refreshToken: "xxx",
|
||||||
} as any,
|
} as any,
|
||||||
|
pcloud: {
|
||||||
|
refreshToken: "xxx",
|
||||||
|
} as any,
|
||||||
password: "password",
|
password: "password",
|
||||||
serviceType: "s3",
|
serviceType: "s3",
|
||||||
currLogLevel: "info",
|
currLogLevel: "info",
|
||||||
|
|||||||
@ -13,6 +13,8 @@ const DEFAULT_GOOGLEDRIVE_CLIENT_SECRET =
|
|||||||
process.env.GOOGLEDRIVE_CLIENT_SECRET || "";
|
process.env.GOOGLEDRIVE_CLIENT_SECRET || "";
|
||||||
const DEFAULT_BOX_CLIENT_ID = process.env.BOX_CLIENT_ID || "";
|
const DEFAULT_BOX_CLIENT_ID = process.env.BOX_CLIENT_ID || "";
|
||||||
const DEFAULT_BOX_CLIENT_SECRET = process.env.BOX_CLIENT_SECRET || "";
|
const DEFAULT_BOX_CLIENT_SECRET = process.env.BOX_CLIENT_SECRET || "";
|
||||||
|
const DEFAULT_PCLOUD_CLIENT_ID = process.env.PCLOUD_CLIENT_ID || "";
|
||||||
|
const DEFAULT_PCLOUD_CLIENT_SECRET = process.env.PCLOUD_CLIENT_SECRET || "";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: "./src/main.ts",
|
entry: "./src/main.ts",
|
||||||
@ -33,6 +35,8 @@ module.exports = {
|
|||||||
"process.env.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET": `"${DEFAULT_GOOGLEDRIVE_CLIENT_SECRET}"`,
|
"process.env.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET": `"${DEFAULT_GOOGLEDRIVE_CLIENT_SECRET}"`,
|
||||||
"process.env.DEFAULT_BOX_CLIENT_ID": `"${DEFAULT_BOX_CLIENT_ID}"`,
|
"process.env.DEFAULT_BOX_CLIENT_ID": `"${DEFAULT_BOX_CLIENT_ID}"`,
|
||||||
"process.env.DEFAULT_BOX_CLIENT_SECRET": `"${DEFAULT_BOX_CLIENT_SECRET}"`,
|
"process.env.DEFAULT_BOX_CLIENT_SECRET": `"${DEFAULT_BOX_CLIENT_SECRET}"`,
|
||||||
|
"process.env.DEFAULT_PCLOUD_CLIENT_ID": `"${DEFAULT_PCLOUD_CLIENT_ID}"`,
|
||||||
|
"process.env.DEFAULT_PCLOUD_CLIENT_SECRET": `"${DEFAULT_PCLOUD_CLIENT_SECRET}"`,
|
||||||
}),
|
}),
|
||||||
// Work around for Buffer is undefined:
|
// Work around for Buffer is undefined:
|
||||||
// https://github.com/webpack/changelog-v5/issues/10
|
// https://github.com/webpack/changelog-v5/issues/10
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user