azblob
This commit is contained in:
@@ -290,6 +290,11 @@ export const checkProRunnableAndFixInplace = async (
|
||||
service: "koofr",
|
||||
name: "Koofr",
|
||||
},
|
||||
{
|
||||
feature: "feature-azure_blob_storage",
|
||||
service: "azureblobstorage",
|
||||
name: "Azure Blob Storage",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { feature, service, name } of toChecked) {
|
||||
|
||||
+47
-16
@@ -1,10 +1,29 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
// hacks
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
declare global {
|
||||
var DEFAULT_REMOTELYSAVE_WEBSITE: string;
|
||||
var DEFAULT_REMOTELYSAVE_CLIENT_ID: string;
|
||||
var DEFAULT_GOOGLEDRIVE_CLIENT_ID: string;
|
||||
var DEFAULT_GOOGLEDRIVE_CLIENT_SECRET: string;
|
||||
var DEFAULT_BOX_CLIENT_ID: string;
|
||||
var DEFAULT_BOX_CLIENT_SECRET: string;
|
||||
var DEFAULT_PCLOUD_CLIENT_ID: string;
|
||||
var DEFAULT_PCLOUD_CLIENT_SECRET: string;
|
||||
var DEFAULT_YANDEXDISK_CLIENT_ID: string;
|
||||
var DEFAULT_YANDEXDISK_CLIENT_SECRET: string;
|
||||
var DEFAULT_KOOFR_CLIENT_ID: string;
|
||||
var DEFAULT_KOOFR_CLIENT_SECRET: string;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// PRO
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
export const COMMAND_CALLBACK_PRO = "remotely-save-cb-pro";
|
||||
export const PRO_CLIENT_ID = process.env.DEFAULT_REMOTELYSAVE_CLIENT_ID;
|
||||
export const PRO_WEBSITE = process.env.DEFAULT_REMOTELYSAVE_WEBSITE;
|
||||
export const PRO_CLIENT_ID = global.DEFAULT_REMOTELYSAVE_CLIENT_ID;
|
||||
export const PRO_WEBSITE = global.DEFAULT_REMOTELYSAVE_WEBSITE;
|
||||
|
||||
export type PRO_FEATURE_TYPE =
|
||||
| "feature-smart_conflict"
|
||||
@@ -12,7 +31,8 @@ export type PRO_FEATURE_TYPE =
|
||||
| "feature-box"
|
||||
| "feature-pcloud"
|
||||
| "feature-yandex_disk"
|
||||
| "feature-koofr";
|
||||
| "feature-koofr"
|
||||
| "feature-azure_blob_storage";
|
||||
|
||||
export interface FeatureInfo {
|
||||
featureName: PRO_FEATURE_TYPE;
|
||||
@@ -51,18 +71,17 @@ export interface GoogleDriveConfig {
|
||||
kind: "googledrive";
|
||||
}
|
||||
|
||||
export const DEFAULT_GOOGLEDRIVE_CLIENT_ID =
|
||||
process.env.DEFAULT_GOOGLEDRIVE_CLIENT_ID;
|
||||
export const DEFAULT_GOOGLEDRIVE_CLIENT_SECRET =
|
||||
process.env.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET;
|
||||
export const GOOGLEDRIVE_CLIENT_ID = global.DEFAULT_GOOGLEDRIVE_CLIENT_ID;
|
||||
export const GOOGLEDRIVE_CLIENT_SECRET =
|
||||
global.DEFAULT_GOOGLEDRIVE_CLIENT_SECRET;
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// box
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
export const COMMAND_CALLBACK_BOX = "remotely-save-cb-box";
|
||||
export const BOX_CLIENT_ID = process.env.DEFAULT_BOX_CLIENT_ID;
|
||||
export const BOX_CLIENT_SECRET = process.env.DEFAULT_BOX_CLIENT_SECRET;
|
||||
export const BOX_CLIENT_ID = global.DEFAULT_BOX_CLIENT_ID;
|
||||
export const BOX_CLIENT_SECRET = global.DEFAULT_BOX_CLIENT_SECRET;
|
||||
|
||||
export interface BoxConfig {
|
||||
accessToken: string;
|
||||
@@ -79,8 +98,8 @@ export interface BoxConfig {
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
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 const PCLOUD_CLIENT_ID = global.DEFAULT_PCLOUD_CLIENT_ID;
|
||||
export const PCLOUD_CLIENT_SECRET = global.DEFAULT_PCLOUD_CLIENT_SECRET;
|
||||
|
||||
export interface PCloudConfig {
|
||||
accessToken: string;
|
||||
@@ -101,9 +120,8 @@ export interface PCloudConfig {
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
export const COMMAND_CALLBACK_YANDEXDISK = "remotely-save-cb-yandexdisk";
|
||||
export const YANDEXDISK_CLIENT_ID = process.env.DEFAULT_YANDEXDISK_CLIENT_ID;
|
||||
export const YANDEXDISK_CLIENT_SECRET =
|
||||
process.env.DEFAULT_YANDEXDISK_CLIENT_SECRET;
|
||||
export const YANDEXDISK_CLIENT_ID = global.DEFAULT_YANDEXDISK_CLIENT_ID;
|
||||
export const YANDEXDISK_CLIENT_SECRET = global.DEFAULT_YANDEXDISK_CLIENT_SECRET;
|
||||
|
||||
export interface YandexDiskConfig {
|
||||
accessToken: string;
|
||||
@@ -121,8 +139,8 @@ export interface YandexDiskConfig {
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
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 const KOOFR_CLIENT_ID = global.DEFAULT_KOOFR_CLIENT_ID;
|
||||
export const KOOFR_CLIENT_SECRET = global.DEFAULT_KOOFR_CLIENT_SECRET;
|
||||
|
||||
export interface KoofrConfig {
|
||||
accessToken: string;
|
||||
@@ -136,3 +154,16 @@ export interface KoofrConfig {
|
||||
mountID: string;
|
||||
kind: "koofr";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Azure Blob Storage
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
export interface AzureBlobStorageConfig {
|
||||
containerSasUrl: string;
|
||||
containerName: string;
|
||||
remotePrefix: string;
|
||||
generateFolderObject: boolean;
|
||||
partsConcurrency: number;
|
||||
kind: "azureblobstorage";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import * as path from "path";
|
||||
import {
|
||||
AnonymousCredential,
|
||||
BaseRequestPolicy,
|
||||
type BlobGetPropertiesResponse,
|
||||
BlobServiceClient,
|
||||
BlobUploadCommonResponse,
|
||||
BlockBlobClient,
|
||||
ContainerClient,
|
||||
newPipeline,
|
||||
} from "@azure/storage-blob";
|
||||
import type { Entity } from "../../src/baseTypes";
|
||||
import { FakeFs } from "../../src/fsAll";
|
||||
import { arrayBufferToHex, getFolderLevels } from "../../src/misc";
|
||||
import type { AzureBlobStorageConfig } from "./baseTypesPro";
|
||||
|
||||
export const simpleTransRemotePrefix = (x: string) => {
|
||||
if (x === undefined) {
|
||||
return "";
|
||||
}
|
||||
let y = path.posix.normalize(x.trim());
|
||||
if (y === undefined || y === "" || y === "/" || y === ".") {
|
||||
return "";
|
||||
}
|
||||
if (y.startsWith("/")) {
|
||||
y = y.slice(1);
|
||||
}
|
||||
if (!y.endsWith("/")) {
|
||||
y = `${y}/`;
|
||||
}
|
||||
return y;
|
||||
};
|
||||
|
||||
export const DEFAULT_AZUREBLOBSTORAGE_CONFIG: AzureBlobStorageConfig = {
|
||||
containerSasUrl: "",
|
||||
containerName: "",
|
||||
remotePrefix: "",
|
||||
generateFolderObject: false,
|
||||
partsConcurrency: 5,
|
||||
kind: "azureblobstorage",
|
||||
};
|
||||
|
||||
const getNormPath = (fileOrFolderPath: string, remotePrefix: string) => {
|
||||
if (remotePrefix.startsWith("/") || !remotePrefix.endsWith("/")) {
|
||||
throw Error(
|
||||
`remotePrefix should not have leading slash but should have tailing slash: ${remotePrefix}`
|
||||
);
|
||||
}
|
||||
if (!fileOrFolderPath.startsWith(remotePrefix)) {
|
||||
throw Error(`${fileOrFolderPath} does not start with ${remotePrefix}!`);
|
||||
}
|
||||
return fileOrFolderPath.slice(remotePrefix.length);
|
||||
};
|
||||
|
||||
const getBlobPath = (fileOrFolderPath: string, remotePrefix: string) => {
|
||||
if (remotePrefix.startsWith("/") || !remotePrefix.endsWith("/")) {
|
||||
throw Error(
|
||||
`remotePrefix should not have leading slash but should have tailing slash: ${remotePrefix}`
|
||||
);
|
||||
}
|
||||
return `${remotePrefix}${fileOrFolderPath}`;
|
||||
};
|
||||
|
||||
const fromBlobPropsToEntity = (
|
||||
name: string,
|
||||
props: BlobGetPropertiesResponse,
|
||||
remotePrefix: string
|
||||
): Entity => {
|
||||
const key = getNormPath(name, remotePrefix);
|
||||
|
||||
let mtimeCli = props.lastModified!.valueOf();
|
||||
const mtimeStr = props.metadata?.mtime;
|
||||
if (mtimeStr !== undefined && mtimeStr !== "") {
|
||||
try {
|
||||
mtimeCli = new Date(mtimeStr).valueOf();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let hash: undefined | string = undefined;
|
||||
if (props.contentMD5 !== undefined) {
|
||||
hash = arrayBufferToHex(props.contentMD5.buffer);
|
||||
}
|
||||
|
||||
const entity: Entity = {
|
||||
key: key,
|
||||
keyRaw: key,
|
||||
mtimeCli: mtimeCli,
|
||||
mtimeSvr: props.lastModified!.valueOf(),
|
||||
size: props.contentLength ?? 0,
|
||||
sizeRaw: props.contentLength ?? 0,
|
||||
hash: hash,
|
||||
};
|
||||
|
||||
if (key.endsWith("/")) {
|
||||
entity.synthesizedFolder = false;
|
||||
}
|
||||
|
||||
return entity;
|
||||
};
|
||||
|
||||
export class FakeFsAzureBlobStorage extends FakeFs {
|
||||
kind: string;
|
||||
config: AzureBlobStorageConfig;
|
||||
vaultName: string;
|
||||
containerClient: ContainerClient;
|
||||
remotePrefix: string;
|
||||
synthFoldersCache: Record<string, Entity>;
|
||||
|
||||
constructor(config: AzureBlobStorageConfig, vaultName: string) {
|
||||
super();
|
||||
this.kind = "azureblobstorage";
|
||||
this.config = config;
|
||||
this.vaultName = vaultName;
|
||||
this.synthFoldersCache = {};
|
||||
|
||||
this.remotePrefix = `${vaultName}/`;
|
||||
const k = simpleTransRemotePrefix(this.config.remotePrefix);
|
||||
if (k !== "") {
|
||||
// we have prefix
|
||||
this.remotePrefix = k;
|
||||
}
|
||||
|
||||
this.containerClient = new ContainerClient(this.config.containerSasUrl);
|
||||
}
|
||||
|
||||
async walk(): Promise<Entity[]> {
|
||||
const entities: Entity[] = [];
|
||||
const realEntities = new Set<string>();
|
||||
for await (const blob of this.containerClient.listBlobsFlat({
|
||||
prefix: this.remotePrefix,
|
||||
includeMetadata: true,
|
||||
})) {
|
||||
const blockBlobClient = this.containerClient.getBlockBlobClient(
|
||||
blob.name
|
||||
);
|
||||
const props = await blockBlobClient.getProperties();
|
||||
|
||||
// console.debug(blob.name)
|
||||
|
||||
const entity = fromBlobPropsToEntity(blob.name, props, this.remotePrefix);
|
||||
entities.push(entity);
|
||||
|
||||
// so we need to fake the folders
|
||||
realEntities.add(entity.key!);
|
||||
for (const f of getFolderLevels(entity.key!, true)) {
|
||||
if (realEntities.has(f)) {
|
||||
delete this.synthFoldersCache[f];
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!this.synthFoldersCache.hasOwnProperty(f) ||
|
||||
entity.mtimeSvr! >= this.synthFoldersCache[f].mtimeSvr!
|
||||
) {
|
||||
this.synthFoldersCache[f] = {
|
||||
key: f,
|
||||
keyRaw: f,
|
||||
size: 0,
|
||||
sizeRaw: 0,
|
||||
sizeEnc: 0,
|
||||
mtimeSvr: entity.mtimeSvr,
|
||||
mtimeSvrFmt: entity.mtimeSvrFmt,
|
||||
mtimeCli: entity.mtimeCli,
|
||||
mtimeCliFmt: entity.mtimeCliFmt,
|
||||
synthesizedFolder: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(this.synthFoldersCache)) {
|
||||
entities.push(this.synthFoldersCache[key]);
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
async walkPartial(): Promise<Entity[]> {
|
||||
const entities: Entity[] = [];
|
||||
for await (const blob of this.containerClient.listBlobsByHierarchy("/", {
|
||||
prefix: this.remotePrefix,
|
||||
includeMetadata: true,
|
||||
})) {
|
||||
if (blob.kind === "prefix") {
|
||||
continue;
|
||||
}
|
||||
const blockBlobClient = this.containerClient.getBlockBlobClient(
|
||||
blob.name
|
||||
);
|
||||
const props = await blockBlobClient.getProperties();
|
||||
|
||||
const entity = fromBlobPropsToEntity(blob.name, props, this.remotePrefix);
|
||||
entities.push(entity);
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
async stat(key: string): Promise<Entity> {
|
||||
const remotePath = getBlobPath(key, this.remotePrefix);
|
||||
const blockBlobClient = this.containerClient.getBlockBlobClient(remotePath);
|
||||
const props = await blockBlobClient.getProperties();
|
||||
|
||||
const entity = fromBlobPropsToEntity(remotePath, props, this.remotePrefix);
|
||||
return entity;
|
||||
}
|
||||
|
||||
async mkdir(
|
||||
key: string,
|
||||
mtime?: number | undefined,
|
||||
ctime?: number | undefined
|
||||
): Promise<Entity> {
|
||||
if (!key.endsWith("/")) {
|
||||
throw new Error(`You should not call mkdir on ${key}!`);
|
||||
}
|
||||
|
||||
const generateFolderObject = this.config.generateFolderObject ?? false;
|
||||
if (!generateFolderObject) {
|
||||
const synth = {
|
||||
key: key,
|
||||
keyRaw: key,
|
||||
size: 0,
|
||||
sizeRaw: 0,
|
||||
sizeEnc: 0,
|
||||
mtimeSvr: mtime,
|
||||
mtimeCli: mtime,
|
||||
synthesizedFolder: true,
|
||||
};
|
||||
this.synthFoldersCache[key] = synth;
|
||||
return synth;
|
||||
}
|
||||
|
||||
return await this.writeFile(
|
||||
key,
|
||||
new ArrayBuffer(0),
|
||||
mtime ?? Date.now(),
|
||||
ctime ?? Date.now()
|
||||
);
|
||||
}
|
||||
|
||||
async writeFile(
|
||||
key: string,
|
||||
content: ArrayBuffer,
|
||||
mtime: number,
|
||||
ctime: number
|
||||
): Promise<Entity> {
|
||||
const blobPath = getBlobPath(key, this.remotePrefix);
|
||||
|
||||
const blobClient = this.containerClient.getBlockBlobClient(blobPath);
|
||||
const metadata: Record<string, string> = {
|
||||
mtime: new Date(mtime).toISOString(),
|
||||
ctime: new Date(ctime).toISOString(),
|
||||
};
|
||||
|
||||
if (key.endsWith("/")) {
|
||||
console.debug(`yeah we have folder upload`);
|
||||
const generateFolderObject = this.config.generateFolderObject ?? false;
|
||||
if (!generateFolderObject) {
|
||||
throw Error(
|
||||
`if not generate folder object, the func should not go here`
|
||||
);
|
||||
}
|
||||
metadata["hdi_isfolder"] = "true";
|
||||
}
|
||||
|
||||
const uploadResult = await blobClient.uploadData(content, {
|
||||
metadata: metadata,
|
||||
concurrency: this.config.partsConcurrency ?? 5,
|
||||
});
|
||||
|
||||
if (key.endsWith("/")) {
|
||||
console.debug(`yeah we have folder upload`);
|
||||
console.debug(uploadResult);
|
||||
}
|
||||
|
||||
if (uploadResult._response.status >= 300) {
|
||||
throw Error(`upload ${key} failed with ${JSON.stringify(uploadResult)}`);
|
||||
}
|
||||
|
||||
return await this.stat(key);
|
||||
}
|
||||
|
||||
async readFile(key: string): Promise<ArrayBuffer> {
|
||||
const blobPath = getBlobPath(key, this.remotePrefix);
|
||||
const blobClient = this.containerClient.getBlockBlobClient(blobPath);
|
||||
const rsp = await blobClient.download();
|
||||
if (rsp._response.status >= 300) {
|
||||
throw Error(`download ${key} failed with ${JSON.stringify(rsp)}`);
|
||||
}
|
||||
return await (await rsp.blobBody)!.arrayBuffer();
|
||||
}
|
||||
|
||||
async rename(key1: string, key2: string): Promise<void> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async rm(key: string): Promise<void> {
|
||||
const blobPath = getBlobPath(key, this.remotePrefix);
|
||||
if (key.endsWith("/")) {
|
||||
if (this.synthFoldersCache.hasOwnProperty(key)) {
|
||||
delete this.synthFoldersCache[key];
|
||||
}
|
||||
|
||||
// in blob the folder may not exist, so we make our best effort.
|
||||
// do NOT read this.config.generateFolderObject
|
||||
// because the folder might be generated by previous setting
|
||||
try {
|
||||
const blobClient = this.containerClient.getBlockBlobClient(blobPath);
|
||||
await blobClient.deleteIfExists();
|
||||
} catch (e) {}
|
||||
} else {
|
||||
// the file should really exist
|
||||
const blobClient = this.containerClient.getBlockBlobClient(blobPath);
|
||||
const rsp = await blobClient.deleteIfExists();
|
||||
if (!rsp.succeeded) {
|
||||
throw Error(
|
||||
`something goes wrong while deleting ${key}: ${JSON.stringify(rsp)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkConnect(callbackFunc?: any): Promise<boolean> {
|
||||
// if we can walk, we can connect
|
||||
try {
|
||||
await this.walkPartial();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
unixTimeToStr,
|
||||
} from "../../src/misc";
|
||||
import {
|
||||
DEFAULT_GOOGLEDRIVE_CLIENT_ID,
|
||||
DEFAULT_GOOGLEDRIVE_CLIENT_SECRET,
|
||||
GOOGLEDRIVE_CLIENT_ID,
|
||||
GOOGLEDRIVE_CLIENT_SECRET,
|
||||
type GoogleDriveConfig,
|
||||
} from "./baseTypesPro";
|
||||
|
||||
@@ -103,8 +103,8 @@ export const sendRefreshTokenReq = async (refreshToken: string) => {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: DEFAULT_GOOGLEDRIVE_CLIENT_ID ?? "",
|
||||
client_secret: DEFAULT_GOOGLEDRIVE_CLIENT_SECRET ?? "",
|
||||
client_id: GOOGLEDRIVE_CLIENT_ID ?? "",
|
||||
client_secret: GOOGLEDRIVE_CLIENT_SECRET ?? "",
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
}).toString(),
|
||||
|
||||
@@ -92,6 +92,13 @@
|
||||
"modal_koofrrevokeauth_clean_notice": "Cleaned!",
|
||||
"modal_koofrrevokeauth_clean_fail": "Something goes wrong while revoking.",
|
||||
|
||||
"modal_remoteprefix_azureblobstorage_title": "You are changing the remote prefix config",
|
||||
"modal_remoteprefix_azureblobstorage_shortdesc": "1. The plugin would NOT automatically move the content from the old directory to the new one directly on the remote. Everything syncs from the beginning again.\n2. If you set the string to the empty, the prefix will be the vault name.\n3. The remote directory name itself would not be encrypted even you've set an E2E password.\n4. Some special char like '?', '/', '\\' are not allowed. Spaces in the beginning or in the end are also trimmed.",
|
||||
"modal_remoteprefix_azureblobstorage_invaliddirhint": "Your input contains special characters like '?', '/', '\\' which are not allowed.",
|
||||
"modal_remoteprefix_azureblobstorage_tosave": "The prefix to save is \"{{{prefix}}}\"",
|
||||
"modal_remoteprefix_azureblobstorage_secondconfirm_change": "Confirm To Change",
|
||||
"modal_remoteprefix_azureblobstorage_notice": "New remote prefix config saved!",
|
||||
|
||||
"modal_prorevokeauth": "Revoke auth by clicking here and follow the steps.",
|
||||
"modal_prorevokeauth_clean": "Clean",
|
||||
"modal_prorevokeauth_clean_desc": "Clean local auth record",
|
||||
@@ -199,11 +206,36 @@
|
||||
"settings_koofr_connect_succ": "Great! We can connect to Koofr!",
|
||||
"settings_koofr_connect_fail": "We cannot connect to Koofr.",
|
||||
|
||||
"settings_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_chooseservice_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_azureblobstorage_disclaimer1": "Disclaimer: This app is NOT an official Microsoft / Azure product. The app just uses Azure Blob Storage's public api.",
|
||||
"settings_azureblobstorage_disclaimer2": "Disclaimer: The information is stored locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Azure Blob Storage, please immediately delete the SAS Url and <a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">try to change your policy</a> or account info.",
|
||||
"settings_azureblobstorage_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_azureblobstorage_notshowuphint": "Azure Blob Storage Settings Not Available",
|
||||
"settings_azureblobstorage_notshowuphint_desc": "Azure Blob Storage settings are not available, because you haven't subscribed to the PRO feature in your Remotely Save account.",
|
||||
"settings_azureblobstorage_notshowuphint_view_pro": "View PRO Settings",
|
||||
"settings_azureblobstorage_folder": "We will create and sync inside the prefix {{remoteBaseDir}} on your Azure Blob Storage.",
|
||||
"settings_azureblobstorage_containersasurl": "Container SAS Url",
|
||||
"settings_azureblobstorage_containersasurl_desc": "The only connction method is providing the Container SAS Url. Remember to configure CORS as well. Please read the <a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">doc</a> for details.",
|
||||
"settings_azureblobstorage_containername": "Container Name",
|
||||
"settings_azureblobstorage_containername_desc": "Input your container name here.",
|
||||
"settings_azureblobstorage_remoteprefix": "Remote Prefix",
|
||||
"settings_azureblobstorage_remoteprefix_desc": "Input your remote prefix of blobs for this vault. If you set this value to empty, the <strong>vault name</strong> will be used as prefix. <strong>Empty prefix is not allowed and not settable here.</strong>",
|
||||
"settings_azureblobstorage_parts": "Parts Concurrency",
|
||||
"settings_azureblobstorage_parts_desc": "Large files are split into small parts to upload. How many parts do you want to upload in parallel at most?",
|
||||
"settings_azureblobstorage_generatefolderobject": "Generate Folder Object Or Not",
|
||||
"settings_azureblobstorage_generatefolderobject_desc": "Azure Blob Storage doesn't have \"real\" folder. If you set \"Generate\" here, the plugin will upload a zero-byte object endding with \"/\" to represent the folder. By default, the plugin skips generating folder object.",
|
||||
"settings_azureblobstorage_generatefolderobject_notgenerate": "Not generate (default)",
|
||||
"settings_azureblobstorage_generatefolderobject_generate": "Generate",
|
||||
"settings_azureblobstorage_connect_succ": "Great! We can connect to Azure Blob Storage!",
|
||||
"settings_azureblobstorage_connect_fail": "We cannot connect to Azure Blob Storage.",
|
||||
|
||||
"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_export_azureblobstorage_button": "Export Azure Blob Storage 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.",
|
||||
|
||||
@@ -102,6 +102,14 @@
|
||||
"modal_koofrrevokeauth_clean_notice": "已清理!",
|
||||
"modal_koofrrevokeauth_clean_fail": "清理授权时候发生了错误。",
|
||||
|
||||
"modal_remoteprefix_azureblobstorage_title": "您正在修改远端路径前缀设置",
|
||||
"modal_remoteprefix_azureblobstorage_shortdesc": "1. 本插件并不会自动在远端把内容从旧文件夹移动到新文件夹。所有内容都会重新同步。\n2. 如果你使得文本输入框为空,那么本设置为保存为空,文件将会使用 Vault 名字作为前缀。\n3. 即使您设置了端对端加密的密码,远端文件夹名称本身也不会被加密。\n4. 某些特殊字符,如“?”、“/”、“\\”是不允许的。文本前后的空格也会被自动删去。",
|
||||
"modal_remoteprefix_azureblobstorage_invaliddirhint": "您所输入的内容含有某些特殊字符,如“?”、“/”、“\\”,它们是不允许的。",
|
||||
"modal_remoteprefix_azureblobstorage_tosave": "您设定的新前缀为:“{{{prefix}}}”",
|
||||
"modal_remoteprefix_azureblobstorage_secondconfirm_empty": "前缀为空,文件会保存在根目录",
|
||||
"modal_remoteprefix_azureblobstorage_secondconfirm_change": "确认修改",
|
||||
"modal_remoteprefix_azureblobstorage_notice": "新的远端路径前缀设置已保存!",
|
||||
|
||||
"modal_prorevokeauth": "点击这里和按照步骤取消授权。",
|
||||
"modal_prorevokeauth_clean": "清理",
|
||||
"modal_prorevokeauth_clean_desc": "清理本地授权记录",
|
||||
@@ -209,11 +217,36 @@
|
||||
"settings_koofr_connect_succ": "很好!我们可连接上 Koofr!",
|
||||
"settings_koofr_connect_fail": "我们未能连接上 Koofr。",
|
||||
|
||||
"settings_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_chooseservice_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_azureblobstorage_disclaimer1": "声明:本插件不是微软或 Azure 的官方产品。只是用到了 Azure 的公开 API。",
|
||||
"settings_azureblobstorage_disclaimer2": "声明:您所输入的信息存储于本地。其它有害的或者出错的插件,是有可能读取到这些信息的。如果您发现任何不符合预期的 Azure Blob Storage 访问,请立刻删除 SAS Url 然后<a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">修改预设的 policy</a> 或账号信息。",
|
||||
"settings_azureblobstorage_pro_desc": "<p><strong>!!这是 PRO(付费)功能! 您需要在线账号来使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 账号的更多信息。)</p>",
|
||||
"settings_azureblobstorage_notshowuphint": "Azure Blob Storage 设置不可用",
|
||||
"settings_azureblobstorage_notshowuphint_desc": "Azure Blob Storage 设置不可用,因为您没有在 Remotely Save 账号里开启这个 PRO 功能。",
|
||||
"settings_azureblobstorage_notshowuphint_view_pro": "查看 PRO 相关设置",
|
||||
"settings_azureblobstorage_folder": "Azure Blob Storage 里,我们会用这个前缀保存你的文件: {{remoteBaseDir}} 。",
|
||||
"settings_azureblobstorage_containersasurl": "Container SAS Url",
|
||||
"settings_azureblobstorage_containersasurl_desc": "目前只支持通过 Container SAS Url 进行连接。请记得同时设置 CORS。可阅读<a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">文档</a>获取更多说明。",
|
||||
"settings_azureblobstorage_containername": "Container 名字",
|
||||
"settings_azureblobstorage_containername_desc": "输入 Container 名字。",
|
||||
"settings_azureblobstorage_remoteprefix": "修改远端前缀路径",
|
||||
"settings_azureblobstorage_remoteprefix_desc": "您可以在这里修改路径前缀。如果设置为空,那么<strong>库(vault)名字</strong>会作为前缀。<strong>不允许设置空前缀。</strong>您需要点击“确认”。",
|
||||
"settings_azureblobstorage_parts": "分块并行度",
|
||||
"settings_azureblobstorage_parts_desc": "大文件会被分块上传。您希望同一时间最多有多少个分块被上传?",
|
||||
"settings_azureblobstorage_generatefolderobject": "是否生成文件夹 Object",
|
||||
"settings_azureblobstorage_generatefolderobject_desc": "Azure Blob Storage 不存在“真正”的文件夹。如果您设置了“生成”,那么插件会上传 0 字节的以“/”结尾的 Object 来代表文件夹。默认跳过生成这种文件夹 Object。",
|
||||
"settings_azureblobstorage_generatefolderobject_notgenerate": "不生成(默认)",
|
||||
"settings_azureblobstorage_generatefolderobject_generate": "生成",
|
||||
"settings_azureblobstorage_connect_succ": "很好!我们可连接上 Azure Blob Storage!",
|
||||
"settings_azureblobstorage_connect_fail": "我们未能连接上 Azure Blob Storage。",
|
||||
|
||||
"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_export_azureblobstorage_button": "导出 Azure Blob Storage 部分",
|
||||
|
||||
"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>第二部:点击“连接”按钮,从而连接本设备和在线账号。",
|
||||
|
||||
@@ -102,6 +102,14 @@
|
||||
"modal_koofrrevokeauth_clean_notice": "已清理!",
|
||||
"modal_koofrrevokeauth_clean_fail": "清理授權時候發生了錯誤。",
|
||||
|
||||
"modal_remoteprefix_azureblobstorage_title": "您正在修改遠端路徑字首設定",
|
||||
"modal_remoteprefix_azureblobstorage_shortdesc": "1. 本外掛並不會自動在遠端把內容從舊資料夾移動到新資料夾。所有內容都會重新同步。\n2. 如果你使得文字輸入框為空,那麼本設定為儲存為空,檔案將會使用 Vault 名字作為字首。\n3. 即使您設定了端對端加密的密碼,遠端資料夾名稱本身也不會被加密。\n4. 某些特殊字元,如“?”、“/”、“\\”是不允許的。文字前後的空格也會被自動刪去。",
|
||||
"modal_remoteprefix_azureblobstorage_invaliddirhint": "您所輸入的內容含有某些特殊字元,如“?”、“/”、“\\”,它們是不允許的。",
|
||||
"modal_remoteprefix_azureblobstorage_tosave": "您設定的新字首為:“{{{prefix}}}”",
|
||||
"modal_remoteprefix_azureblobstorage_secondconfirm_empty": "字首為空,檔案會儲存在根目錄",
|
||||
"modal_remoteprefix_azureblobstorage_secondconfirm_change": "確認修改",
|
||||
"modal_remoteprefix_azureblobstorage_notice": "新的遠端路徑字首設定已儲存!",
|
||||
|
||||
"modal_prorevokeauth": "點選這裡和按照步驟取消授權。",
|
||||
"modal_prorevokeauth_clean": "清理",
|
||||
"modal_prorevokeauth_clean_desc": "清理本地授權記錄",
|
||||
@@ -209,11 +217,36 @@
|
||||
"settings_koofr_connect_succ": "很好!我們可連線上 Koofr!",
|
||||
"settings_koofr_connect_fail": "我們未能連線上 Koofr。",
|
||||
|
||||
"settings_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_chooseservice_azureblobstorage": "Azure Blob Storage (PRO) (alpha)",
|
||||
"settings_azureblobstorage_disclaimer1": "宣告:本外掛不是微軟或 Azure 的官方產品。只是用到了 Azure 的公開 API。",
|
||||
"settings_azureblobstorage_disclaimer2": "宣告:您所輸入的資訊儲存於本地。其它有害的或者出錯的外掛,是有可能讀取到這些資訊的。如果您發現任何不符合預期的 Azure Blob Storage 訪問,請立刻刪除 SAS Url 然後<a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">修改預設的 policy</a> 或賬號資訊。",
|
||||
"settings_azureblobstorage_pro_desc": "<p><strong>!!這是 PRO(付費)功能! 您需要線上賬號來使用此功能!!</strong>(<a href=\"#settings-pro\">向下滑</a>可以看到 PRO 賬號的更多資訊。)</p>",
|
||||
"settings_azureblobstorage_notshowuphint": "Azure Blob Storage 設定不可用",
|
||||
"settings_azureblobstorage_notshowuphint_desc": "Azure Blob Storage 設定不可用,因為您沒有在 Remotely Save 賬號裡開啟這個 PRO 功能。",
|
||||
"settings_azureblobstorage_notshowuphint_view_pro": "檢視 PRO 相關設定",
|
||||
"settings_azureblobstorage_folder": "Azure Blob Storage 裡,我們會用這個字首儲存你的檔案: {{remoteBaseDir}} 。",
|
||||
"settings_azureblobstorage_containersasurl": "Container SAS Url",
|
||||
"settings_azureblobstorage_containersasurl_desc": "目前只支援透過 Container SAS Url 進行連線。請記得同時設定 CORS。可閱讀<a href=\"https://github.com/remotely-save/remotely-save/blob/master/docs/remote_services/azureblobstorage/README.md\">文件</a>獲取更多說明。",
|
||||
"settings_azureblobstorage_containername": "Container 名字",
|
||||
"settings_azureblobstorage_containername_desc": "輸入 Container 名字。",
|
||||
"settings_azureblobstorage_remoteprefix": "修改遠端字首路徑",
|
||||
"settings_azureblobstorage_remoteprefix_desc": "您可以在這裡修改路徑字首。如果設定為空,那麼<strong>庫(vault)名字</strong>會作為字首。<strong>不允許設定空字首。</strong>您需要點選“確認”。",
|
||||
"settings_azureblobstorage_parts": "分塊並行度",
|
||||
"settings_azureblobstorage_parts_desc": "大檔案會被分塊上傳。您希望同一時間最多有多少個分塊被上傳?",
|
||||
"settings_azureblobstorage_generatefolderobject": "是否生成資料夾 Object",
|
||||
"settings_azureblobstorage_generatefolderobject_desc": "Azure Blob Storage 不存在“真正”的資料夾。如果您設定了“生成”,那麼外掛會上傳 0 位元組的以“/”結尾的 Object 來代表資料夾。預設跳過生成這種資料夾 Object。",
|
||||
"settings_azureblobstorage_generatefolderobject_notgenerate": "不生成(預設)",
|
||||
"settings_azureblobstorage_generatefolderobject_generate": "生成",
|
||||
"settings_azureblobstorage_connect_succ": "很好!我們可連線上 Azure Blob Storage!",
|
||||
"settings_azureblobstorage_connect_fail": "我們未能連線上 Azure Blob Storage。",
|
||||
|
||||
"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_export_azureblobstorage_button": "匯出 Azure Blob Storage 部分",
|
||||
|
||||
"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,293 @@
|
||||
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 { wrapTextWithPasswordHide } from "../../src/settings";
|
||||
import {
|
||||
DEFAULT_AZUREBLOBSTORAGE_CONFIG,
|
||||
simpleTransRemotePrefix,
|
||||
} from "./fsAzureBlobStorage";
|
||||
|
||||
class ChangeAzureBlobStorageRemotePrefixModal extends Modal {
|
||||
readonly plugin: RemotelySavePlugin;
|
||||
readonly newRemotePrefix: string;
|
||||
constructor(app: App, plugin: RemotelySavePlugin, newRemotePrefix: string) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.newRemotePrefix = newRemotePrefix;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
|
||||
const t = (x: TransItemType, vars?: any) => {
|
||||
return this.plugin.i18n.t(x, vars);
|
||||
};
|
||||
|
||||
contentEl.createEl("h2", {
|
||||
text: t("modal_remoteprefix_azureblobstorage_title"),
|
||||
});
|
||||
t("modal_remoteprefix_azureblobstorage_shortdesc")
|
||||
.split("\n")
|
||||
.forEach((val, idx) => {
|
||||
contentEl.createEl("p", {
|
||||
text: val,
|
||||
});
|
||||
});
|
||||
|
||||
contentEl.createEl("p", {
|
||||
text: t("modal_remoteprefix_azureblobstorage_tosave", {
|
||||
prefix: this.newRemotePrefix,
|
||||
}),
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton((button) => {
|
||||
button.setButtonText(
|
||||
t("modal_remoteprefix_azureblobstorage_secondconfirm_change")
|
||||
);
|
||||
button.onClick(async () => {
|
||||
this.plugin.settings.azureblobstorage.remotePrefix =
|
||||
this.newRemotePrefix;
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(t("modal_remoteprefix_azureblobstorage_notice"));
|
||||
this.close();
|
||||
});
|
||||
button.setClass("remoteprefix-azureblobstorage-second-confirm");
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText(t("goback"));
|
||||
button.onClick(() => {
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
export const generateAzureBlobStorageSettingsPart = (
|
||||
containerEl: HTMLElement,
|
||||
t: (x: TransItemType, vars?: any) => string,
|
||||
app: App,
|
||||
plugin: RemotelySavePlugin,
|
||||
saveUpdatedConfigFunc: () => Promise<any> | undefined
|
||||
) => {
|
||||
const azureBlobStorageDiv = containerEl.createEl("div", {
|
||||
cls: "azureblobstorage-hide",
|
||||
});
|
||||
azureBlobStorageDiv.toggleClass(
|
||||
"azureblobstorage-hide",
|
||||
plugin.settings.serviceType !== "azureblobstorage"
|
||||
);
|
||||
azureBlobStorageDiv.createEl("h2", { text: t("settings_azureblobstorage") });
|
||||
|
||||
const azureBlobStorageLongDescDiv = azureBlobStorageDiv.createEl("div", {
|
||||
cls: "settings-long-desc",
|
||||
});
|
||||
for (const c of [
|
||||
t("settings_azureblobstorage_disclaimer1"),
|
||||
stringToFragment(t("settings_azureblobstorage_disclaimer2")),
|
||||
]) {
|
||||
azureBlobStorageLongDescDiv.createEl("p", {
|
||||
text: c,
|
||||
cls: "azureblobstorage-disclaimer",
|
||||
});
|
||||
}
|
||||
|
||||
azureBlobStorageLongDescDiv.createEl("p", {
|
||||
text: t("settings_azureblobstorage_folder", {
|
||||
remotePrefix:
|
||||
plugin.settings.azureblobstorage.remotePrefix ||
|
||||
`${app.vault.getName()}/`,
|
||||
}),
|
||||
});
|
||||
|
||||
azureBlobStorageLongDescDiv.createDiv({
|
||||
text: stringToFragment(t("settings_azureblobstorage_pro_desc")),
|
||||
cls: "azureblobstorage-disclaimer",
|
||||
});
|
||||
|
||||
const azureBlobStorageNotShowUpHintSetting = new Setting(azureBlobStorageDiv)
|
||||
.setName(t("settings_azureblobstorage_notshowuphint"))
|
||||
.setDesc(t("settings_azureblobstorage_notshowuphint_desc"))
|
||||
.addButton(async (button) => {
|
||||
button.setButtonText(
|
||||
t("settings_azureblobstorage_notshowuphint_view_pro")
|
||||
);
|
||||
button.onClick(async () => {
|
||||
window.location.href = "#settings-pro";
|
||||
});
|
||||
});
|
||||
|
||||
const azureBlobStorageAllowedToUsedDiv = azureBlobStorageDiv.createDiv();
|
||||
// if pro enabled, show up; otherwise hide.
|
||||
const allowAzureBlobStorage =
|
||||
plugin.settings.pro?.enabledProFeatures.filter(
|
||||
(x) => x.featureName === "feature-azure_blob_storage"
|
||||
).length === 1;
|
||||
console.debug(
|
||||
`allow to show up azureBlobStorage settings? ${allowAzureBlobStorage}`
|
||||
);
|
||||
if (allowAzureBlobStorage) {
|
||||
azureBlobStorageAllowedToUsedDiv.removeClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
azureBlobStorageNotShowUpHintSetting.settingEl.addClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
} else {
|
||||
azureBlobStorageAllowedToUsedDiv.addClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
azureBlobStorageNotShowUpHintSetting.settingEl.removeClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
}
|
||||
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.setName(t("settings_azureblobstorage_containersasurl"))
|
||||
.setDesc(
|
||||
stringToFragment(t("settings_azureblobstorage_containersasurl_desc"))
|
||||
)
|
||||
.addText((text) => {
|
||||
wrapTextWithPasswordHide(text);
|
||||
text
|
||||
.setPlaceholder("")
|
||||
.setValue(`${plugin.settings.azureblobstorage.containerSasUrl}`)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.azureblobstorage.containerSasUrl = value.trim();
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.setName(t("settings_azureblobstorage_containername"))
|
||||
.setDesc(t("settings_azureblobstorage_containername_desc"))
|
||||
.addText((text) => {
|
||||
wrapTextWithPasswordHide(text);
|
||||
text
|
||||
.setPlaceholder("")
|
||||
.setValue(`${plugin.settings.azureblobstorage.containerName}`)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.azureblobstorage.containerName = value.trim();
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
let newAzureBlobStorageRemotePrefix =
|
||||
plugin.settings.azureblobstorage.remotePrefix || "";
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.setName(t("settings_azureblobstorage_remoteprefix"))
|
||||
.setDesc(t("settings_azureblobstorage_remoteprefix_desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(`${app.vault.getName()}/`)
|
||||
.setValue(newAzureBlobStorageRemotePrefix)
|
||||
.onChange((value) => {
|
||||
const k = simpleTransRemotePrefix(value);
|
||||
if (k === "") {
|
||||
newAzureBlobStorageRemotePrefix = `${app.vault.getName()}/`;
|
||||
} else {
|
||||
newAzureBlobStorageRemotePrefix = k;
|
||||
}
|
||||
})
|
||||
)
|
||||
.addButton((button) => {
|
||||
button.setButtonText(t("confirm"));
|
||||
button.onClick(() => {
|
||||
new ChangeAzureBlobStorageRemotePrefixModal(
|
||||
app,
|
||||
plugin,
|
||||
newAzureBlobStorageRemotePrefix
|
||||
).open();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.setName(t("settings_azureblobstorage_parts"))
|
||||
.setDesc(t("settings_azureblobstorage_parts_desc"))
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("1", "1");
|
||||
dropdown.addOption("2", "2");
|
||||
dropdown.addOption("3", "3");
|
||||
dropdown.addOption("5", "5");
|
||||
dropdown.addOption("10", "10");
|
||||
dropdown.addOption("15", "15");
|
||||
dropdown.addOption("20", "20 (default)");
|
||||
|
||||
dropdown
|
||||
.setValue(`${plugin.settings.azureblobstorage.partsConcurrency}`)
|
||||
.onChange(async (val) => {
|
||||
const realVal = Number.parseInt(val);
|
||||
plugin.settings.azureblobstorage.partsConcurrency = realVal;
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.setName(t("settings_azureblobstorage_generatefolderobject"))
|
||||
.setDesc(t("settings_azureblobstorage_generatefolderobject_desc"))
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown
|
||||
.addOption(
|
||||
"notgenerate",
|
||||
t("settings_azureblobstorage_generatefolderobject_notgenerate")
|
||||
)
|
||||
.addOption(
|
||||
"generate",
|
||||
t("settings_azureblobstorage_generatefolderobject_generate")
|
||||
);
|
||||
|
||||
dropdown
|
||||
.setValue(
|
||||
`${
|
||||
plugin.settings.azureblobstorage.generateFolderObject
|
||||
? "generate"
|
||||
: "notgenerate"
|
||||
}`
|
||||
)
|
||||
.onChange(async (val) => {
|
||||
if (val === "generate") {
|
||||
plugin.settings.azureblobstorage.generateFolderObject = true;
|
||||
} else {
|
||||
plugin.settings.azureblobstorage.generateFolderObject = false;
|
||||
}
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(azureBlobStorageAllowedToUsedDiv)
|
||||
.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_azureblobstorage_connect_succ"));
|
||||
} else {
|
||||
new Notice(t("settings_azureblobstorage_connect_fail"));
|
||||
new Notice(errors.msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
azureBlobStorageDiv: azureBlobStorageDiv,
|
||||
azureBlobStorageAllowedToUsedDiv: azureBlobStorageAllowedToUsedDiv,
|
||||
azureBlobStorageNotShowUpHintSetting: azureBlobStorageNotShowUpHintSetting,
|
||||
};
|
||||
};
|
||||
+26
-1
@@ -257,7 +257,9 @@ export const generateProSettingsPart = (
|
||||
yandexDiskAllowedToUsedDiv: HTMLDivElement,
|
||||
yandexDiskNotShowUpHintSetting: Setting,
|
||||
koofrAllowedToUsedDiv: HTMLDivElement,
|
||||
koofrNotShowUpHintSetting: Setting
|
||||
koofrNotShowUpHintSetting: Setting,
|
||||
azureBlobStorageAllowedToUsedDiv: HTMLDivElement,
|
||||
azureBlobStorageNotShowUpHintSetting: Setting
|
||||
) => {
|
||||
proDiv
|
||||
.createEl("h2", { text: t("settings_pro") })
|
||||
@@ -389,6 +391,29 @@ export const generateProSettingsPart = (
|
||||
);
|
||||
}
|
||||
|
||||
const allowAzureBlobStorage =
|
||||
plugin.settings.pro?.enabledProFeatures.filter(
|
||||
(x) => x.featureName === "feature-azure_blob_storage"
|
||||
).length === 1;
|
||||
console.debug(
|
||||
`allow to show up AzureBlobStorage settings? ${allowAzureBlobStorage}`
|
||||
);
|
||||
if (allowAzureBlobStorage) {
|
||||
azureBlobStorageAllowedToUsedDiv.removeClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
azureBlobStorageNotShowUpHintSetting.settingEl.addClass(
|
||||
"azureBlobStorage-allow-to-use-hide"
|
||||
);
|
||||
} else {
|
||||
azureBlobStorageAllowedToUsedDiv.addClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
azureBlobStorageNotShowUpHintSetting.settingEl.removeClass(
|
||||
"azureblobstorage-allow-to-use-hide"
|
||||
);
|
||||
}
|
||||
|
||||
new Notice(t("settings_pro_features_refresh_succ"));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user