mirror of
https://github.com/jvsteiner/send-note.git
synced 2026-07-22 11:20:26 +00:00
converted to use aws
This commit is contained in:
parent
bed608b97b
commit
686bf8aaec
6 changed files with 2778 additions and 179 deletions
1
declare.d.ts
vendored
Normal file
1
declare.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
declare module "@smithy/fetch-http-handler/dist-es/request-timeout";
|
||||
2289
package-lock.json
generated
2289
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -16,16 +16,20 @@
|
|||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4",
|
||||
"browser-image-compression": "^2.0.2"
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.693.0",
|
||||
"@aws-sdk/protocol-http": "^3.370.0",
|
||||
"@aws-sdk/querystring-builder": "^3.370.0",
|
||||
"@smithy/fetch-http-handler": "^4.1.1",
|
||||
"csso": "^5.0.5",
|
||||
"data-uri-to-buffer": "^6.0.1"
|
||||
}
|
||||
|
|
|
|||
245
src/main.ts
245
src/main.ts
|
|
@ -1,16 +1,24 @@
|
|||
import { Plugin, requestUrl, setIcon, TFile } from "obsidian";
|
||||
import { Plugin, requestUrl, setIcon, TFile, RequestUrlParam } from "obsidian";
|
||||
import { DEFAULT_SETTINGS, SendNoteSettings, SendNoteSettingsTab, YamlField } from "./settings";
|
||||
import Note, { SharedNote } from "./note";
|
||||
import API, { parseExistingShareUrl } from "./api";
|
||||
import StatusMessage, { StatusType } from "./StatusMessage";
|
||||
import { shortHash, sha256, decryptString } from "./crypto";
|
||||
import UI from "./UI";
|
||||
import { S3Client, PutObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
||||
import { HttpHandlerOptions } from "@aws-sdk/types";
|
||||
import { buildQueryString } from "@aws-sdk/querystring-builder";
|
||||
import { requestTimeout } from "@smithy/fetch-http-handler/dist-es/request-timeout";
|
||||
import { FetchHttpHandler, FetchHttpHandlerOptions } from "@smithy/fetch-http-handler";
|
||||
import { HttpRequest, HttpResponse } from "@aws-sdk/protocol-http";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
export default class SendNotePlugin extends Plugin {
|
||||
settings: SendNoteSettings;
|
||||
api: API;
|
||||
settingsPage: SendNoteSettingsTab;
|
||||
ui: UI;
|
||||
s3: S3Client;
|
||||
|
||||
// Expose some tools in the plugin object
|
||||
hash = shortHash;
|
||||
|
|
@ -23,8 +31,28 @@ export default class SendNotePlugin extends Plugin {
|
|||
this.settingsPage = new SendNoteSettingsTab(this.app, this);
|
||||
this.addSettingTab(this.settingsPage);
|
||||
|
||||
let apiEndpoint = this.settings.useCustomEndpoint
|
||||
? this.settings.customEndpoint
|
||||
: `https://s3.${this.settings.region}.amazonaws.com/`;
|
||||
this.settings.imageUrlPath = this.settings.useCustomImageUrl
|
||||
? this.settings.customImageUrl
|
||||
: this.settings.forcePathStyle
|
||||
? apiEndpoint + this.settings.bucket + "/"
|
||||
: apiEndpoint.replace("://", `://${this.settings.bucket}.`);
|
||||
|
||||
// Initialise the backend API
|
||||
this.ui = new UI(this.app);
|
||||
this.s3 = new S3Client({
|
||||
region: this.settings.region,
|
||||
credentials: {
|
||||
// clientConfig: { region: this.settings.region },
|
||||
accessKeyId: this.settings.accessKey,
|
||||
secretAccessKey: this.settings.secretKey,
|
||||
},
|
||||
endpoint: apiEndpoint,
|
||||
// forcePathStyle: this.settings.forcePathStyle,
|
||||
requestHandler: new ObsHttpHandler({ keepAlive: false }),
|
||||
});
|
||||
|
||||
// To get an API key, we send the user to a Cloudflare Turnstile page to verify they are a human,
|
||||
// as a way to prevent abuse. The key is then sent back to Obsidian via this URI handler.
|
||||
|
|
@ -126,6 +154,43 @@ export default class SendNotePlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
async uploadFile(content: string, key: string): Promise<string> {
|
||||
// const buf = await file.arrayBuffer();
|
||||
|
||||
let folder = this.settings.folder;
|
||||
|
||||
const currentDate = new Date();
|
||||
folder = folder
|
||||
.replace("${year}", currentDate.getFullYear().toString())
|
||||
.replace("${month}", String(currentDate.getMonth() + 1).padStart(2, "0"))
|
||||
.replace("${day}", String(currentDate.getDate()).padStart(2, "0"));
|
||||
|
||||
const keyHash = md5Hash(key);
|
||||
|
||||
key = folder ? `${folder}/${keyHash}` : keyHash;
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const utf8Array = encoder.encode(content);
|
||||
|
||||
await this.s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: this.settings.bucket,
|
||||
Key: key,
|
||||
Body: utf8Array,
|
||||
ContentType: "text/plain",
|
||||
})
|
||||
);
|
||||
let urlString = this.settings.imageUrlPath + key;
|
||||
if (this.settings.queryStringKey && this.settings.queryStringValue) {
|
||||
let urlObject = new URL(urlString);
|
||||
|
||||
// The searchParams property provides methods to manipulate query parameters
|
||||
urlObject.searchParams.append(this.settings.queryStringKey, this.settings.queryStringValue);
|
||||
urlString = urlObject.toString();
|
||||
}
|
||||
return urlString;
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
||||
async loadSettings() {
|
||||
|
|
@ -292,33 +357,52 @@ export default class SendNotePlugin extends Plugin {
|
|||
}
|
||||
|
||||
async deletePaste(pasteKey: string) {
|
||||
const url = "https://pastebin.com/api/api_post.php";
|
||||
|
||||
const formData = new URLSearchParams();
|
||||
formData.append("api_dev_key", this.settings.pastebinApiKey);
|
||||
formData.append("api_user_key", this.settings.pastebinUserKey);
|
||||
formData.append("api_paste_key", pasteKey);
|
||||
formData.append("api_option", "delete");
|
||||
|
||||
try {
|
||||
const response = await requestUrl({
|
||||
url: url,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: formData.toString(),
|
||||
this.s3
|
||||
.send(
|
||||
new DeleteObjectsCommand({
|
||||
Bucket: this.settings.bucket,
|
||||
Delete: {
|
||||
Objects: [
|
||||
{
|
||||
Key: pasteKey,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
)
|
||||
.then((data) => {
|
||||
console.log("data", data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("error", err);
|
||||
});
|
||||
// const url = "https://pastebin.com/api/api_post.php";
|
||||
|
||||
const result = await response.text;
|
||||
// const formData = new URLSearchParams();
|
||||
// // formData.append("api_dev_key", this.settings.pastebinApiKey);
|
||||
// // formData.append("api_user_key", this.settings.pastebinUserKey);
|
||||
// formData.append("api_paste_key", pasteKey);
|
||||
// formData.append("api_option", "delete");
|
||||
|
||||
if (result.trim() === "Paste Removed") {
|
||||
} else {
|
||||
console.error("Error deleting paste:", result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Network or parsing error:", error);
|
||||
}
|
||||
// try {
|
||||
// const response = await requestUrl({
|
||||
// url: url,
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/x-www-form-urlencoded",
|
||||
// },
|
||||
// body: formData.toString(),
|
||||
// });
|
||||
|
||||
// const result = await response.text;
|
||||
|
||||
// if (result.trim() === "Paste Removed") {
|
||||
// } else {
|
||||
// console.error("Error deleting paste:", result);
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Network or parsing error:", error);
|
||||
// }
|
||||
}
|
||||
|
||||
private generateRandomString(length = 5) {
|
||||
|
|
@ -349,13 +433,13 @@ export default class SendNotePlugin extends Plugin {
|
|||
const sendUrlObj = new URL(sendUrlParam);
|
||||
|
||||
// Extract the pathname from the URL
|
||||
const pathname = sendUrlObj.pathname;
|
||||
// const pathname = sendUrlObj.pathname;
|
||||
|
||||
// Extract and return the identifier from the pathname
|
||||
// Assuming the identifier is the part after the last '/' in the pathname
|
||||
const identifier = pathname.substring(pathname.lastIndexOf("/") + 1);
|
||||
// const identifier = pathname.substring(pathname.lastIndexOf("/") + 1);
|
||||
|
||||
return identifier;
|
||||
return sendUrlObj.pathname.slice(1);
|
||||
} else {
|
||||
throw new Error("sendurl parameter not found");
|
||||
}
|
||||
|
|
@ -365,3 +449,108 @@ export default class SendNotePlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is close to origin implementation of FetchHttpHandler
|
||||
* https://github.com/aws/aws-sdk-js-v3/blob/main/packages/fetch-http-handler/src/fetch-http-handler.ts
|
||||
* that is released under Apache 2 License.
|
||||
* But this uses Obsidian requestUrl instead.
|
||||
*/
|
||||
class ObsHttpHandler extends FetchHttpHandler {
|
||||
requestTimeoutInMs: number | undefined;
|
||||
constructor(options?: FetchHttpHandlerOptions) {
|
||||
super(options);
|
||||
this.requestTimeoutInMs = options === undefined ? undefined : options.requestTimeout;
|
||||
}
|
||||
async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> {
|
||||
if (abortSignal?.aborted) {
|
||||
const abortError = new Error("Request aborted");
|
||||
abortError.name = "AbortError";
|
||||
return Promise.reject(abortError);
|
||||
}
|
||||
|
||||
let path = request.path;
|
||||
if (request.query) {
|
||||
const queryString = buildQueryString(request.query);
|
||||
if (queryString) {
|
||||
path += `?${queryString}`;
|
||||
}
|
||||
}
|
||||
|
||||
const { port, method } = request;
|
||||
const url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ""}${path}`;
|
||||
const body = method === "GET" || method === "HEAD" ? undefined : request.body;
|
||||
|
||||
const transformedHeaders: Record<string, string> = {};
|
||||
for (const key of Object.keys(request.headers)) {
|
||||
const keyLower = key.toLowerCase();
|
||||
if (keyLower === "host" || keyLower === "content-length") {
|
||||
continue;
|
||||
}
|
||||
transformedHeaders[keyLower] = request.headers[key];
|
||||
}
|
||||
|
||||
let contentType: string | undefined = undefined;
|
||||
if (transformedHeaders["content-type"] !== undefined) {
|
||||
contentType = transformedHeaders["content-type"];
|
||||
}
|
||||
|
||||
let transformedBody: any = body;
|
||||
if (ArrayBuffer.isView(body)) {
|
||||
transformedBody = bufferToArrayBuffer(body);
|
||||
}
|
||||
|
||||
const param: RequestUrlParam = {
|
||||
body: transformedBody,
|
||||
headers: transformedHeaders,
|
||||
method: method,
|
||||
url: url,
|
||||
contentType: contentType,
|
||||
};
|
||||
|
||||
const raceOfPromises = [
|
||||
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>({
|
||||
start(controller) {
|
||||
controller.enqueue(new Uint8Array(rsp.arrayBuffer));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
return {
|
||||
response: new HttpResponse({
|
||||
headers: headersLower,
|
||||
statusCode: rsp.status,
|
||||
body: stream,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
requestTimeout(this.requestTimeoutInMs),
|
||||
];
|
||||
|
||||
if (abortSignal) {
|
||||
raceOfPromises.push(
|
||||
new Promise<never>((resolve, reject) => {
|
||||
abortSignal.onabort = () => {
|
||||
const abortError = new Error("Request aborted");
|
||||
abortError.name = "AbortError";
|
||||
reject(abortError);
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.race(raceOfPromises);
|
||||
}
|
||||
}
|
||||
|
||||
const bufferToArrayBuffer = (b: Buffer | Uint8Array | ArrayBufferView) => {
|
||||
return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
|
||||
};
|
||||
|
||||
function md5Hash(text: string): string {
|
||||
return crypto.createHash("md5").update(text, "utf8").digest("hex");
|
||||
}
|
||||
|
|
|
|||
91
src/note.ts
91
src/note.ts
|
|
@ -94,40 +94,34 @@ export default class Note {
|
|||
}
|
||||
});
|
||||
|
||||
const pastebinData = {
|
||||
api_dev_key: this.plugin.settings.pastebinApiKey,
|
||||
api_user_key: this.plugin.settings.pastebinUserKey,
|
||||
api_option: "paste",
|
||||
api_paste_private: 1,
|
||||
api_paste_name: this.file.basename,
|
||||
api_paste_expire_date: this.plugin.settings.pastebinExpiry,
|
||||
api_paste_format: "text",
|
||||
api_paste_code: encodeURIComponent(noteContent),
|
||||
};
|
||||
// const pastebinData = {
|
||||
// api_dev_key: this.plugin.settings.pastebinApiKey,
|
||||
// api_user_key: this.plugin.settings.pastebinUserKey,
|
||||
// api_option: "paste",
|
||||
// api_paste_private: 1,
|
||||
// api_paste_name: this.file.basename,
|
||||
// api_paste_expire_date: this.plugin.settings.pastebinExpiry,
|
||||
// api_paste_format: "text",
|
||||
// api_paste_code: encodeURIComponent(noteContent),
|
||||
// };
|
||||
|
||||
const body = this.createQueryString(pastebinData);
|
||||
|
||||
requestUrl({
|
||||
url: "https://pastebin.com/api/api_post.php",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: body,
|
||||
})
|
||||
// const body = this.createQueryString(pastebinData);
|
||||
this.plugin
|
||||
.uploadFile(noteContent, this.file.basename)
|
||||
.then((res) => {
|
||||
const responseText = res.text.trim();
|
||||
console.log(res);
|
||||
const responseText = res.trim();
|
||||
if (!responseText.startsWith("http")) {
|
||||
throw new Error("Invalid URL response from Pastebin");
|
||||
throw new Error("Invalid URL response from AWS");
|
||||
}
|
||||
const urlObj = new URL(responseText);
|
||||
const pathSegments = urlObj.pathname.split("/");
|
||||
const suffix = pathSegments[pathSegments.length - 1];
|
||||
let obsidianUrl = "";
|
||||
if (shareUnencrypted) {
|
||||
obsidianUrl = `obsidian://send-note?sendurl=https://pastebin.com/raw/${suffix}&filename=${this.file.basename}.md`;
|
||||
obsidianUrl = `obsidian://send-note?sendurl=${responseText}&filename=${this.file.basename}.md`;
|
||||
} else {
|
||||
obsidianUrl = `obsidian://send-note?sendurl=https://pastebin.com/raw/${suffix}&filename=${this.file.basename}.md&encrypted=true&key=${encryptionKey}`;
|
||||
obsidianUrl = `obsidian://send-note?sendurl=${responseText}&filename=${this.file.basename}.md&encrypted=true&key=${encryptionKey}`;
|
||||
}
|
||||
// console.log(obsidianUrl);
|
||||
navigator.clipboard.writeText(obsidianUrl);
|
||||
|
|
@ -139,14 +133,49 @@ export default class Note {
|
|||
this.plugin.addShareIcons();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
this.status = new StatusMessage(
|
||||
err +
|
||||
". The note uploading failed. If you got status 422, it might be because you have exceeded your allowed number of pastes for 24 hours.",
|
||||
StatusType.Error,
|
||||
5 * 1000
|
||||
);
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// requestUrl({
|
||||
// url: "https://pastebin.com/api/api_post.php",
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/x-www-form-urlencoded",
|
||||
// },
|
||||
// body: body,
|
||||
// })
|
||||
// .then((res) => {
|
||||
// const responseText = res.text.trim();
|
||||
// if (!responseText.startsWith("http")) {
|
||||
// throw new Error("Invalid URL response from Pastebin");
|
||||
// }
|
||||
// const urlObj = new URL(responseText);
|
||||
// const pathSegments = urlObj.pathname.split("/");
|
||||
// const suffix = pathSegments[pathSegments.length - 1];
|
||||
// let obsidianUrl = "";
|
||||
// if (shareUnencrypted) {
|
||||
// obsidianUrl = `obsidian://send-note?sendurl=https://pastebin.com/raw/${suffix}&filename=${this.file.basename}.md`;
|
||||
// } else {
|
||||
// obsidianUrl = `obsidian://send-note?sendurl=https://pastebin.com/raw/${suffix}&filename=${this.file.basename}.md&encrypted=true&key=${encryptionKey}`;
|
||||
// }
|
||||
// // console.log(obsidianUrl);
|
||||
// navigator.clipboard.writeText(obsidianUrl);
|
||||
// this.plugin.app.fileManager.processFrontMatter(this.file, (frontmatter) => {
|
||||
// if ((frontmatter["send_link"] = true)) {
|
||||
// frontmatter["send_link"] = obsidianUrl;
|
||||
// }
|
||||
// });
|
||||
// this.plugin.addShareIcons();
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// console.log("err", err);
|
||||
// this.status = new StatusMessage(
|
||||
// err +
|
||||
// ". The note uploading failed. If you got status 422, it might be because you have exceeded your allowed number of pastes for 24 hours.",
|
||||
// StatusType.Error,
|
||||
// 5 * 1000
|
||||
// );
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
323
src/settings.ts
323
src/settings.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { App, PluginSettingTab, Setting, TextComponent, requestUrl } from "obsidian";
|
||||
import { App, PluginSettingTab, Setting, TextComponent, requestUrl, setIcon } from "obsidian";
|
||||
import SendNotePlugin from "./main";
|
||||
import StatusMessage, { StatusType } from "./StatusMessage";
|
||||
|
||||
|
|
@ -30,12 +30,21 @@ export interface SendNoteSettings {
|
|||
clipboard: boolean;
|
||||
shareUnencrypted: boolean;
|
||||
debug: number;
|
||||
pastebinApiKey: string;
|
||||
pastebinUsername: string;
|
||||
pastebinPassword: string;
|
||||
pastebinUserKey: string;
|
||||
pastebinPublic: string;
|
||||
pastebinExpiry: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
region: string;
|
||||
bucket: string;
|
||||
folder: string;
|
||||
expiry: string;
|
||||
imageUrlPath: string;
|
||||
useCustomEndpoint: boolean;
|
||||
customEndpoint: string;
|
||||
forcePathStyle: boolean;
|
||||
useCustomImageUrl: boolean;
|
||||
customImageUrl: string;
|
||||
bypassCors: boolean;
|
||||
queryStringValue: string;
|
||||
queryStringKey: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: SendNoteSettings = {
|
||||
|
|
@ -45,12 +54,21 @@ export const DEFAULT_SETTINGS: SendNoteSettings = {
|
|||
clipboard: true,
|
||||
shareUnencrypted: false,
|
||||
debug: 0,
|
||||
pastebinApiKey: "",
|
||||
pastebinUsername: "",
|
||||
pastebinPassword: "",
|
||||
pastebinUserKey: "",
|
||||
pastebinPublic: "1",
|
||||
pastebinExpiry: "1D",
|
||||
accessKey: "",
|
||||
secretKey: "",
|
||||
region: "eu-west-2",
|
||||
bucket: "",
|
||||
folder: "",
|
||||
expiry: "24",
|
||||
imageUrlPath: "",
|
||||
useCustomEndpoint: false,
|
||||
customEndpoint: "",
|
||||
forcePathStyle: false,
|
||||
useCustomImageUrl: false,
|
||||
customImageUrl: "",
|
||||
bypassCors: false,
|
||||
queryStringValue: "",
|
||||
queryStringKey: "",
|
||||
};
|
||||
|
||||
export class SendNoteSettingsTab extends PluginSettingTab {
|
||||
|
|
@ -63,102 +81,175 @@ export class SendNoteSettingsTab extends PluginSettingTab {
|
|||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
setUserKey(): void {
|
||||
if (
|
||||
this.plugin.settings.pastebinApiKey &&
|
||||
this.plugin.settings.pastebinUsername &&
|
||||
this.plugin.settings.pastebinPassword
|
||||
) {
|
||||
this.getUserKey(
|
||||
this.plugin.settings.pastebinApiKey,
|
||||
this.plugin.settings.pastebinUsername,
|
||||
this.plugin.settings.pastebinPassword
|
||||
).then((key) => {
|
||||
this.plugin.settings.pastebinUserKey = key;
|
||||
this.plugin.saveSettings();
|
||||
console.log("Set user key", key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
// Pastebin API key
|
||||
new Setting(containerEl)
|
||||
.setName("Pastebin API key")
|
||||
.setDesc("Pastebin API key")
|
||||
.addText((inputEl) => {
|
||||
inputEl
|
||||
.setPlaceholder("Pastebin API key")
|
||||
.setValue(this.plugin.settings.pastebinApiKey)
|
||||
.setName("AWS Access Key ID")
|
||||
.setDesc("AWS access key ID for a user with S3 access.")
|
||||
.addText((text) => {
|
||||
wrapTextWithPasswordHide(text);
|
||||
text
|
||||
.setPlaceholder("access key")
|
||||
.setValue(this.plugin.settings.accessKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pastebinApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.setUserKey();
|
||||
});
|
||||
});
|
||||
|
||||
// Pastebin username
|
||||
new Setting(containerEl)
|
||||
.setName("Pastebin username")
|
||||
.setDesc("Pastebin username")
|
||||
.addText((inputEl) => {
|
||||
inputEl
|
||||
.setPlaceholder("Pastebin username")
|
||||
.setValue(this.plugin.settings.pastebinUsername)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pastebinUsername = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.setUserKey();
|
||||
});
|
||||
});
|
||||
|
||||
// Pastebin password
|
||||
new Setting(containerEl)
|
||||
.setName("Pastebin password")
|
||||
.setDesc("Pastebin password")
|
||||
.addText((inputEl) => {
|
||||
inputEl
|
||||
.setPlaceholder("Pastebin password")
|
||||
.setValue(this.plugin.settings.pastebinPassword)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pastebinPassword = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.setUserKey();
|
||||
});
|
||||
});
|
||||
|
||||
// Pastebin expiry period
|
||||
new Setting(containerEl)
|
||||
.setName("Pastebin expiry")
|
||||
.setDesc("Pastebin expiry period, ie.: N, 1D, 1W, 1M, 1Y")
|
||||
.addText((inputEl) => {
|
||||
inputEl
|
||||
.setPlaceholder("Pastebin expiry period")
|
||||
.setValue(this.plugin.settings.pastebinExpiry)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pastebinExpiry = value;
|
||||
this.plugin.settings.accessKey = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
// Pastebin public setting
|
||||
new Setting(containerEl)
|
||||
.setName("Pastebin public setting")
|
||||
.setDesc("Pastebin public: 0 for public, 1 for unlisted, 2 for private. Default is 1")
|
||||
.addText((inputEl) => {
|
||||
inputEl
|
||||
.setPlaceholder("Pastebinpublic setting")
|
||||
.setValue(this.plugin.settings.pastebinPublic)
|
||||
.setName("AWS Secret Key")
|
||||
.setDesc("AWS secret key for that user.")
|
||||
.addText((text) => {
|
||||
wrapTextWithPasswordHide(text);
|
||||
text
|
||||
.setPlaceholder("secret key")
|
||||
.setValue(this.plugin.settings.secretKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pastebinPublic = value;
|
||||
this.plugin.settings.secretKey = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Region")
|
||||
.setDesc("AWS region of the S3 bucket.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("aws region")
|
||||
.setValue(this.plugin.settings.region)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.region = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("S3 Bucket")
|
||||
.setDesc("S3 bucket name.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("bucket name")
|
||||
.setValue(this.plugin.settings.bucket)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.bucket = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Bucket folder")
|
||||
.setDesc("Optional folder in s3 bucket. Support the use of ${year}, ${month}, and ${day} variables.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("folder")
|
||||
.setValue(this.plugin.settings.folder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.folder = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new Setting(containerEl)
|
||||
.setName("Use custom endpoint")
|
||||
.setDesc("Use the custom api endpoint below.")
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.useCustomEndpoint).onChange(async (value) => {
|
||||
this.plugin.settings.useCustomEndpoint = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Custom S3 Endpoint")
|
||||
.setDesc("Optionally set a custom endpoint for any S3 compatible storage provider.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("https://s3.myhost.com/")
|
||||
.setValue(this.plugin.settings.customEndpoint)
|
||||
.onChange(async (value) => {
|
||||
value = value.match(/https?:\/\//) // Force to start http(s)://
|
||||
? value
|
||||
: "https://" + value;
|
||||
value = value.replace(/([^\/])$/, "$1/"); // Force to end with slash
|
||||
this.plugin.settings.customEndpoint = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("S3 Path Style URLs")
|
||||
.setDesc(
|
||||
"Advanced option to force using (legacy) path-style s3 URLs (s3.myhost.com/bucket) instead of the modern AWS standard host-style (bucket.s3.myhost.com)."
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.forcePathStyle).onChange(async (value) => {
|
||||
this.plugin.settings.forcePathStyle = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Use custom image URL")
|
||||
.setDesc("Use the custom image URL below.")
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.useCustomImageUrl).onChange(async (value) => {
|
||||
this.plugin.settings.useCustomImageUrl = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Custom Image URL")
|
||||
.setDesc("Advanced option to force inserting custom image URLs. This option is helpful if you are using CDN.")
|
||||
.addText((text) =>
|
||||
text.setValue(this.plugin.settings.customImageUrl).onChange(async (value) => {
|
||||
value = value.match(/https?:\/\//) // Force to start http(s)://
|
||||
? value
|
||||
: "https://" + value;
|
||||
value = value.replace(/([^\/])$/, "$1/"); // Force to end with slash
|
||||
this.plugin.settings.customImageUrl = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Bypass local CORS check")
|
||||
.setDesc("Bypass local CORS preflight checks - it might work on later versions of Obsidian.")
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.bypassCors).onChange(async (value) => {
|
||||
this.plugin.settings.bypassCors = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new Setting(containerEl)
|
||||
.setName("Query String Key")
|
||||
.setDesc("Appended to the end of the URL. Optional")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Empty means no query string key")
|
||||
.setValue(this.plugin.settings.queryStringKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.queryStringKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Query String Value")
|
||||
.setDesc("Appended to the end of the URL. Optional")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Empty means no query string value")
|
||||
.setValue(this.plugin.settings.queryStringValue)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.queryStringValue = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// Local YAML field
|
||||
new Setting(containerEl)
|
||||
.setName("Frontmatter property prefix")
|
||||
|
|
@ -215,32 +306,6 @@ export class SendNoteSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
// .then((setting) => addDocs(setting, "https://docs.note.sx/notes/encryption"));
|
||||
}
|
||||
|
||||
private async getUserKey(apiKey: string, username: string, password: string): Promise<string> {
|
||||
const url = "https://pastebin.com/api/api_login.php";
|
||||
const params = new URLSearchParams();
|
||||
params.append("api_dev_key", apiKey);
|
||||
params.append("api_user_name", username);
|
||||
params.append("api_user_password", password);
|
||||
|
||||
const response = await requestUrl({
|
||||
url: url,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return response.text;
|
||||
} else {
|
||||
this.status = new StatusMessage(
|
||||
"Failed to get user key. Please check your Pastebin API key, username and password.",
|
||||
StatusType.Error,
|
||||
5 * 1000
|
||||
);
|
||||
throw new Error("Failed to get user key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addDocs(setting: Setting, url: string) {
|
||||
|
|
@ -250,3 +315,25 @@ function addDocs(setting: Setting, url: string) {
|
|||
href: url,
|
||||
});
|
||||
}
|
||||
|
||||
const wrapTextWithPasswordHide = (text: TextComponent) => {
|
||||
const hider = text.inputEl.insertAdjacentElement("beforebegin", createSpan());
|
||||
if (!hider) {
|
||||
return;
|
||||
}
|
||||
setIcon(hider as HTMLElement, "eye-off");
|
||||
|
||||
hider.addEventListener("click", () => {
|
||||
const isText = text.inputEl.getAttribute("type") === "text";
|
||||
if (isText) {
|
||||
setIcon(hider as HTMLElement, "eye-off");
|
||||
text.inputEl.setAttribute("type", "password");
|
||||
} else {
|
||||
setIcon(hider as HTMLElement, "eye");
|
||||
text.inputEl.setAttribute("type", "text");
|
||||
}
|
||||
text.inputEl.focus();
|
||||
});
|
||||
text.inputEl.setAttribute("type", "password");
|
||||
return text;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue