Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fce079481 | ||
|
|
baf4e6b369 | ||
|
|
dd39e229d8 | ||
|
|
ba6e8be304 | ||
|
|
cb7a372b69 | ||
|
|
9a4dd0186d | ||
|
|
f9cdf323b8 | ||
|
|
474216b9ab | ||
|
|
99f143115b | ||
|
|
39940deb3f | ||
|
|
df53ea957f | ||
|
|
0cf8023278 | ||
|
|
be1386b345 | ||
|
|
9a189d63c9 | ||
|
|
00d0c557df | ||
|
|
a70a53eeec | ||
|
|
8cec2b00eb | ||
|
|
6efe52b26e |
@@ -20,7 +20,7 @@ The list is for information purposes only.
|
|||||||
| [Nginx `ngx_http_dav_module`](http://nginx.org/en/docs/http/ngx_http_dav_module.html) | Yes? | | Yes? | | Yes? | ? |
|
| [Nginx `ngx_http_dav_module`](http://nginx.org/en/docs/http/ngx_http_dav_module.html) | Yes? | | Yes? | | Yes? | ? |
|
||||||
| NextCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
|
| NextCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
|
||||||
| OwnCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
|
| OwnCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
|
||||||
| Seafile | Yes? | | Yes? | | Yes? | |
|
| Seafile | Yes | | Yes | | Yes? | No CORS config by default. |
|
||||||
| `rclone serve webdav` | Yes | | Yes | | Yes | No CORS support. |
|
| `rclone serve webdav` | Yes | | Yes | | Yes | No CORS support. |
|
||||||
| [Nutstore 坚果云](https://www.jianguoyun.com/) | Yes | | Yes | | Yes | No CORS support. |
|
| [Nutstore 坚果云](https://www.jianguoyun.com/) | Yes | | Yes | | Yes | No CORS support. |
|
||||||
| [TeraCLOUD](https://teracloud.jp/en/) | Yes | | Yes | | Yes | No CORS support. |
|
| [TeraCLOUD](https://teracloud.jp/en/) | Yes | | Yes | | Yes | No CORS support. |
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.3.17",
|
"version": "0.3.19",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+10
-8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.3.17",
|
"version": "0.3.19",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev2": "node esbuild.config.mjs",
|
"dev2": "node esbuild.config.mjs",
|
||||||
@@ -51,13 +51,13 @@
|
|||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.54.0",
|
"@aws-sdk/client-s3": "^3.67.0",
|
||||||
"@aws-sdk/fetch-http-handler": "^3.54.0",
|
"@aws-sdk/fetch-http-handler": "^3.58.0",
|
||||||
"@aws-sdk/lib-storage": "^3.54.0",
|
"@aws-sdk/lib-storage": "^3.67.0",
|
||||||
"@aws-sdk/protocol-http": "^3.54.0",
|
"@aws-sdk/protocol-http": "^3.58.0",
|
||||||
"@aws-sdk/querystring-builder": "^3.54.0",
|
"@aws-sdk/querystring-builder": "^3.55.0",
|
||||||
"@aws-sdk/signature-v4-crt": "^3.54.0",
|
"@aws-sdk/signature-v4-crt": "^3.66.0",
|
||||||
"@aws-sdk/types": "^3.54.0",
|
"@aws-sdk/types": "^3.55.0",
|
||||||
"@azure/msal-node": "^1.6.0",
|
"@azure/msal-node": "^1.6.0",
|
||||||
"@fyears/tsqueue": "^1.0.1",
|
"@fyears/tsqueue": "^1.0.1",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.1",
|
"@microsoft/microsoft-graph-client": "^3.0.1",
|
||||||
@@ -67,7 +67,9 @@
|
|||||||
"aws-crt": "^1.10.1",
|
"aws-crt": "^1.10.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
|
"delay": "^5.0.0",
|
||||||
"dropbox": "^10.22.0",
|
"dropbox": "^10.22.0",
|
||||||
|
"emoji-regex": "^10.1.0",
|
||||||
"http-status-codes": "^2.2.0",
|
"http-status-codes": "^2.2.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
+5
-2
@@ -154,9 +154,12 @@ export interface FileOrFolderMixedState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const API_VER_STAT_FOLDER = "0.13.27";
|
export const API_VER_STAT_FOLDER = "0.13.27";
|
||||||
export const API_VER_REQURL = "0.13.26";
|
export const API_VER_REQURL = "0.13.26"; // desktop ver 0.13.26, iOS ver 1.1.1
|
||||||
|
export const API_VER_REQURL_ANDROID = "0.14.6"; // Android ver 1.2.1
|
||||||
|
|
||||||
export const VALID_REQURL =
|
export const VALID_REQURL =
|
||||||
requireApiVersion(API_VER_REQURL) && !Platform.isAndroidApp;
|
(!Platform.isAndroidApp && requireApiVersion(API_VER_REQURL)) ||
|
||||||
|
(Platform.isAndroidApp && requireApiVersion(API_VER_REQURL_ANDROID));
|
||||||
|
|
||||||
export const DEFAULT_DEBUG_FOLDER = "_debug_remotely_save/";
|
export const DEFAULT_DEBUG_FOLDER = "_debug_remotely_save/";
|
||||||
export const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX =
|
export const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX =
|
||||||
|
|||||||
+70
-4
@@ -11,14 +11,73 @@ import {
|
|||||||
DEFAULT_DEBUG_FOLDER,
|
DEFAULT_DEBUG_FOLDER,
|
||||||
DEFAULT_LOG_HISTORY_FILE_PREFIX,
|
DEFAULT_LOG_HISTORY_FILE_PREFIX,
|
||||||
DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX,
|
DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX,
|
||||||
|
FileOrFolderMixedState,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
|
|
||||||
import { log } from "./moreOnLog";
|
import { log } from "./moreOnLog";
|
||||||
|
|
||||||
|
const turnSyncPlanToTable = (record: string) => {
|
||||||
|
const syncPlan: SyncPlanType = JSON.parse(record);
|
||||||
|
const { ts, tsFmt, remoteType, mixedStates } = syncPlan;
|
||||||
|
|
||||||
|
type allowedHeadersType = keyof FileOrFolderMixedState;
|
||||||
|
const headers: allowedHeadersType[] = [
|
||||||
|
"key",
|
||||||
|
"remoteEncryptedKey",
|
||||||
|
"existLocal",
|
||||||
|
"sizeLocal",
|
||||||
|
"mtimeLocal",
|
||||||
|
"deltimeLocal",
|
||||||
|
"changeLocalMtimeUsingMapping",
|
||||||
|
"existRemote",
|
||||||
|
"sizeRemote",
|
||||||
|
"mtimeRemote",
|
||||||
|
"deltimeRemote",
|
||||||
|
"changeRemoteMtimeUsingMapping",
|
||||||
|
"decision",
|
||||||
|
"decisionBranch",
|
||||||
|
];
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
`ts: ${ts}${tsFmt !== undefined ? " / " + tsFmt : ""}`,
|
||||||
|
`remoteType: ${remoteType}`,
|
||||||
|
`| ${headers.join(" | ")} |`,
|
||||||
|
`| ${headers.map((x) => "---").join(" | ")} |`,
|
||||||
|
];
|
||||||
|
for (const [k1, v1] of Object.entries(syncPlan.mixedStates)) {
|
||||||
|
const k = k1 as string;
|
||||||
|
const v = v1 as FileOrFolderMixedState;
|
||||||
|
const singleLine = [];
|
||||||
|
for (const h of headers) {
|
||||||
|
const field = v[h];
|
||||||
|
if (field === undefined) {
|
||||||
|
singleLine.push("");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
h === "mtimeLocal" ||
|
||||||
|
h === "deltimeLocal" ||
|
||||||
|
h === "mtimeRemote" ||
|
||||||
|
h === "deltimeRemote"
|
||||||
|
) {
|
||||||
|
const fmt = v[(h + "Fmt") as allowedHeadersType] as string;
|
||||||
|
const s = `${field}${fmt !== undefined ? " / " + fmt : ""}`;
|
||||||
|
singleLine.push(s);
|
||||||
|
} else {
|
||||||
|
singleLine.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push(`| ${singleLine.join(" | ")} |`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
export const exportVaultSyncPlansToFiles = async (
|
export const exportVaultSyncPlansToFiles = async (
|
||||||
db: InternalDBs,
|
db: InternalDBs,
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
vaultRandomID: string
|
vaultRandomID: string,
|
||||||
|
toFormat: "table" | "json" = "json"
|
||||||
) => {
|
) => {
|
||||||
log.info("exporting");
|
log.info("exporting");
|
||||||
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
||||||
@@ -27,9 +86,16 @@ export const exportVaultSyncPlansToFiles = async (
|
|||||||
if (records.length === 0) {
|
if (records.length === 0) {
|
||||||
md = "No sync plans history found";
|
md = "No sync plans history found";
|
||||||
} else {
|
} else {
|
||||||
md =
|
if (toFormat === "json") {
|
||||||
"Sync plans found:\n\n" +
|
md =
|
||||||
records.map((x) => "```json\n" + x + "\n```\n").join("\n");
|
"Sync plans found:\n\n" +
|
||||||
|
records.map((x) => "```json\n" + x + "\n```\n").join("\n");
|
||||||
|
} else if (toFormat === "table") {
|
||||||
|
md =
|
||||||
|
"Sync plans found:\n\n" + records.map(turnSyncPlanToTable).join("\n\n");
|
||||||
|
} else {
|
||||||
|
const _: never = toFormat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const filePath = `${DEFAULT_DEBUG_FOLDER}${DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX}${ts}.md`;
|
const filePath = `${DEFAULT_DEBUG_FOLDER}${DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX}${ts}.md`;
|
||||||
|
|||||||
+1
-1
Submodule src/langs updated: c46d302441...82d0619761
+58
-3
@@ -8,7 +8,7 @@ import {
|
|||||||
FileSystemAdapter,
|
FileSystemAdapter,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { createElement, RotateCcw, RefreshCcw } from "lucide";
|
import { createElement, RotateCcw, RefreshCcw, FileText } from "lucide";
|
||||||
import type { RemotelySavePluginSettings } from "./baseTypes";
|
import type { RemotelySavePluginSettings } from "./baseTypes";
|
||||||
import {
|
import {
|
||||||
COMMAND_CALLBACK,
|
COMMAND_CALLBACK,
|
||||||
@@ -57,6 +57,10 @@ import { applyPresetRulesInplace } from "./presetRules";
|
|||||||
|
|
||||||
import { applyLogWriterInplace, log } from "./moreOnLog";
|
import { applyLogWriterInplace, log } from "./moreOnLog";
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from "aggregate-error";
|
||||||
|
import {
|
||||||
|
exportVaultLoggerOutputToFiles,
|
||||||
|
exportVaultSyncPlansToFiles,
|
||||||
|
} from "./debugMode";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||||
s3: DEFAULT_S3_CONFIG,
|
s3: DEFAULT_S3_CONFIG,
|
||||||
@@ -89,6 +93,7 @@ type SyncTriggerSourceType = "manual" | "auto" | "dry" | "autoOnceInit";
|
|||||||
|
|
||||||
const iconNameSyncWait = `remotely-save-sync-wait`;
|
const iconNameSyncWait = `remotely-save-sync-wait`;
|
||||||
const iconNameSyncRunning = `remotely-save-sync-running`;
|
const iconNameSyncRunning = `remotely-save-sync-running`;
|
||||||
|
const iconNameLogs = `remotely-save-logs`;
|
||||||
|
|
||||||
const getIconSvg = () => {
|
const getIconSvg = () => {
|
||||||
const iconSvgSyncWait = createElement(RotateCcw);
|
const iconSvgSyncWait = createElement(RotateCcw);
|
||||||
@@ -97,13 +102,18 @@ const getIconSvg = () => {
|
|||||||
const iconSvgSyncRunning = createElement(RefreshCcw);
|
const iconSvgSyncRunning = createElement(RefreshCcw);
|
||||||
iconSvgSyncRunning.setAttribute("width", "100");
|
iconSvgSyncRunning.setAttribute("width", "100");
|
||||||
iconSvgSyncRunning.setAttribute("height", "100");
|
iconSvgSyncRunning.setAttribute("height", "100");
|
||||||
|
const iconSvgLogs = createElement(FileText);
|
||||||
|
iconSvgLogs.setAttribute("width", "100");
|
||||||
|
iconSvgLogs.setAttribute("height", "100");
|
||||||
const res = {
|
const res = {
|
||||||
iconSvgSyncWait: iconSvgSyncWait.outerHTML,
|
iconSvgSyncWait: iconSvgSyncWait.outerHTML,
|
||||||
iconSvgSyncRunning: iconSvgSyncRunning.outerHTML,
|
iconSvgSyncRunning: iconSvgSyncRunning.outerHTML,
|
||||||
|
iconSvgLogs: iconSvgLogs.outerHTML,
|
||||||
};
|
};
|
||||||
|
|
||||||
iconSvgSyncWait.empty();
|
iconSvgSyncWait.empty();
|
||||||
iconSvgSyncRunning.empty();
|
iconSvgSyncRunning.empty();
|
||||||
|
iconSvgLogs.empty();
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -204,7 +214,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
() => self.saveSettings()
|
() => self.saveSettings()
|
||||||
);
|
);
|
||||||
const remoteRsp = await client.listFromRemote();
|
const remoteRsp = await client.listFromRemote();
|
||||||
log.debug(remoteRsp);
|
// log.debug(remoteRsp);
|
||||||
|
|
||||||
getNotice(
|
getNotice(
|
||||||
t("syncrun_step3", {
|
t("syncrun_step3", {
|
||||||
@@ -369,10 +379,11 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
async onload() {
|
async onload() {
|
||||||
log.info(`loading plugin ${this.manifest.id}`);
|
log.info(`loading plugin ${this.manifest.id}`);
|
||||||
|
|
||||||
const { iconSvgSyncWait, iconSvgSyncRunning } = getIconSvg();
|
const { iconSvgSyncWait, iconSvgSyncRunning, iconSvgLogs } = getIconSvg();
|
||||||
|
|
||||||
addIcon(iconNameSyncWait, iconSvgSyncWait);
|
addIcon(iconNameSyncWait, iconSvgSyncWait);
|
||||||
addIcon(iconNameSyncRunning, iconSvgSyncRunning);
|
addIcon(iconNameSyncRunning, iconSvgSyncRunning);
|
||||||
|
addIcon(iconNameLogs, iconSvgLogs);
|
||||||
|
|
||||||
this.oauth2Info = {
|
this.oauth2Info = {
|
||||||
verifier: "",
|
verifier: "",
|
||||||
@@ -664,6 +675,50 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "export-sync-plans-json",
|
||||||
|
name: t("command_exportsyncplans_json"),
|
||||||
|
icon: iconNameLogs,
|
||||||
|
callback: async () => {
|
||||||
|
await exportVaultSyncPlansToFiles(
|
||||||
|
this.db,
|
||||||
|
this.app.vault,
|
||||||
|
this.vaultRandomID,
|
||||||
|
"json"
|
||||||
|
);
|
||||||
|
new Notice(t("settings_syncplans_notice"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "export-sync-plans-table",
|
||||||
|
name: t("command_exportsyncplans_table"),
|
||||||
|
icon: iconNameLogs,
|
||||||
|
callback: async () => {
|
||||||
|
await exportVaultSyncPlansToFiles(
|
||||||
|
this.db,
|
||||||
|
this.app.vault,
|
||||||
|
this.vaultRandomID,
|
||||||
|
"table"
|
||||||
|
);
|
||||||
|
new Notice(t("settings_syncplans_notice"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "export-logs-in-db",
|
||||||
|
name: t("command_exportlogsindb"),
|
||||||
|
icon: iconNameLogs,
|
||||||
|
callback: async () => {
|
||||||
|
await exportVaultLoggerOutputToFiles(
|
||||||
|
this.db,
|
||||||
|
this.app.vault,
|
||||||
|
this.vaultRandomID
|
||||||
|
);
|
||||||
|
new Notice(t("settings_logtodbexport_notice"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.addSettingTab(new RemotelySaveSettingTab(this.app, this));
|
this.addSettingTab(new RemotelySaveSettingTab(this.app, this));
|
||||||
|
|
||||||
// this.registerDomEvent(document, "click", (evt: MouseEvent) => {
|
// this.registerDomEvent(document, "click", (evt: MouseEvent) => {
|
||||||
|
|||||||
+29
@@ -3,6 +3,7 @@ import * as path from "path";
|
|||||||
|
|
||||||
import { base32, base64url } from "rfc4648";
|
import { base32, base64url } from "rfc4648";
|
||||||
import XRegExp from "xregexp";
|
import XRegExp from "xregexp";
|
||||||
|
import emojiRegex from "emoji-regex";
|
||||||
|
|
||||||
import { log } from "./moreOnLog";
|
import { log } from "./moreOnLog";
|
||||||
|
|
||||||
@@ -162,6 +163,34 @@ export const isVaildText = (a: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use regex to detect a text contains emoji or not.
|
||||||
|
* @param a
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const hasEmojiInText = (a: string) => {
|
||||||
|
const regex = emojiRegex();
|
||||||
|
return regex.test(a);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the headers to a normal object.
|
||||||
|
* @param h
|
||||||
|
* @param toLower
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const headersToRecord = (h: Headers, toLower: boolean = true) => {
|
||||||
|
const res: Record<string, string> = {};
|
||||||
|
h.forEach((v, k) => {
|
||||||
|
if (toLower) {
|
||||||
|
res[k.toLowerCase()] = v;
|
||||||
|
} else {
|
||||||
|
res[k] = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If input is already a folder, returns it as is;
|
* If input is already a folder, returns it as is;
|
||||||
* And if input is a file, returns its direname.
|
* And if input is a file, returns its direname.
|
||||||
|
|||||||
+148
-30
@@ -1,4 +1,6 @@
|
|||||||
import { Dropbox, DropboxAuth, files } from "dropbox";
|
import delay from "delay";
|
||||||
|
import { Dropbox, DropboxAuth } from "dropbox";
|
||||||
|
import type { files, DropboxResponseError, DropboxResponse } from "dropbox";
|
||||||
import { Vault } from "obsidian";
|
import { Vault } from "obsidian";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import {
|
import {
|
||||||
@@ -8,7 +10,13 @@ import {
|
|||||||
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||||
import { bufferToArrayBuffer, getFolderLevels, mkdirpInVault } from "./misc";
|
import {
|
||||||
|
bufferToArrayBuffer,
|
||||||
|
getFolderLevels,
|
||||||
|
hasEmojiInText,
|
||||||
|
headersToRecord,
|
||||||
|
mkdirpInVault,
|
||||||
|
} from "./misc";
|
||||||
|
|
||||||
export { Dropbox } from "dropbox";
|
export { Dropbox } from "dropbox";
|
||||||
|
|
||||||
@@ -270,6 +278,73 @@ export const setConfigBySuccessfullAuthInplace = async (
|
|||||||
// Other usual common methods
|
// Other usual common methods
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
interface ErrSubType {
|
||||||
|
error: {
|
||||||
|
retry_after: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryReq<T>(
|
||||||
|
reqFunc: () => Promise<DropboxResponse<T>>,
|
||||||
|
extraHint: string = ""
|
||||||
|
): Promise<DropboxResponse<T>> {
|
||||||
|
const waitSeconds = [1, 2, 4, 8]; // hard code exponential backoff
|
||||||
|
for (let idx = 0; idx < waitSeconds.length; ++idx) {
|
||||||
|
try {
|
||||||
|
if (idx !== 0) {
|
||||||
|
log.warn(
|
||||||
|
`${extraHint === "" ? "" : extraHint + ": "}The ${
|
||||||
|
idx + 1
|
||||||
|
}-th try starts at time ${Date.now()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return await reqFunc();
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const err = e as DropboxResponseError<ErrSubType>;
|
||||||
|
if (err.status === undefined) {
|
||||||
|
// then the err is not DropboxResponseError
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (err.status !== 429) {
|
||||||
|
// then the err is not "too many requests", give up
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx === waitSeconds.length - 1) {
|
||||||
|
// the last retry also failed, give up
|
||||||
|
throw new Error(
|
||||||
|
`${
|
||||||
|
extraHint === "" ? "" : extraHint + ": "
|
||||||
|
}"429 too many requests", after retrying for ${
|
||||||
|
idx + 1
|
||||||
|
} times still failed.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = headersToRecord(err.headers);
|
||||||
|
const svrSec =
|
||||||
|
err.error.error.retry_after ||
|
||||||
|
parseInt(headers["retry-after"] || "1") ||
|
||||||
|
1;
|
||||||
|
const fallbackSec = waitSeconds[idx];
|
||||||
|
const secMin = Math.max(svrSec, fallbackSec);
|
||||||
|
const secMax = Math.max(secMin * 1.8, 2);
|
||||||
|
log.warn(
|
||||||
|
`${
|
||||||
|
extraHint === "" ? "" : extraHint + ": "
|
||||||
|
}We have "429 too many requests" error of ${
|
||||||
|
idx + 1
|
||||||
|
}-th try, at time ${Date.now()}, and wait for ${secMin} ~ ${secMax} seconds to retry. Original info: ${JSON.stringify(
|
||||||
|
err.error,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
await delay.range(secMin * 1000, secMax * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class WrappedDropboxClient {
|
export class WrappedDropboxClient {
|
||||||
dropboxConfig: DropboxConfig;
|
dropboxConfig: DropboxConfig;
|
||||||
remoteBaseDir: string;
|
remoteBaseDir: string;
|
||||||
@@ -338,6 +413,13 @@ export class WrappedDropboxClient {
|
|||||||
}
|
}
|
||||||
if (!this.vaultFolderExists) {
|
if (!this.vaultFolderExists) {
|
||||||
log.info(`remote does not have folder /${this.remoteBaseDir}`);
|
log.info(`remote does not have folder /${this.remoteBaseDir}`);
|
||||||
|
|
||||||
|
if (hasEmojiInText(`/${this.remoteBaseDir}`)) {
|
||||||
|
throw new Error(
|
||||||
|
`/${this.remoteBaseDir}: Error: Dropbox does not support emoji in folder names.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.dropbox.filesCreateFolderV2({
|
await this.dropbox.filesCreateFolderV2({
|
||||||
path: `/${this.remoteBaseDir}`,
|
path: `/${this.remoteBaseDir}`,
|
||||||
});
|
});
|
||||||
@@ -377,10 +459,12 @@ export const getRemoteMeta = async (
|
|||||||
// filesGetMetadata doesn't support root folder
|
// filesGetMetadata doesn't support root folder
|
||||||
// we instead try to list files
|
// we instead try to list files
|
||||||
// if no error occurs, we ensemble a fake result.
|
// if no error occurs, we ensemble a fake result.
|
||||||
const rsp = await client.dropbox.filesListFolder({
|
const rsp = await retryReq(() =>
|
||||||
path: `/${client.remoteBaseDir}`,
|
client.dropbox.filesListFolder({
|
||||||
recursive: false, // don't need to recursive here
|
path: `/${client.remoteBaseDir}`,
|
||||||
});
|
recursive: false, // don't need to recursive here
|
||||||
|
})
|
||||||
|
);
|
||||||
if (rsp.status !== 200) {
|
if (rsp.status !== 200) {
|
||||||
throw Error(JSON.stringify(rsp));
|
throw Error(JSON.stringify(rsp));
|
||||||
}
|
}
|
||||||
@@ -395,9 +479,11 @@ export const getRemoteMeta = async (
|
|||||||
|
|
||||||
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
|
|
||||||
const rsp = await client.dropbox.filesGetMetadata({
|
const rsp = await retryReq(() =>
|
||||||
path: key,
|
client.dropbox.filesGetMetadata({
|
||||||
});
|
path: key,
|
||||||
|
})
|
||||||
|
);
|
||||||
if (rsp.status !== 200) {
|
if (rsp.status !== 200) {
|
||||||
throw Error(JSON.stringify(rsp));
|
throw Error(JSON.stringify(rsp));
|
||||||
}
|
}
|
||||||
@@ -423,6 +509,12 @@ export const uploadToRemote = async (
|
|||||||
}
|
}
|
||||||
uploadFile = getDropboxPath(uploadFile, client.remoteBaseDir);
|
uploadFile = getDropboxPath(uploadFile, client.remoteBaseDir);
|
||||||
|
|
||||||
|
if (hasEmojiInText(uploadFile)) {
|
||||||
|
throw new Error(
|
||||||
|
`${uploadFile}: Error: Dropbox does not support emoji in file / folder names.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const isFolder = fileOrFolderPath.endsWith("/");
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
if (isFolder && isRecursively) {
|
if (isFolder && isRecursively) {
|
||||||
@@ -438,11 +530,19 @@ export const uploadToRemote = async (
|
|||||||
// created, pass
|
// created, pass
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await client.dropbox.filesCreateFolderV2({
|
await retryReq(
|
||||||
path: uploadFile,
|
() =>
|
||||||
});
|
client.dropbox.filesCreateFolderV2({
|
||||||
|
path: uploadFile,
|
||||||
|
}),
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
foldersCreatedBefore?.add(uploadFile);
|
foldersCreatedBefore?.add(uploadFile);
|
||||||
} catch (err) {
|
} catch (e: unknown) {
|
||||||
|
const err = e as DropboxResponseError<files.CreateFolderError>;
|
||||||
|
if (err.status === undefined) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
if (err.status === 409) {
|
if (err.status === 409) {
|
||||||
// pass
|
// pass
|
||||||
foldersCreatedBefore?.add(uploadFile);
|
foldersCreatedBefore?.add(uploadFile);
|
||||||
@@ -455,10 +555,14 @@ export const uploadToRemote = async (
|
|||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
// if encrypted, upload a fake file with the encrypted file name
|
// if encrypted, upload a fake file with the encrypted file name
|
||||||
await client.dropbox.filesUpload({
|
await retryReq(
|
||||||
path: uploadFile,
|
() =>
|
||||||
contents: "",
|
client.dropbox.filesUpload({
|
||||||
});
|
path: uploadFile,
|
||||||
|
contents: "",
|
||||||
|
}),
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
return await getRemoteMeta(client, uploadFile);
|
return await getRemoteMeta(client, uploadFile);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -480,13 +584,19 @@ export const uploadToRemote = async (
|
|||||||
}
|
}
|
||||||
// in dropbox, we don't need to create folders before uploading! cool!
|
// in dropbox, we don't need to create folders before uploading! cool!
|
||||||
// TODO: filesUploadSession for larger files (>=150 MB)
|
// TODO: filesUploadSession for larger files (>=150 MB)
|
||||||
await client.dropbox.filesUpload({
|
|
||||||
path: uploadFile,
|
await retryReq(
|
||||||
contents: remoteContent,
|
() =>
|
||||||
mode: {
|
client.dropbox.filesUpload({
|
||||||
".tag": "overwrite",
|
path: uploadFile,
|
||||||
},
|
contents: remoteContent,
|
||||||
});
|
mode: {
|
||||||
|
".tag": "overwrite",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
|
|
||||||
// we want to mark that parent folders are created
|
// we want to mark that parent folders are created
|
||||||
if (foldersCreatedBefore !== undefined) {
|
if (foldersCreatedBefore !== undefined) {
|
||||||
const dirs = getFolderLevels(uploadFile).map((x) =>
|
const dirs = getFolderLevels(uploadFile).map((x) =>
|
||||||
@@ -554,9 +664,13 @@ const downloadFromRemoteRaw = async (
|
|||||||
) => {
|
) => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
const rsp = await client.dropbox.filesDownload({
|
const rsp = await retryReq(
|
||||||
path: key,
|
() =>
|
||||||
});
|
client.dropbox.filesDownload({
|
||||||
|
path: key,
|
||||||
|
}),
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
if ((rsp.result as any).fileBlob !== undefined) {
|
if ((rsp.result as any).fileBlob !== undefined) {
|
||||||
// we get a Blob
|
// we get a Blob
|
||||||
const content = (rsp.result as any).fileBlob as Blob;
|
const content = (rsp.result as any).fileBlob as Blob;
|
||||||
@@ -631,9 +745,13 @@ export const deleteFromRemote = async (
|
|||||||
|
|
||||||
await client.init();
|
await client.init();
|
||||||
try {
|
try {
|
||||||
await client.dropbox.filesDeleteV2({
|
await retryReq(
|
||||||
path: remoteFileName,
|
() =>
|
||||||
});
|
client.dropbox.filesDeleteV2({
|
||||||
|
path: remoteFileName,
|
||||||
|
}),
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("some error while deleting");
|
console.error("some error while deleting");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type {
|
|||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { request, requestUrl, requireApiVersion, Vault } from "obsidian";
|
import { request, requestUrl, requireApiVersion, Vault } from "obsidian";
|
||||||
import {
|
import {
|
||||||
API_VER_REQURL,
|
VALID_REQURL,
|
||||||
COMMAND_CALLBACK_ONEDRIVE,
|
COMMAND_CALLBACK_ONEDRIVE,
|
||||||
DEFAULT_CONTENT_TYPE,
|
DEFAULT_CONTENT_TYPE,
|
||||||
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
||||||
@@ -400,7 +400,7 @@ export class WrappedOnedriveClient {
|
|||||||
// log.info(`already checked, /${this.remoteBaseDir} exist before`)
|
// log.info(`already checked, /${this.remoteBaseDir} exist before`)
|
||||||
} else {
|
} else {
|
||||||
const k = await this.getJson("/drive/special/approot/children");
|
const k = await this.getJson("/drive/special/approot/children");
|
||||||
log.debug(k);
|
// log.debug(k);
|
||||||
this.vaultFolderExists =
|
this.vaultFolderExists =
|
||||||
(k.value as DriveItem[]).filter((x) => x.name === this.remoteBaseDir)
|
(k.value as DriveItem[]).filter((x) => x.name === this.remoteBaseDir)
|
||||||
.length > 0;
|
.length > 0;
|
||||||
@@ -484,7 +484,7 @@ export class WrappedOnedriveClient {
|
|||||||
deleteJson = async (pathFragOrig: string) => {
|
deleteJson = async (pathFragOrig: string) => {
|
||||||
const theUrl = this.buildUrl(pathFragOrig);
|
const theUrl = this.buildUrl(pathFragOrig);
|
||||||
log.debug(`deleteJson, theUrl=${theUrl}`);
|
log.debug(`deleteJson, theUrl=${theUrl}`);
|
||||||
if (requireApiVersion(API_VER_REQURL)) {
|
if (VALID_REQURL) {
|
||||||
await requestUrl({
|
await requestUrl({
|
||||||
url: theUrl,
|
url: theUrl,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
@@ -508,7 +508,7 @@ export class WrappedOnedriveClient {
|
|||||||
// TODO:
|
// TODO:
|
||||||
// 20220401: On Android, requestUrl has issue that text becomes base64.
|
// 20220401: On Android, requestUrl has issue that text becomes base64.
|
||||||
// Use fetch everywhere instead!
|
// Use fetch everywhere instead!
|
||||||
if (false /*requireApiVersion(API_VER_REQURL)*/) {
|
if (false /*VALID_REQURL*/) {
|
||||||
await requestUrl({
|
await requestUrl({
|
||||||
url: theUrl,
|
url: theUrl,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -556,7 +556,7 @@ export class WrappedOnedriveClient {
|
|||||||
// TODO:
|
// TODO:
|
||||||
// 20220401: On Android, requestUrl has issue that text becomes base64.
|
// 20220401: On Android, requestUrl has issue that text becomes base64.
|
||||||
// Use fetch everywhere instead!
|
// Use fetch everywhere instead!
|
||||||
if (false /*requireApiVersion(API_VER_REQURL)*/) {
|
if (false /*VALID_REQURL*/) {
|
||||||
const res = await requestUrl({
|
const res = await requestUrl({
|
||||||
url: theUrl,
|
url: theUrl,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -617,6 +617,7 @@ export const listFromRemote = async (
|
|||||||
`/drive/special/approot:/${client.remoteBaseDir}:/delta`
|
`/drive/special/approot:/${client.remoteBaseDir}:/delta`
|
||||||
);
|
);
|
||||||
let driveItems = res.value as DriveItem[];
|
let driveItems = res.value as DriveItem[];
|
||||||
|
// log.debug(driveItems);
|
||||||
|
|
||||||
while (NEXT_LINK_KEY in res) {
|
while (NEXT_LINK_KEY in res) {
|
||||||
res = await client.getJson(res[NEXT_LINK_KEY]);
|
res = await client.getJson(res[NEXT_LINK_KEY]);
|
||||||
@@ -807,7 +808,7 @@ const downloadFromRemoteRaw = async (
|
|||||||
`${key}?$select=@microsoft.graph.downloadUrl`
|
`${key}?$select=@microsoft.graph.downloadUrl`
|
||||||
);
|
);
|
||||||
const downloadUrl: string = rsp["@microsoft.graph.downloadUrl"];
|
const downloadUrl: string = rsp["@microsoft.graph.downloadUrl"];
|
||||||
if (requireApiVersion(API_VER_REQURL)) {
|
if (VALID_REQURL) {
|
||||||
const content = (await requestUrl({ url: downloadUrl })).arrayBuffer;
|
const content = (await requestUrl({ url: downloadUrl })).arrayBuffer;
|
||||||
return content;
|
return content;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+6
-1
@@ -109,6 +109,11 @@ class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
|
|
||||||
const raceOfPromises = [
|
const raceOfPromises = [
|
||||||
requestUrl(param).then((rsp) => {
|
requestUrl(param).then((rsp) => {
|
||||||
|
const headers = rsp.headers;
|
||||||
|
const headersLower: Record<string, string> = {};
|
||||||
|
for (const key of Object.keys(headers)) {
|
||||||
|
headersLower[key.toLowerCase()] = headers[key];
|
||||||
|
}
|
||||||
const stream = new ReadableStream<Uint8Array>({
|
const stream = new ReadableStream<Uint8Array>({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
controller.enqueue(new Uint8Array(rsp.arrayBuffer));
|
controller.enqueue(new Uint8Array(rsp.arrayBuffer));
|
||||||
@@ -117,7 +122,7 @@ class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
response: new HttpResponse({
|
response: new HttpResponse({
|
||||||
headers: rsp.headers,
|
headers: headersLower,
|
||||||
statusCode: rsp.status,
|
statusCode: rsp.status,
|
||||||
body: stream,
|
body: stream,
|
||||||
}),
|
}),
|
||||||
|
|||||||
+70
-39
@@ -7,6 +7,8 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
requireApiVersion,
|
requireApiVersion,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
|
import type { TextComponent } from "obsidian";
|
||||||
|
import { createElement, Eye, EyeOff } from "lucide";
|
||||||
import {
|
import {
|
||||||
API_VER_REQURL,
|
API_VER_REQURL,
|
||||||
DEFAULT_DEBUG_FOLDER,
|
DEFAULT_DEBUG_FOLDER,
|
||||||
@@ -627,8 +629,33 @@ class ExportSettingsQrCodeModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getEyesElements = () => {
|
||||||
|
const eyeEl = createElement(Eye);
|
||||||
|
const eyeOffEl = createElement(EyeOff);
|
||||||
|
return {
|
||||||
|
eye: eyeEl.outerHTML,
|
||||||
|
eyeOff: eyeOffEl.outerHTML,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapTextWithPasswordHide = (text: TextComponent) => {
|
||||||
|
const { eye, eyeOff } = getEyesElements();
|
||||||
|
const hider = text.inputEl.insertAdjacentElement("afterend", createSpan());
|
||||||
|
// the init type of hider is "hidden" === eyeOff === password
|
||||||
|
hider.innerHTML = eyeOff;
|
||||||
|
hider.addEventListener("click", (e) => {
|
||||||
|
const isText = text.inputEl.getAttribute("type") === "text";
|
||||||
|
text.inputEl.setAttribute("type", isText ? "password" : "text");
|
||||||
|
hider.innerHTML = isText ? eyeOff : eye;
|
||||||
|
});
|
||||||
|
|
||||||
|
// the init type of text el is password
|
||||||
|
text.inputEl.setAttribute("type", "password");
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
export class RemotelySaveSettingTab extends PluginSettingTab {
|
export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
plugin: RemotelySavePlugin;
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
|
||||||
constructor(app: App, plugin: RemotelySavePlugin) {
|
constructor(app: App, plugin: RemotelySavePlugin) {
|
||||||
super(app, plugin);
|
super(app, plugin);
|
||||||
@@ -658,14 +685,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
new Setting(passwordDiv)
|
new Setting(passwordDiv)
|
||||||
.setName(t("settings_password"))
|
.setName(t("settings_password"))
|
||||||
.setDesc(t("settings_password_desc"))
|
.setDesc(t("settings_password_desc"))
|
||||||
.addText((text) =>
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(`${this.plugin.settings.password}`)
|
.setValue(`${this.plugin.settings.password}`)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
newPassword = value.trim();
|
newPassword = value.trim();
|
||||||
})
|
});
|
||||||
)
|
})
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
button.setButtonText(t("confirm"));
|
button.setButtonText(t("confirm"));
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
@@ -816,29 +844,31 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
new Setting(s3Div)
|
new Setting(s3Div)
|
||||||
.setName(t("settings_s3_accesskeyid"))
|
.setName(t("settings_s3_accesskeyid"))
|
||||||
.setDesc(t("settings_s3_accesskeyid"))
|
.setDesc(t("settings_s3_accesskeyid_desc"))
|
||||||
.addText((text) =>
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(`${this.plugin.settings.s3.s3AccessKeyID}`)
|
.setValue(`${this.plugin.settings.s3.s3AccessKeyID}`)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.s3.s3AccessKeyID = value.trim();
|
this.plugin.settings.s3.s3AccessKeyID = value.trim();
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
new Setting(s3Div)
|
new Setting(s3Div)
|
||||||
.setName(t("settings_s3_secretaccesskey"))
|
.setName(t("settings_s3_secretaccesskey"))
|
||||||
.setDesc(t("settings_s3_secretaccesskey"))
|
.setDesc(t("settings_s3_secretaccesskey_desc"))
|
||||||
.addText((text) =>
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(`${this.plugin.settings.s3.s3SecretAccessKey}`)
|
.setValue(`${this.plugin.settings.s3.s3SecretAccessKey}`)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.s3.s3SecretAccessKey = value.trim();
|
this.plugin.settings.s3.s3SecretAccessKey = value.trim();
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
new Setting(s3Div)
|
new Setting(s3Div)
|
||||||
.setName(t("settings_s3_bucketname"))
|
.setName(t("settings_s3_bucketname"))
|
||||||
@@ -877,11 +907,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
if (VALID_REQURL) {
|
if (VALID_REQURL) {
|
||||||
new Setting(s3Div)
|
new Setting(s3Div)
|
||||||
.setName(t("settings_s3_bypasscorslocally"))
|
.setName(t("settings_s3_bypasscorslocally"))
|
||||||
.setDesc(
|
.setDesc(t("settings_s3_bypasscorslocally_desc"))
|
||||||
t("settings_s3_bypasscorslocally_desc", {
|
|
||||||
ver: API_VER_REQURL,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
dropdown
|
dropdown
|
||||||
.addOption("disable", t("disable"))
|
.addOption("disable", t("disable"))
|
||||||
@@ -1297,15 +1323,9 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!VALID_REQURL) {
|
if (!VALID_REQURL) {
|
||||||
if (Platform.isAndroidApp) {
|
webdavDiv.createEl("p", {
|
||||||
webdavDiv.createEl("p", {
|
text: t("settings_webdav_cors_os"),
|
||||||
text: t("settings_webdav_cors_android"),
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
webdavDiv.createEl("p", {
|
|
||||||
text: t("settings_webdav_cors_otheros"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
webdavDiv.createEl("p", {
|
webdavDiv.createEl("p", {
|
||||||
text: t("settings_webdav_cors"),
|
text: t("settings_webdav_cors"),
|
||||||
@@ -1346,7 +1366,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
new Setting(webdavDiv)
|
new Setting(webdavDiv)
|
||||||
.setName(t("settings_webdav_user"))
|
.setName(t("settings_webdav_user"))
|
||||||
.setDesc(t("settings_webdav_user_desc"))
|
.setDesc(t("settings_webdav_user_desc"))
|
||||||
.addText((text) =>
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(this.plugin.settings.webdav.username)
|
.setValue(this.plugin.settings.webdav.username)
|
||||||
@@ -1359,13 +1380,14 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
this.plugin.settings.webdav.depth = "auto_unknown";
|
||||||
}
|
}
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
new Setting(webdavDiv)
|
new Setting(webdavDiv)
|
||||||
.setName(t("settings_webdav_password"))
|
.setName(t("settings_webdav_password"))
|
||||||
.setDesc(t("settings_webdav_password_desc"))
|
.setDesc(t("settings_webdav_password_desc"))
|
||||||
.addText((text) =>
|
.addText((text) => {
|
||||||
|
wrapTextWithPasswordHide(text);
|
||||||
text
|
text
|
||||||
.setPlaceholder("")
|
.setPlaceholder("")
|
||||||
.setValue(this.plugin.settings.webdav.password)
|
.setValue(this.plugin.settings.webdav.password)
|
||||||
@@ -1378,8 +1400,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
this.plugin.settings.webdav.depth = "auto_unknown";
|
||||||
}
|
}
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
new Setting(webdavDiv)
|
new Setting(webdavDiv)
|
||||||
.setName(t("settings_webdav_auth"))
|
.setName(t("settings_webdav_auth"))
|
||||||
@@ -1678,11 +1700,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
button.setButtonText(t("settings_outputsettingsconsole_button"));
|
button.setButtonText(t("settings_outputsettingsconsole_button"));
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
const c = messyConfigToNormal(await this.plugin.loadData());
|
const c = messyConfigToNormal(await this.plugin.loadData());
|
||||||
if (c.currLogLevel === "debug") {
|
log.info(c);
|
||||||
// no need to ouput it again because debug mode already output it
|
|
||||||
} else {
|
|
||||||
log.info(c);
|
|
||||||
}
|
|
||||||
new Notice(t("settings_outputsettingsconsole_notice"));
|
new Notice(t("settings_outputsettingsconsole_notice"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1691,12 +1709,25 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setName(t("settings_syncplans"))
|
.setName(t("settings_syncplans"))
|
||||||
.setDesc(t("settings_syncplans_desc"))
|
.setDesc(t("settings_syncplans_desc"))
|
||||||
.addButton(async (button) => {
|
.addButton(async (button) => {
|
||||||
button.setButtonText(t("settings_syncplans_button"));
|
button.setButtonText(t("settings_syncplans_button_json"));
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
await exportVaultSyncPlansToFiles(
|
await exportVaultSyncPlansToFiles(
|
||||||
this.plugin.db,
|
this.plugin.db,
|
||||||
this.app.vault,
|
this.app.vault,
|
||||||
this.plugin.vaultRandomID
|
this.plugin.vaultRandomID,
|
||||||
|
"json"
|
||||||
|
);
|
||||||
|
new Notice(t("settings_syncplans_notice"));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText(t("settings_syncplans_button_table"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
await exportVaultSyncPlansToFiles(
|
||||||
|
this.plugin.db,
|
||||||
|
this.app.vault,
|
||||||
|
this.plugin.vaultRandomID,
|
||||||
|
"table"
|
||||||
);
|
);
|
||||||
new Notice(t("settings_syncplans_notice"));
|
new Notice(t("settings_syncplans_notice"));
|
||||||
});
|
});
|
||||||
|
|||||||
+9
-3
@@ -459,9 +459,15 @@ const ensembleMixedStates = async (
|
|||||||
results[key].changeLocalMtimeUsingMapping =
|
results[key].changeLocalMtimeUsingMapping =
|
||||||
r.changeLocalMtimeUsingMapping;
|
r.changeLocalMtimeUsingMapping;
|
||||||
} else {
|
} else {
|
||||||
results[key] = r;
|
// So, the file doesn't exist,
|
||||||
results[key].existLocal = false; // we have already checked local
|
// except that it existed in the "renamed to" history records.
|
||||||
results[key].existRemote = false; // we have already checked remote
|
// Most likely because that the user deleted the file while Obsidian was closed,
|
||||||
|
// so Obsidian could not track the deletions.
|
||||||
|
// We are not sure how to deal with this, so do not generate anything here!
|
||||||
|
// // // The following 3 lines are of old logic, and have been removed:
|
||||||
|
// // results[key] = r;
|
||||||
|
// // results[key].existLocal = false; // we have already checked local
|
||||||
|
// // results[key].existRemote = false; // we have already checked remote
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Error(
|
throw Error(
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"0.3.2": "0.12.15",
|
"0.3.2": "0.12.15",
|
||||||
"0.3.17": "0.13.21"
|
"0.3.19": "0.13.21"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user