add koofr
This commit is contained in:
@@ -285,6 +285,11 @@ export const checkProRunnableAndFixInplace = async (
|
||||
service: "yandexdisk",
|
||||
name: "Yandex Disk",
|
||||
},
|
||||
{
|
||||
feature: "feature-koofr",
|
||||
service: "koofr",
|
||||
name: "Koofr",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { feature, service, name } of toChecked) {
|
||||
|
||||
+23
-1
@@ -11,7 +11,8 @@ export type PRO_FEATURE_TYPE =
|
||||
| "feature-google_drive"
|
||||
| "feature-box"
|
||||
| "feature-pcloud"
|
||||
| "feature-yandex_disk";
|
||||
| "feature-yandex_disk"
|
||||
| "feature-koofr";
|
||||
|
||||
export interface FeatureInfo {
|
||||
featureName: PRO_FEATURE_TYPE;
|
||||
@@ -114,3 +115,24 @@ export interface YandexDiskConfig {
|
||||
scope: string;
|
||||
kind: "yandexdisk";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Koofr
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
export const COMMAND_CALLBACK_KOOFR = "remotely-save-cb-koofr";
|
||||
export const KOOFR_CLIENT_ID = process.env.DEFAULT_KOOFR_CLIENT_ID;
|
||||
export const KOOFR_CLIENT_SECRET = process.env.DEFAULT_KOOFR_CLIENT_SECRET;
|
||||
|
||||
export interface KoofrConfig {
|
||||
accessToken: string;
|
||||
accessTokenExpiresInMs: number;
|
||||
accessTokenExpiresAtTimeMs: number;
|
||||
refreshToken: string;
|
||||
remoteBaseDir?: string;
|
||||
credentialsShouldBeDeletedAtTimeMs?: number;
|
||||
scope: string;
|
||||
api: string;
|
||||
mountID: string;
|
||||
kind: "koofr";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import createClient, { type Middleware } from "openapi-fetch";
|
||||
import { base64 } from "rfc4648";
|
||||
import type { Entity } from "../../src/baseTypes";
|
||||
import { FakeFs } from "../../src/fsAll";
|
||||
import { getParentFolder } from "../../src/misc";
|
||||
import {
|
||||
COMMAND_CALLBACK_KOOFR,
|
||||
KOOFR_CLIENT_ID,
|
||||
KOOFR_CLIENT_SECRET,
|
||||
type KoofrConfig,
|
||||
} from "./baseTypesPro";
|
||||
import type { paths } from "./koofrApi";
|
||||
import type { components } from "./koofrApi";
|
||||
|
||||
type FilesListRecursiveItem = components["schemas"]["FilesListRecursiveItem"];
|
||||
type FilesFile = components["schemas"]["FilesFile"];
|
||||
|
||||
export const DEFAULT_KOOFR_CONFIG: KoofrConfig = {
|
||||
accessToken: "",
|
||||
accessTokenExpiresInMs: 0,
|
||||
accessTokenExpiresAtTimeMs: 0,
|
||||
refreshToken: "",
|
||||
remoteBaseDir: "",
|
||||
credentialsShouldBeDeletedAtTimeMs: 0,
|
||||
scope: "",
|
||||
api: "https://app.koofr.net",
|
||||
mountID: "",
|
||||
kind: "koofr",
|
||||
};
|
||||
|
||||
/**
|
||||
* https://app.koofr.net/developers
|
||||
*/
|
||||
export const generateAuthUrl = (apiAddr: string, hasCallback: boolean) => {
|
||||
let callback = `urn:ietf:wg:oauth:2.0:oob`;
|
||||
if (hasCallback) {
|
||||
callback = `obsidian://${COMMAND_CALLBACK_KOOFR}`;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
response_type: "code",
|
||||
client_id: KOOFR_CLIENT_ID ?? "",
|
||||
redirect_uri: callback,
|
||||
scope: "public",
|
||||
state: nanoid(),
|
||||
});
|
||||
|
||||
const url = `${apiAddr}/oauth2/auth?${params}`;
|
||||
return url;
|
||||
};
|
||||
|
||||
interface AuthResSucc {
|
||||
token_type: "Bearer";
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string | undefined;
|
||||
}
|
||||
|
||||
interface AuthResFail {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://app.koofr.net/developers
|
||||
*/
|
||||
export const sendAuthReq = async (
|
||||
apiAddr: string,
|
||||
authCode: string,
|
||||
errorCallBack: any,
|
||||
hasCallback: boolean
|
||||
) => {
|
||||
let callback = `urn:ietf:wg:oauth:2.0:oob`;
|
||||
if (hasCallback) {
|
||||
callback = `obsidian://${COMMAND_CALLBACK_KOOFR}`;
|
||||
}
|
||||
try {
|
||||
const k = {
|
||||
code: authCode,
|
||||
grant_type: "authorization_code",
|
||||
client_id: KOOFR_CLIENT_ID ?? "",
|
||||
client_secret: KOOFR_CLIENT_SECRET ?? "",
|
||||
redirect_uri: callback,
|
||||
};
|
||||
// console.debug(k);
|
||||
const resp1 = await fetch(`${apiAddr}/oauth2/token`, {
|
||||
method: "POST",
|
||||
body: new URLSearchParams(k),
|
||||
});
|
||||
|
||||
if (resp1.status !== 200) {
|
||||
const resp2: AuthResFail = await resp1.json();
|
||||
throw Error(JSON.stringify(resp2));
|
||||
}
|
||||
|
||||
const resp2: AuthResSucc = await resp1.json();
|
||||
return resp2;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (errorCallBack !== undefined) {
|
||||
await errorCallBack(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sendRefreshTokenReq = async (
|
||||
apiAddr: string,
|
||||
refreshToken: string
|
||||
) => {
|
||||
console.debug(`refreshing token`);
|
||||
const x = await fetch(`${apiAddr}/oauth2/token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: KOOFR_CLIENT_ID ?? "",
|
||||
client_secret: KOOFR_CLIENT_SECRET ?? "",
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
}).toString(),
|
||||
});
|
||||
|
||||
if (x.status === 200) {
|
||||
const y: AuthResSucc = await x.json();
|
||||
console.debug(`new token obtained`);
|
||||
return y;
|
||||
} else {
|
||||
const y: AuthResFail = await x.json();
|
||||
throw Error(`cannot refresh an access token: ${JSON.stringify(y)}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const setConfigBySuccessfullAuthInplace = async (
|
||||
config: KoofrConfig,
|
||||
authRes: AuthResSucc,
|
||||
saveUpdatedConfigFunc: () => Promise<any> | undefined
|
||||
) => {
|
||||
if (authRes.access_token === undefined || authRes.access_token === "") {
|
||||
throw Error(
|
||||
`you should not save the setting for ${JSON.stringify(authRes)}`
|
||||
);
|
||||
}
|
||||
|
||||
config.accessToken = authRes.access_token;
|
||||
config.accessTokenExpiresAtTimeMs =
|
||||
Date.now() + authRes.expires_in * 1000 - 5 * 60 * 1000;
|
||||
config.accessTokenExpiresInMs = authRes.expires_in * 1000;
|
||||
config.refreshToken = authRes.refresh_token || config.refreshToken;
|
||||
|
||||
config.scope = authRes.scope || config.scope;
|
||||
|
||||
// manually set it expired after 60 days;
|
||||
config.credentialsShouldBeDeletedAtTimeMs =
|
||||
Date.now() + 1000 * 60 * 60 * 24 * 59;
|
||||
|
||||
await saveUpdatedConfigFunc?.();
|
||||
|
||||
console.info("finish updating local info of Koofr token");
|
||||
};
|
||||
|
||||
const getNormPathFromBasedir = (x: string, type: "dir" | "file") => {
|
||||
if (x === "/" || x === "") {
|
||||
throw Error(`do not know how to deal with path: ${x}`);
|
||||
}
|
||||
|
||||
if (!x.startsWith("/")) {
|
||||
throw Error(`path returned by koofr should starts with slash: ${x}`);
|
||||
}
|
||||
|
||||
if (type === "file") {
|
||||
return x.slice(1);
|
||||
} else if (type === "dir") {
|
||||
return `${x.slice(1)}/`;
|
||||
} else {
|
||||
throw Error(`do not know how to deal with path and type: ${x}, ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getKoofrPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||
let key = fileOrFolderPath;
|
||||
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
||||
// special
|
||||
key = `/${remoteBaseDir}`;
|
||||
} else if (fileOrFolderPath.startsWith("/")) {
|
||||
console.warn(
|
||||
`why the path ${fileOrFolderPath} starts with '/'? but we just go on.`
|
||||
);
|
||||
key = `/${remoteBaseDir}${fileOrFolderPath}`;
|
||||
} else {
|
||||
key = `/${remoteBaseDir}/${fileOrFolderPath}`;
|
||||
}
|
||||
if (key.endsWith("/")) {
|
||||
key = key.slice(0, key.length - 1);
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
const fromItemToEntity = (x: FilesListRecursiveItem): Entity => {
|
||||
if (x.type === "error") {
|
||||
throw Error(`cannot understand ${JSON.stringify(x)}`);
|
||||
}
|
||||
|
||||
const key = getNormPathFromBasedir(x.path ?? "/", x.file?.type as any);
|
||||
|
||||
if (x.file?.type === "dir" || x.file?.type === "file") {
|
||||
return {
|
||||
key: key,
|
||||
keyRaw: key,
|
||||
mtimeCli: x.file.modified,
|
||||
mtimeSvr: x.file.modified,
|
||||
size: x.file.size,
|
||||
sizeRaw: x.file.size,
|
||||
hash: x.file.hash,
|
||||
} as Entity;
|
||||
} else {
|
||||
throw Error(`cannot understand ${JSON.stringify(x)}`);
|
||||
}
|
||||
};
|
||||
|
||||
const fromFileToEntity = (x: FilesFile, parentPath: string): Entity => {
|
||||
const key = getNormPathFromBasedir(`${parentPath}/${x.name}`, x.type as any);
|
||||
if (x.type === "dir" || x.type === "file") {
|
||||
return {
|
||||
key: key,
|
||||
keyRaw: key,
|
||||
mtimeCli: x.modified,
|
||||
mtimeSvr: x.modified,
|
||||
size: x.size,
|
||||
sizeRaw: x.size,
|
||||
hash: x.hash,
|
||||
} as Entity;
|
||||
} else {
|
||||
throw Error(`cannot understand ${JSON.stringify(x)}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* https://app.koofr.net/developers
|
||||
*
|
||||
*/
|
||||
// const getAuthHeader = (email: string, password: string) => {
|
||||
// const x = `${email}:${password}`;
|
||||
// const y = base64.stringify(new TextEncoder().encode(x));
|
||||
// const z = `Basic ${y}`;
|
||||
// return z;
|
||||
// };
|
||||
|
||||
// const getAuthMiddleware = (email: string, password: string) => {
|
||||
// const authMiddleware: Middleware = {
|
||||
// async onRequest(req) {
|
||||
// req.headers.set("Authorization", getAuthHeader(email, password));
|
||||
// return req;
|
||||
// },
|
||||
// };
|
||||
// return authMiddleware;
|
||||
// };
|
||||
|
||||
const getAuthMiddleware = (
|
||||
koofrConfig: KoofrConfig,
|
||||
saveUpdatedConfigFunc: any
|
||||
) => {
|
||||
const authMiddleware: Middleware = {
|
||||
async onRequest(req) {
|
||||
const getAccessToken = async () => {
|
||||
if (koofrConfig.refreshToken === "") {
|
||||
throw Error("The user has not manually auth yet.");
|
||||
}
|
||||
|
||||
const ts = Date.now();
|
||||
const comp = koofrConfig.accessTokenExpiresAtTimeMs > ts;
|
||||
// console.debug(`koofrConfig.accessTokenExpiresAtTimeMs=${koofrConfig.accessTokenExpiresAtTimeMs},ts=${ts},comp=${comp}`)
|
||||
if (comp) {
|
||||
return koofrConfig.accessToken;
|
||||
}
|
||||
|
||||
// refresh
|
||||
const k = await sendRefreshTokenReq(
|
||||
koofrConfig.api,
|
||||
koofrConfig.refreshToken
|
||||
);
|
||||
koofrConfig.accessToken = k.access_token;
|
||||
koofrConfig.accessTokenExpiresInMs = k.expires_in * 1000;
|
||||
koofrConfig.accessTokenExpiresAtTimeMs =
|
||||
ts + k.expires_in * 1000 - 60 * 2 * 1000;
|
||||
await saveUpdatedConfigFunc();
|
||||
console.info("Koofr accessToken updated");
|
||||
return koofrConfig.accessToken;
|
||||
};
|
||||
|
||||
const access = await getAccessToken();
|
||||
req.headers.set("Authorization", `Bearer ${access}`);
|
||||
return req;
|
||||
},
|
||||
};
|
||||
return authMiddleware;
|
||||
};
|
||||
|
||||
export class FakeFsKoofr extends FakeFs {
|
||||
kind: string;
|
||||
koofrConfig: KoofrConfig;
|
||||
remoteBaseDir: string;
|
||||
vaultFolderExists: boolean;
|
||||
saveUpdatedConfigFunc: () => Promise<any>;
|
||||
client: ReturnType<typeof createClient<paths>>;
|
||||
placeID: string;
|
||||
|
||||
constructor(
|
||||
koofrConfig: KoofrConfig,
|
||||
vaultName: string,
|
||||
saveUpdatedConfigFunc: () => Promise<any>
|
||||
) {
|
||||
super();
|
||||
this.kind = "koofr";
|
||||
this.koofrConfig = koofrConfig;
|
||||
this.remoteBaseDir = this.koofrConfig.remoteBaseDir || vaultName || "";
|
||||
this.vaultFolderExists = false;
|
||||
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||
this.placeID = this.koofrConfig.mountID;
|
||||
const client = createClient<paths>({ baseUrl: this.koofrConfig.api });
|
||||
client.use(getAuthMiddleware(this.koofrConfig, saveUpdatedConfigFunc));
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async _init() {
|
||||
if (this.koofrConfig.refreshToken === "") {
|
||||
throw Error(`You have not auth yet!`);
|
||||
}
|
||||
|
||||
if (this.placeID === undefined || this.placeID === "") {
|
||||
const { data, error } = await this.client.GET("/api/v2.1/places");
|
||||
const primaryPlaceID = data?.places.filter((x) => x.isPrimary)[0].id;
|
||||
this.placeID = primaryPlaceID ?? "";
|
||||
|
||||
if (this.placeID === "") {
|
||||
throw Error(`cannot find primary placeID`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.vaultFolderExists) {
|
||||
// pass
|
||||
} else {
|
||||
const { data, error } = await this.client.GET(
|
||||
"/api/v2.1/mounts/{mountId}/files/list",
|
||||
{
|
||||
params: {
|
||||
query: {
|
||||
path: "/",
|
||||
},
|
||||
path: {
|
||||
mountId: this.placeID,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
const x = data?.files.filter((x) => x.name === this.remoteBaseDir);
|
||||
if ((x?.length ?? 0) > 0) {
|
||||
this.vaultFolderExists = true;
|
||||
} else {
|
||||
const { data, error } = await this.client.POST(
|
||||
"/api/v2.1/mounts/{mountId}/files/folder",
|
||||
{
|
||||
params: {
|
||||
query: { path: "/" },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
body: {
|
||||
name: this.remoteBaseDir,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (data !== undefined) {
|
||||
this.vaultFolderExists = true;
|
||||
} else {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async walk(): Promise<Entity[]> {
|
||||
await this._init();
|
||||
const { data, error } = await this.client.GET(
|
||||
"/content/api/v2.1/mounts/{mountId}/files/listrecursive",
|
||||
{
|
||||
params: {
|
||||
query: { path: this.remoteBaseDir },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
parseAs: "text",
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
const items = JSON.parse(
|
||||
`[${data.trim().split("\n").join(",")}]`
|
||||
) as FilesListRecursiveItem[];
|
||||
const entities = items.filter((x) => x.path !== "/").map(fromItemToEntity);
|
||||
return entities;
|
||||
}
|
||||
|
||||
async walkPartial(): Promise<Entity[]> {
|
||||
await this._init();
|
||||
|
||||
const { data, error } = await this.client.GET(
|
||||
"/api/v2.1/mounts/{mountId}/files/list",
|
||||
{
|
||||
params: {
|
||||
query: { path: this.remoteBaseDir },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
const entities = data.files.map((x) => fromFileToEntity(x, ""));
|
||||
// console.debug(entities);
|
||||
return entities;
|
||||
}
|
||||
|
||||
async stat(key: string): Promise<Entity> {
|
||||
await this._init();
|
||||
const { data, error } = await this.client.GET(
|
||||
"/api/v2.1/mounts/{mountId}/files/info",
|
||||
{
|
||||
params: {
|
||||
query: { path: getKoofrPath(key, this.remoteBaseDir) },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
const entity = fromFileToEntity(
|
||||
data,
|
||||
getKoofrPath(getParentFolder(key), this.remoteBaseDir)
|
||||
);
|
||||
// console.debug(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async mkdir(
|
||||
key: string,
|
||||
mtime?: number | undefined,
|
||||
ctime?: number | undefined
|
||||
): Promise<Entity> {
|
||||
await this._init();
|
||||
|
||||
// "abc/efg" -> "abc/"
|
||||
const parent = getParentFolder(key);
|
||||
const itself = key.slice(0, -1).split("/").pop()!;
|
||||
const { data, error } = await this.client.POST(
|
||||
"/api/v2.1/mounts/{mountId}/files/folder",
|
||||
{
|
||||
params: {
|
||||
query: { path: getKoofrPath(parent, this.remoteBaseDir) },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
body: {
|
||||
name: itself,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
return this.stat(key);
|
||||
}
|
||||
|
||||
async writeFile(
|
||||
key: string,
|
||||
content: ArrayBuffer,
|
||||
mtime: number,
|
||||
ctime: number
|
||||
): Promise<Entity> {
|
||||
await this._init();
|
||||
const itself = key.split("/").pop()!;
|
||||
const { data, error } = await this.client.POST(
|
||||
"/content/api/v2.1/mounts/{mountId}/files/put",
|
||||
{
|
||||
params: {
|
||||
query: {
|
||||
path: getKoofrPath(getParentFolder(key), this.remoteBaseDir),
|
||||
filename: itself,
|
||||
info: true,
|
||||
overwrite: true,
|
||||
autorename: false,
|
||||
modified: mtime,
|
||||
},
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
|
||||
body: content as any,
|
||||
bodySerializer(body) {
|
||||
return body;
|
||||
},
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
const entity = fromFileToEntity(
|
||||
data,
|
||||
getKoofrPath(getParentFolder(key), this.remoteBaseDir)
|
||||
);
|
||||
// console.debug(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async readFile(key: string): Promise<ArrayBuffer> {
|
||||
await this._init();
|
||||
const { data, error } = await this.client.GET(
|
||||
"/content/api/v2.1/mounts/{mountId}/files/get",
|
||||
{
|
||||
params: {
|
||||
query: { path: getKoofrPath(key, this.remoteBaseDir), force: true },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
parseAs: "arrayBuffer",
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
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 { data, error } = await this.client.DELETE(
|
||||
"/api/v2.1/mounts/{mountId}/files/remove",
|
||||
{
|
||||
params: {
|
||||
query: { path: getKoofrPath(key, this.remoteBaseDir), force: true },
|
||||
path: { mountId: this.placeID },
|
||||
},
|
||||
}
|
||||
);
|
||||
if (error !== undefined) {
|
||||
throw Error(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async revokeAuth(): Promise<any> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
allowEmptyFile(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Vendored
+9953
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,11 @@
|
||||
"protocol_yandexdisk_connect_fail": "Something went wrong from response from Yandex Disk website. Maybe the network connection is not good. Maybe you rejected the auth?",
|
||||
"protocol_yandexdisk_connect_succ_revoke": "You've connected. If you want to disconnect, click this button.",
|
||||
|
||||
"protocol_koofr_connecting": "Connectting",
|
||||
"protocol_koofr_connect_manualinput_succ": "You've connected",
|
||||
"protocol_koofr_connect_fail": "Something went wrong from response from Koofr website. Maybe the network connection is not good. Maybe you rejected the auth?",
|
||||
"protocol_koofr_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_copybutton": "Click to copy the auth url",
|
||||
"modal_googledriveauth_copynotice": "The auth url is copied to the clipboard!",
|
||||
@@ -76,6 +81,17 @@
|
||||
"modal_yandexdiskrevokeauth_clean_notice": "Cleaned!",
|
||||
"modal_yandexdiskrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
||||
|
||||
"modal_koofrauth_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_koofrauth_copybutton": "Click to copy the auth url",
|
||||
"modal_koofrauth_copynotice": "The auth url is copied to the clipboard!",
|
||||
"modal_koofrrevokeauth_step1": "Step 1: Go to the following address, you can remove the connection there.",
|
||||
"modal_koofrrevokeauth_step2": "Step 2: Click the button below, to clean the locally-saved login credentials.",
|
||||
"modal_koofrrevokeauth_clean": "Clean Locally-Saved Login Credentials",
|
||||
"modal_koofrrevokeauth_clean_desc": "You need to click the button.",
|
||||
"modal_koofrrevokeauth_clean_button": "Clean",
|
||||
"modal_koofrrevokeauth_clean_notice": "Cleaned!",
|
||||
"modal_koofrrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
||||
|
||||
"modal_prorevokeauth": "Revoke auth by clicking here and follow the steps.",
|
||||
"modal_prorevokeauth_clean": "Clean",
|
||||
"modal_prorevokeauth_clean_desc": "Clean local auth record",
|
||||
@@ -165,10 +181,29 @@
|
||||
"settings_yandexdisk_connect_succ": "Great! We can connect to Yandex Disk!",
|
||||
"settings_yandexdisk_connect_fail": "We cannot connect to Yandex Disk.",
|
||||
|
||||
"settings_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_chooseservice_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_koofr_disclaimer1": "Disclaimer: This app is NOT an official Koofr product. The app just uses Koofr's public api.",
|
||||
"settings_koofr_disclaimer2": "Disclaimer: The information is stored locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Koofr, please immediately disconnect this app on https://app.koofr.net/app/admin/preferences/security .",
|
||||
"settings_koofr_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_koofr_notshowuphint": "Koofr Settings Not Available",
|
||||
"settings_koofr_notshowuphint_desc": "Koofr settings are not available, because you haven't subscribed to the PRO feature in your Remotely Save account.",
|
||||
"settings_koofr_notshowuphint_view_pro": "View PRO Settings",
|
||||
"settings_koofr_folder": "We will create and sync inside the folder {{remoteBaseDir}} on your Koofr. DO NOT create this folder by yourself manually.",
|
||||
"settings_koofr_revoke": "Revoke Auth",
|
||||
"settings_koofr_revoke_desc": "You've connected. If you want to disconnect, click this button.",
|
||||
"settings_koofr_revoke_button": "Revoke Auth",
|
||||
"settings_koofr_auth": "Auth",
|
||||
"settings_koofr_auth_desc": "Auth.",
|
||||
"settings_koofr_auth_button": "Auth",
|
||||
"settings_koofr_connect_succ": "Great! We can connect to Koofr!",
|
||||
"settings_koofr_connect_fail": "We cannot connect to Koofr.",
|
||||
|
||||
"settings_export_googledrive_button": "Export Google Drive Part",
|
||||
"settings_export_box_button": "Export Box Part",
|
||||
"settings_export_pcloud_button": "Export pCloud Part",
|
||||
"settings_export_yandexdisk_button": "Export Yandex Disk Part",
|
||||
"settings_export_koofr_button": "Export Koofr Part",
|
||||
|
||||
"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.",
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"protocol_yandexdisk_connect_fail": "Yandex Disk 官网返回错误。可能是网络连接不稳定。也可能是您拒绝了授权?",
|
||||
"protocol_yandexdisk_connect_succ_revoke": "您已连接上账号。如果要取消连接,请点击此按钮。",
|
||||
|
||||
"protocol_koofr_connecting": "正在连接",
|
||||
"protocol_koofr_connect_manualinput_succ": "连接成功",
|
||||
"protocol_koofr_connect_fail": "Yandex Disk 官网返回错误。可能是网络连接不稳定。也可能是您拒绝了授权?",
|
||||
"protocol_koofr_connect_succ_revoke": "您已连接上账号。如果要取消连接,请点击此按钮。",
|
||||
|
||||
"modal_googledriveauth_tutorial": "<p>请访问此网址,然后会进入授权流程。最后,您会看到一个码,请复制粘贴到这里然后提交。</p>",
|
||||
"modal_googledriveauth_copybutton": "点击以复制网址",
|
||||
"modal_googledriveauth_copynotice": "网址已复制!",
|
||||
@@ -81,6 +86,22 @@
|
||||
"modal_yandexdiskrevokeauth_clean_notice": "已清理!",
|
||||
"modal_yandexdiskrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
||||
|
||||
"modal_koofrauth_tutorial": "<p>请访问此网址,然后会进入授权流程。最后,您会被重定向回来。</p>",
|
||||
"modal_koofrauth_copybutton": "点击以复制网址",
|
||||
"modal_koofrauth_copynotice": "网址已复制!",
|
||||
"modal_koofr_maualinput": "网站上的码",
|
||||
"modal_koofr_maualinput_desc": "请粘贴授权流程最后的那个码,然后点击确认。",
|
||||
"modal_koofr_maualinput_notice": "正在尝试连接 Yandex Disk 并更新授权信息......",
|
||||
"modal_koofr_maualinput_succ_notice": "很好!授权信息已更新!",
|
||||
"modal_koofr_maualinput_fail_notice": "更新授权信息失败。请稍后重试。",
|
||||
"modal_koofrrevokeauth_step1": "第 1 步:访问以下网址,可以删除连接。",
|
||||
"modal_koofrrevokeauth_step2": "第 2 步:点击以下按钮,从而清理本地的登录信息。",
|
||||
"modal_koofrrevokeauth_clean": "清理本地登录信息",
|
||||
"modal_koofrrevokeauth_clean_desc": "您需要点击此按钮。",
|
||||
"modal_koofrrevokeauth_clean_button": "清理",
|
||||
"modal_koofrrevokeauth_clean_notice": "已清理!",
|
||||
"modal_koofrrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
||||
|
||||
"modal_prorevokeauth": "点击这里和按照步骤取消授权。",
|
||||
"modal_prorevokeauth_clean": "清理",
|
||||
"modal_prorevokeauth_clean_desc": "清理本地授权记录",
|
||||
@@ -170,10 +191,29 @@
|
||||
"settings_yandexdisk_connect_succ": "很好!我们可连接上 Yandex Disk!",
|
||||
"settings_yandexdisk_connect_fail": "我们未能连接上 Yandex Disk。",
|
||||
|
||||
"settings_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_chooseservice_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_koofr_disclaimer1": "声明:本插件不是 Koofr 的官方产品。只是用到了它的公开 API。",
|
||||
"settings_koofr_disclaimer2": "声明:您所输入的信息存储于本地。其它有害的或者出错的插件,是有可能读取到这些信息的。如果您发现任何不符合预期的 Koofr 访问,请立刻在以下网站操作断开连接: https://app.koofr.net/app/admin/preferences/security 。",
|
||||
"settings_koofr_pro_desc": "<p><strong>!!这是 PRO(付费)功能! 您需要在线账号来使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 账号的更多信息。)</p>",
|
||||
"settings_koofr_notshowuphint": "Koofr 设置不可用",
|
||||
"settings_koofr_notshowuphint_desc": "Koofr 设置不可用,因为您没有在 Remotely Save 账号里开启这个 PRO 功能。",
|
||||
"settings_koofr_notshowuphint_view_pro": "查看 PRO 相关设置",
|
||||
"settings_koofr_folder": "我们会在 Koofr 创建此文件夹并同步内容进去: {{remoteBaseDir}} 。请不要手动在网站上创建。",
|
||||
"settings_koofr_revoke": "撤回鉴权",
|
||||
"settings_koofr_revoke_desc": "您现在已连接。如果想取消连接,请点击此按钮。",
|
||||
"settings_koofr_revoke_button": "撤回鉴权",
|
||||
"settings_koofr_auth": "鉴权",
|
||||
"settings_koofr_auth_desc": "鉴权.",
|
||||
"settings_koofr_auth_button": "鉴权",
|
||||
"settings_koofr_connect_succ": "很好!我们可连接上 Koofr!",
|
||||
"settings_koofr_connect_fail": "我们未能连接上 Koofr。",
|
||||
|
||||
"settings_export_googledrive_button": "导出 Google Drive 部分",
|
||||
"settings_export_box_button": "导出 Box 部分",
|
||||
"settings_export_pcloud_button": "导出 pCloud 部分",
|
||||
"settings_export_yandexdisk_button": "导出 Yandex Disk 部分",
|
||||
"settings_export_koofr_button": "导出 Koofr 部分",
|
||||
|
||||
"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>第二部:点击“连接”按钮,从而连接本设备和在线账号。",
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"protocol_yandexdisk_connect_fail": "Yandex Disk 官網返回錯誤。可能是網路連線不穩定。也可能是您拒絕了授權?",
|
||||
"protocol_yandexdisk_connect_succ_revoke": "您已連線上賬號。如果要取消連線,請點選此按鈕。",
|
||||
|
||||
"protocol_koofr_connecting": "正在連線",
|
||||
"protocol_koofr_connect_manualinput_succ": "連線成功",
|
||||
"protocol_koofr_connect_fail": "Yandex Disk 官網返回錯誤。可能是網路連線不穩定。也可能是您拒絕了授權?",
|
||||
"protocol_koofr_connect_succ_revoke": "您已連線上賬號。如果要取消連線,請點選此按鈕。",
|
||||
|
||||
"modal_googledriveauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會看到一個碼,請複製貼上到這裡然後提交。</p>",
|
||||
"modal_googledriveauth_copybutton": "點選以複製網址",
|
||||
"modal_googledriveauth_copynotice": "網址已複製!",
|
||||
@@ -81,6 +86,22 @@
|
||||
"modal_yandexdiskrevokeauth_clean_notice": "已清理!",
|
||||
"modal_yandexdiskrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||
|
||||
"modal_koofrauth_tutorial": "<p>請訪問此網址,然後會進入授權流程。最後,您會被重定向回來。</p>",
|
||||
"modal_koofrauth_copybutton": "點選以複製網址",
|
||||
"modal_koofrauth_copynotice": "網址已複製!",
|
||||
"modal_koofr_maualinput": "網站上的碼",
|
||||
"modal_koofr_maualinput_desc": "請貼上授權流程最後的那個碼,然後點選確認。",
|
||||
"modal_koofr_maualinput_notice": "正在嘗試連線 Yandex Disk 並更新授權資訊......",
|
||||
"modal_koofr_maualinput_succ_notice": "很好!授權資訊已更新!",
|
||||
"modal_koofr_maualinput_fail_notice": "更新授權資訊失敗。請稍後重試。",
|
||||
"modal_koofrrevokeauth_step1": "第 1 步:訪問以下網址,可以刪除連線。",
|
||||
"modal_koofrrevokeauth_step2": "第 2 步:點選以下按鈕,從而清理本地的登入資訊。",
|
||||
"modal_koofrrevokeauth_clean": "清理本地登入資訊",
|
||||
"modal_koofrrevokeauth_clean_desc": "您需要點選此按鈕。",
|
||||
"modal_koofrrevokeauth_clean_button": "清理",
|
||||
"modal_koofrrevokeauth_clean_notice": "已清理!",
|
||||
"modal_koofrrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||
|
||||
"modal_prorevokeauth": "點選這裡和按照步驟取消授權。",
|
||||
"modal_prorevokeauth_clean": "清理",
|
||||
"modal_prorevokeauth_clean_desc": "清理本地授權記錄",
|
||||
@@ -170,10 +191,29 @@
|
||||
"settings_yandexdisk_connect_succ": "很好!我們可連線上 Yandex Disk!",
|
||||
"settings_yandexdisk_connect_fail": "我們未能連線上 Yandex Disk。",
|
||||
|
||||
"settings_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_chooseservice_koofr": "Koofr (PRO) (beta)",
|
||||
"settings_koofr_disclaimer1": "宣告:本外掛不是 Koofr 的官方產品。只是用到了它的公開 API。",
|
||||
"settings_koofr_disclaimer2": "宣告:您所輸入的資訊儲存於本地。其它有害的或者出錯的外掛,是有可能讀取到這些資訊的。如果您發現任何不符合預期的 Koofr 訪問,請立刻在以下網站操作斷開連線: https://app.koofr.net/app/admin/preferences/security 。",
|
||||
"settings_koofr_pro_desc": "<p><strong>!!這是 PRO(付費)功能! 您需要線上賬號來使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 賬號的更多資訊。)</p>",
|
||||
"settings_koofr_notshowuphint": "Koofr 設定不可用",
|
||||
"settings_koofr_notshowuphint_desc": "Koofr 設定不可用,因為您沒有在 Remotely Save 賬號裡開啟這個 PRO 功能。",
|
||||
"settings_koofr_notshowuphint_view_pro": "檢視 PRO 相關設定",
|
||||
"settings_koofr_folder": "我們會在 Koofr 建立此資料夾並同步內容進去: {{remoteBaseDir}} 。請不要手動在網站上建立。",
|
||||
"settings_koofr_revoke": "撤回鑑權",
|
||||
"settings_koofr_revoke_desc": "您現在已連線。如果想取消連線,請點選此按鈕。",
|
||||
"settings_koofr_revoke_button": "撤回鑑權",
|
||||
"settings_koofr_auth": "鑑權",
|
||||
"settings_koofr_auth_desc": "鑑權.",
|
||||
"settings_koofr_auth_button": "鑑權",
|
||||
"settings_koofr_connect_succ": "很好!我們可連線上 Koofr!",
|
||||
"settings_koofr_connect_fail": "我們未能連線上 Koofr。",
|
||||
|
||||
"settings_export_googledrive_button": "匯出 Google Drive 部分",
|
||||
"settings_export_box_button": "匯出 Box 部分",
|
||||
"settings_export_pcloud_button": "匯出 pCloud 部分",
|
||||
"settings_export_yandexdisk_button": "匯出 Yandex Disk 部分",
|
||||
"settings_export_koofr_button": "匯出 Koofr 部分",
|
||||
|
||||
"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>第二部:點選“連線”按鈕,從而連線本裝置和線上賬號。",
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
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_KOOFR_CONFIG,
|
||||
generateAuthUrl,
|
||||
sendRefreshTokenReq,
|
||||
} from "./fsKoofr";
|
||||
|
||||
class KoofrAuthModal 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 = generateAuthUrl(this.plugin.settings.koofr.api, true);
|
||||
const div2 = contentEl.createDiv();
|
||||
div2.createDiv({
|
||||
text: stringToFragment(t("modal_koofrauth_tutorial")),
|
||||
});
|
||||
div2.createEl(
|
||||
"button",
|
||||
{
|
||||
text: t("modal_koofrauth_copybutton"),
|
||||
},
|
||||
(el) => {
|
||||
el.onclick = async () => {
|
||||
await navigator.clipboard.writeText(authUrl);
|
||||
new Notice(t("modal_koofrauth_copynotice"));
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
contentEl.createEl("p").createEl("a", {
|
||||
href: authUrl,
|
||||
text: authUrl,
|
||||
});
|
||||
|
||||
// let refreshToken = "";
|
||||
// new Setting(contentEl)
|
||||
// .setName(t("modal_koofr_maualinput"))
|
||||
// .setDesc(t("modal_koofr_maualinput_desc"))
|
||||
// .addText((text) =>
|
||||
// text
|
||||
// .setPlaceholder("")
|
||||
// .setValue("")
|
||||
// .onChange((val) => {
|
||||
// refreshToken = val.trim();
|
||||
// })
|
||||
// )
|
||||
// .addButton(async (button) => {
|
||||
// button.setButtonText(t("submit"));
|
||||
// button.onClick(async () => {
|
||||
// new Notice(t("modal_koofr_maualinput_notice"));
|
||||
|
||||
// try {
|
||||
// if (this.plugin.settings.koofr === undefined) {
|
||||
// this.plugin.settings.koofr = cloneDeep(
|
||||
// DEFAULT_KOOFR_CONFIG
|
||||
// );
|
||||
// }
|
||||
// this.plugin.settings.koofr.refreshToken = refreshToken;
|
||||
// this.plugin.settings.koofr.accessToken = "access";
|
||||
// this.plugin.settings.koofr.accessTokenExpiresAtTimeMs = 1;
|
||||
// this.plugin.settings.koofr.accessTokenExpiresInMs = 1;
|
||||
|
||||
// // TODO: abstraction leaking now, how to fix?
|
||||
// const k = await sendRefreshTokenReq(refreshToken);
|
||||
// const ts = Date.now();
|
||||
// this.plugin.settings.koofr.accessToken = k.access_token;
|
||||
// this.plugin.settings.koofr.accessTokenExpiresInMs =
|
||||
// k.expires_in * 1000;
|
||||
// this.plugin.settings.koofr.accessTokenExpiresAtTimeMs =
|
||||
// ts + k.expires_in * 1000 - 60 * 2 * 1000;
|
||||
// await this.plugin.saveSettings();
|
||||
|
||||
// // try to remove data in clipboard
|
||||
// await navigator.clipboard.writeText("");
|
||||
|
||||
// new Notice(t("modal_koofr_maualinput_succ_notice"));
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// new Notice(t("modal_koofr_maualinput_fail_notice"));
|
||||
// } finally {
|
||||
// this.authDiv.toggleClass(
|
||||
// "koofr-auth-button-hide",
|
||||
// this.plugin.settings.koofr.refreshToken !== ""
|
||||
// );
|
||||
// this.revokeAuthDiv.toggleClass(
|
||||
// "koofr-revoke-auth-button-hide",
|
||||
// this.plugin.settings.koofr.refreshToken === ""
|
||||
// );
|
||||
// this.close();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class KoofrRevokeAuthModal 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_koofrrevokeauth_step1"),
|
||||
});
|
||||
const consentUrl = "https://app.koofr.net/app/admin/preferences/security";
|
||||
contentEl.createEl("p").createEl("a", {
|
||||
href: consentUrl,
|
||||
text: consentUrl,
|
||||
});
|
||||
|
||||
contentEl.createEl("p", {
|
||||
text: t("modal_koofrrevokeauth_step2"),
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("modal_koofrrevokeauth_clean"))
|
||||
.setDesc(t("modal_koofrrevokeauth_clean_desc"))
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText(t("modal_koofrrevokeauth_clean_button"));
|
||||
button.onClick(async () => {
|
||||
try {
|
||||
this.plugin.settings.koofr = cloneDeep(DEFAULT_KOOFR_CONFIG);
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
this.authDiv.toggleClass(
|
||||
"koofr-auth-button-hide",
|
||||
this.plugin.settings.koofr.refreshToken !== ""
|
||||
);
|
||||
this.revokeAuthDiv.toggleClass(
|
||||
"koofr-revoke-auth-button-hide",
|
||||
this.plugin.settings.koofr.refreshToken === ""
|
||||
);
|
||||
new Notice(t("modal_koofrrevokeauth_clean_notice"));
|
||||
this.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
new Notice(t("modal_koofrrevokeauth_clean_fail"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
export const generateKoofrSettingsPart = (
|
||||
containerEl: HTMLElement,
|
||||
t: (x: TransItemType, vars?: any) => string,
|
||||
app: App,
|
||||
plugin: RemotelySavePlugin,
|
||||
saveUpdatedConfigFunc: () => Promise<any> | undefined
|
||||
) => {
|
||||
const koofrDiv = containerEl.createEl("div", {
|
||||
cls: "koofr-hide",
|
||||
});
|
||||
koofrDiv.toggleClass("koofr-hide", plugin.settings.serviceType !== "koofr");
|
||||
koofrDiv.createEl("h2", { text: t("settings_koofr") });
|
||||
|
||||
const koofrLongDescDiv = koofrDiv.createEl("div", {
|
||||
cls: "settings-long-desc",
|
||||
});
|
||||
for (const c of [
|
||||
t("settings_koofr_disclaimer1"),
|
||||
t("settings_koofr_disclaimer2"),
|
||||
]) {
|
||||
koofrLongDescDiv.createEl("p", {
|
||||
text: c,
|
||||
cls: "koofr-disclaimer",
|
||||
});
|
||||
}
|
||||
|
||||
koofrLongDescDiv.createEl("p", {
|
||||
text: t("settings_koofr_folder", {
|
||||
remoteBaseDir: plugin.settings.koofr.remoteBaseDir || app.vault.getName(),
|
||||
}),
|
||||
});
|
||||
|
||||
koofrLongDescDiv.createDiv({
|
||||
text: stringToFragment(t("settings_koofr_pro_desc")),
|
||||
cls: "koofr-disclaimer",
|
||||
});
|
||||
|
||||
const koofrNotShowUpHintSetting = new Setting(koofrDiv)
|
||||
.setName(t("settings_koofr_notshowuphint"))
|
||||
.setDesc(t("settings_koofr_notshowuphint_desc"))
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText(t("settings_koofr_notshowuphint_view_pro"));
|
||||
button.onClick(async () => {
|
||||
window.location.href = "#settings-pro";
|
||||
});
|
||||
});
|
||||
|
||||
const koofrAllowedToUsedDiv = koofrDiv.createDiv();
|
||||
// if pro enabled, show up; otherwise hide.
|
||||
const allowKoofr =
|
||||
plugin.settings.pro?.enabledProFeatures.filter(
|
||||
(x) => x.featureName === "feature-koofr"
|
||||
).length === 1;
|
||||
console.debug(`allow to show up koofr settings? ${allowKoofr}`);
|
||||
if (allowKoofr) {
|
||||
koofrAllowedToUsedDiv.removeClass("koofr-allow-to-use-hide");
|
||||
koofrNotShowUpHintSetting.settingEl.addClass("koofr-allow-to-use-hide");
|
||||
} else {
|
||||
koofrAllowedToUsedDiv.addClass("koofr-allow-to-use-hide");
|
||||
koofrNotShowUpHintSetting.settingEl.removeClass("koofr-allow-to-use-hide");
|
||||
}
|
||||
|
||||
const koofrSelectAuthDiv = koofrAllowedToUsedDiv.createDiv();
|
||||
const koofrAuthDiv = koofrSelectAuthDiv.createDiv({
|
||||
cls: "koofr-auth-button-hide settings-auth-related",
|
||||
});
|
||||
const koofrRevokeAuthDiv = koofrSelectAuthDiv.createDiv({
|
||||
cls: "koofr-revoke-auth-button-hide settings-auth-related",
|
||||
});
|
||||
|
||||
const koofrRevokeAuthSetting = new Setting(koofrRevokeAuthDiv)
|
||||
.setName(t("settings_koofr_revoke"))
|
||||
.setDesc(t("settings_koofr_revoke_desc"))
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText(t("settings_koofr_revoke_button"));
|
||||
button.onClick(async () => {
|
||||
new KoofrRevokeAuthModal(
|
||||
app,
|
||||
plugin,
|
||||
koofrAuthDiv,
|
||||
koofrRevokeAuthDiv,
|
||||
t
|
||||
).open();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(koofrAuthDiv)
|
||||
.setName(t("settings_koofr_auth"))
|
||||
.setDesc(t("settings_koofr_auth_desc"))
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText(t("settings_koofr_auth_button"));
|
||||
button.onClick(async () => {
|
||||
const modal = new KoofrAuthModal(
|
||||
app,
|
||||
plugin,
|
||||
koofrAuthDiv,
|
||||
koofrRevokeAuthDiv,
|
||||
koofrRevokeAuthSetting,
|
||||
t
|
||||
);
|
||||
plugin.oauth2Info.helperModal = modal;
|
||||
plugin.oauth2Info.authDiv = koofrAuthDiv;
|
||||
plugin.oauth2Info.revokeDiv = koofrRevokeAuthDiv;
|
||||
plugin.oauth2Info.revokeAuthSetting = koofrRevokeAuthSetting;
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
koofrAuthDiv.toggleClass(
|
||||
"koofr-auth-button-hide",
|
||||
plugin.settings.koofr.refreshToken !== ""
|
||||
);
|
||||
koofrRevokeAuthDiv.toggleClass(
|
||||
"koofr-revoke-auth-button-hide",
|
||||
plugin.settings.koofr.refreshToken === ""
|
||||
);
|
||||
|
||||
let newkoofrRemoteBaseDir = plugin.settings.koofr.remoteBaseDir || "";
|
||||
new Setting(koofrAllowedToUsedDiv)
|
||||
.setName(t("settings_remotebasedir"))
|
||||
.setDesc(t("settings_remotebasedir_desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(app.vault.getName())
|
||||
.setValue(newkoofrRemoteBaseDir)
|
||||
.onChange((value) => {
|
||||
newkoofrRemoteBaseDir = value.trim();
|
||||
})
|
||||
)
|
||||
.addButton((button) => {
|
||||
button.setButtonText(t("confirm"));
|
||||
button.onClick(() => {
|
||||
new ChangeRemoteBaseDirModal(
|
||||
app,
|
||||
plugin,
|
||||
newkoofrRemoteBaseDir,
|
||||
"koofr"
|
||||
).open();
|
||||
});
|
||||
});
|
||||
new Setting(koofrAllowedToUsedDiv)
|
||||
.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_koofr_connect_succ"));
|
||||
} else {
|
||||
new Notice(t("settings_koofr_connect_fail"));
|
||||
new Notice(errors.msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
koofrDiv: koofrDiv,
|
||||
koofrAllowedToUsedDiv: koofrAllowedToUsedDiv,
|
||||
koofrNotShowUpHintSetting: koofrNotShowUpHintSetting,
|
||||
};
|
||||
};
|
||||
+18
-1
@@ -255,7 +255,9 @@ export const generateProSettingsPart = (
|
||||
pCloudAllowedToUsedDiv: HTMLDivElement,
|
||||
pCloudNotShowUpHintSetting: Setting,
|
||||
yandexDiskAllowedToUsedDiv: HTMLDivElement,
|
||||
yandexDiskNotShowUpHintSetting: Setting
|
||||
yandexDiskNotShowUpHintSetting: Setting,
|
||||
koofrAllowedToUsedDiv: HTMLDivElement,
|
||||
koofrNotShowUpHintSetting: Setting
|
||||
) => {
|
||||
proDiv
|
||||
.createEl("h2", { text: t("settings_pro") })
|
||||
@@ -372,6 +374,21 @@ export const generateProSettingsPart = (
|
||||
);
|
||||
}
|
||||
|
||||
const allowKoofr =
|
||||
plugin.settings.pro?.enabledProFeatures.filter(
|
||||
(x) => x.featureName === "feature-koofr"
|
||||
).length === 1;
|
||||
console.debug(`allow to show up Koofr settings? ${allowKoofr}`);
|
||||
if (allowKoofr) {
|
||||
koofrAllowedToUsedDiv.removeClass("koofr-allow-to-use-hide");
|
||||
koofrNotShowUpHintSetting.settingEl.addClass("koofr-allow-to-use-hide");
|
||||
} else {
|
||||
koofrAllowedToUsedDiv.addClass("koofr-allow-to-use-hide");
|
||||
koofrNotShowUpHintSetting.settingEl.removeClass(
|
||||
"koofr-allow-to-use-hide"
|
||||
);
|
||||
}
|
||||
|
||||
new Notice(t("settings_pro_features_refresh_succ"));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user