mirror of
https://github.com/elpamplina/mastodon-threading.git
synced 2026-07-22 11:50:25 +00:00
Option to convert links into quotes if possible.
Option to set own posts quote policy. Using granular scopes to limit the permissions granted to the app. Credentials are checked everytime before posting.
This commit is contained in:
parent
d6818737cc
commit
d9c4a06af9
5 changed files with 302 additions and 163 deletions
13
auth.ts
13
auth.ts
|
|
@ -1,12 +1,14 @@
|
|||
import {createRestAPIClient, mastodon} from "masto";
|
||||
import {requestUrl} from "obsidian";
|
||||
|
||||
const SCOPES = 'read:search write:media write:statuses';
|
||||
const REDIRECT = 'obsidian://mastodon-threading';
|
||||
async function createApp(server: string): Promise<{ clientId: string, clientSecret: string } | null> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
client_name: 'Mastodon Threading for Obsidian',
|
||||
redirect_uris: 'obsidian://mastodon-threading',
|
||||
scopes: 'read write',
|
||||
redirect_uris: REDIRECT,
|
||||
scopes: SCOPES,
|
||||
website: 'https://github.com/elpamplina/mastodon-threading'
|
||||
});
|
||||
const resp = await requestUrl(
|
||||
|
|
@ -31,8 +33,8 @@ async function createApp(server: string): Promise<{ clientId: string, clientSecr
|
|||
async function getAuthURL(server: string, clientId: string): Promise<string | null> {
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
scope: 'write',
|
||||
redirect_uri: 'obsidian://mastodon-threading',
|
||||
scope: SCOPES,
|
||||
redirect_uri: REDIRECT,
|
||||
response_type: 'code',
|
||||
});
|
||||
return `https://${server}/oauth/authorize?${params.toString()}`;
|
||||
|
|
@ -43,9 +45,8 @@ async function getAuthToken(server: string, clientId: string, clientSecret: stri
|
|||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code: authCode,
|
||||
redirect_uri: 'obsidian://mastodon-threading',
|
||||
redirect_uri: REDIRECT,
|
||||
grant_type: 'authorization_code',
|
||||
scope: 'write',
|
||||
});
|
||||
const resp = await requestUrl({
|
||||
url: `https://${server}/oauth/token`,
|
||||
|
|
|
|||
15
lang/en.json
15
lang/en.json
|
|
@ -9,6 +9,8 @@
|
|||
"connect_to": "Connect to Mastodon",
|
||||
"connect": "Connect",
|
||||
"error": "Error connecting to server!",
|
||||
"connecting": "Connecting to {{server}}...",
|
||||
"cancel": "Cancel",
|
||||
"max_post": "Post size",
|
||||
"max_post_desc": "{{server}} allows posts up to {{max}} characters.",
|
||||
"visibility_first": "First post visibility",
|
||||
|
|
@ -24,7 +26,18 @@
|
|||
"unlisted": "Quiet public (unlisted and not searchable)",
|
||||
"private": "Private (only followers)"
|
||||
},
|
||||
"default_language": "Default post language"
|
||||
"default_language": "Default post language",
|
||||
"quote_approval": "Who can quote",
|
||||
"quote_approval_desc": "Decide who can quote your posts",
|
||||
"quote": {
|
||||
"default": "As set on your account",
|
||||
"public": "Everybody",
|
||||
"followers": "Your followers only",
|
||||
"nobody": "Nobody"
|
||||
},
|
||||
"quote_links": "Convert links into quotes",
|
||||
"quote_links_desc": "Links to Mastodon posts will be converted into citations whenever possible.",
|
||||
"quotes_not_supported": "The server doesn't support quotes."
|
||||
},
|
||||
"modal": {
|
||||
"send_thread_count": "Send thread to Mastodon with {{count}} posts",
|
||||
|
|
|
|||
15
lang/es.json
15
lang/es.json
|
|
@ -26,7 +26,18 @@
|
|||
"unlisted": "Pública silenciosa (no listado ni en búsquedas)",
|
||||
"private": "Privado (solo para tus seguidores)"
|
||||
},
|
||||
"default_language": "Lenguaje de posts por defecto"
|
||||
"default_language": "Lenguaje de posts por defecto",
|
||||
"quote_approval": "Quién puede citarte",
|
||||
"quote_approval_desc": "Decide quién podrá citar tus publicaciones",
|
||||
"quote": {
|
||||
"default": "Según lo configurado en tu cuenta",
|
||||
"public": "Todos",
|
||||
"followers": "Solo tus seguidores",
|
||||
"nobody": "Nadie"
|
||||
},
|
||||
"quote_links": "Convertir los enlaces en citas",
|
||||
"quote_links_desc": "Los enlaces a publicaciones de Mastodon se convertirán en citas siempre que sea posible.",
|
||||
"quotes_not_supported": "El servidor no soporta citas."
|
||||
},
|
||||
"modal": {
|
||||
"send_thread_count": "Enviar hilo a Mastodon con {{count}} posts",
|
||||
|
|
@ -51,7 +62,7 @@
|
|||
},
|
||||
"error": {
|
||||
"session_lost": "¡Se ha perdido la sesión con Mastodon!",
|
||||
"not_logged": "No estás conectado a Mastodon. Por favor, revisa los ajustes del complemento.",
|
||||
"not_logged": "No estás conectado a Mastodon. Por favor, revisa los ajustes del plugin.",
|
||||
"not_posted": "ERROR: ¡El mensaje no se ha enviado!",
|
||||
"no_text": "¡No hay texto para enviar!",
|
||||
"size_exceeded": "NO ENVIADO: Se superan los {{max}} caracteres.",
|
||||
|
|
|
|||
415
main.ts
415
main.ts
|
|
@ -21,13 +21,16 @@ import {
|
|||
pattern_image,
|
||||
pattern_quote,
|
||||
pattern_server,
|
||||
pattern_url, pattern_warning,
|
||||
pattern_md_link,
|
||||
pattern_url,
|
||||
pattern_warning,
|
||||
SEPARATOR,
|
||||
separatorField,
|
||||
separatorPostProcessor
|
||||
} from "./utils";
|
||||
import mime from "mime/lite";
|
||||
import {decryptText, encryptText, generateKey} from "./encrypt";
|
||||
import {QuoteApprovalPolicy} from "masto/dist/esm/mastodon/rest/v1/statuses";
|
||||
|
||||
type StatusVisibility = mastodon.v1.StatusVisibility;
|
||||
|
||||
|
|
@ -46,8 +49,11 @@ interface MastodonThreadingSettings {
|
|||
serverMaxAttachments: number,
|
||||
serverMaxDescription: number,
|
||||
serverMimeTypes: string[],
|
||||
serverSupportsQuotes: boolean,
|
||||
visibilityFirst: mastodon.v1.StatusVisibility,
|
||||
visibilityRest: mastodon.v1.StatusVisibility,
|
||||
quoteApproval: QuoteApprovalPolicy | 'default',
|
||||
quoteLinks: boolean,
|
||||
postCounter: boolean,
|
||||
defaultLanguage: string,
|
||||
}
|
||||
|
|
@ -71,8 +77,11 @@ const DEFAULT_SETTINGS: MastodonThreadingSettings = {
|
|||
"image/heif",
|
||||
"image/webp",
|
||||
],
|
||||
serverSupportsQuotes: true,
|
||||
visibilityFirst: 'public',
|
||||
visibilityRest: 'unlisted',
|
||||
quoteApproval: 'default',
|
||||
quoteLinks: true,
|
||||
postCounter: false,
|
||||
// @ts-ignore
|
||||
defaultLanguage: i18next.language?.split('-')[0] || 'en',
|
||||
|
|
@ -198,169 +207,194 @@ export default class MastodonThreading extends Plugin {
|
|||
|
||||
async thread_post(editor: Editor) {
|
||||
try {
|
||||
const limit = await this.getInstanceInfo(); // Update server parameters and check availability
|
||||
let requests = 0;
|
||||
if (editor.getSelection() || editor.getValue()) {
|
||||
type postMetadata = {
|
||||
text: string,
|
||||
warning: string | null,
|
||||
images: {
|
||||
file: TFile,
|
||||
alt: string,
|
||||
isimage: boolean,
|
||||
}[]
|
||||
}
|
||||
let message: string = '';
|
||||
if (editor.getSelection()) {
|
||||
// If some fragments selected, get only the last one
|
||||
let chunks = editor.getSelection().split(SEPARATOR);
|
||||
message = chunks[chunks.length - 1];
|
||||
}
|
||||
else {
|
||||
message = editor.getValue();
|
||||
}
|
||||
let chunks = message.split(SEPARATOR);
|
||||
let posts: postMetadata[] = [];
|
||||
let count = 0;
|
||||
let descriptions = true;
|
||||
for (let c of chunks) {
|
||||
if (c.trim().length === 0) {
|
||||
new Notice(t('error.void_fragment'));
|
||||
return;
|
||||
// Update server parameters and check credentials
|
||||
const limit = await this.getInstanceInfo();
|
||||
if (await this.checkCredentials()) {
|
||||
let requests = 0;
|
||||
if (editor.getSelection() || editor.getValue()) {
|
||||
type postMetadata = {
|
||||
text: string,
|
||||
warning: string | null,
|
||||
images: {
|
||||
file: TFile,
|
||||
alt: string,
|
||||
isimage: boolean,
|
||||
}[],
|
||||
quote: string | null,
|
||||
}
|
||||
let post: postMetadata = {
|
||||
text: c,
|
||||
warning: null,
|
||||
images: []
|
||||
let message: string = '';
|
||||
if (editor.getSelection()) {
|
||||
// If some fragments selected, get only the last one
|
||||
let chunks = editor.getSelection().split(SEPARATOR);
|
||||
message = chunks[chunks.length - 1];
|
||||
} else {
|
||||
message = editor.getValue();
|
||||
}
|
||||
// Get images metadata
|
||||
for (let m of post.text.matchAll(pattern_image)) {
|
||||
let mimetype = mime.getType(m[2].toLowerCase()) || '-'
|
||||
if (this.settings.serverMimeTypes.includes(mimetype)) {
|
||||
// Only one video allowed, not mixed with images
|
||||
if (post.images.length > 0) {
|
||||
if (mimetype.startsWith('image')) {
|
||||
if (post.images.some(it => !it.isimage)) {
|
||||
// Trying to add image to videos
|
||||
let chunks = message.split(SEPARATOR);
|
||||
let posts: postMetadata[] = [];
|
||||
let count = 0;
|
||||
let descriptions = true;
|
||||
for (let c of chunks) {
|
||||
if (c.trim().length === 0) {
|
||||
new Notice(t('error.void_fragment'));
|
||||
return;
|
||||
}
|
||||
let post: postMetadata = {
|
||||
text: c,
|
||||
warning: null,
|
||||
images: [],
|
||||
quote: null,
|
||||
}
|
||||
// Get images metadata
|
||||
for (let m of post.text.matchAll(pattern_image)) {
|
||||
let mimetype = mime.getType(m[2].toLowerCase()) || '-'
|
||||
if (this.settings.serverMimeTypes.includes(mimetype)) {
|
||||
// Only one video allowed, not mixed with images
|
||||
if (post.images.length > 0) {
|
||||
if (mimetype.startsWith('image')) {
|
||||
if (post.images.some(it => !it.isimage)) {
|
||||
// Trying to add image to videos
|
||||
new Notice(t('error.filetype_not_allowed', {file: m[1]}));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Trying to add video to another media
|
||||
new Notice(t('error.filetype_not_allowed', {file: m[1]}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Trying to add video to another media
|
||||
new Notice(t('error.filetype_not_allowed', {file: m[1]}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let file = this.app.vault.getFileByPath(m[1]);
|
||||
if (file === null) {
|
||||
// Try on the attachment folder
|
||||
// @ts-ignore
|
||||
file = this.app.vault.getFileByPath(`${this.app.vault.getConfig("attachmentFolderPath").replace(/^\.\//, '')}/${m[1]}`);
|
||||
let file = this.app.vault.getFileByPath(m[1]);
|
||||
if (file === null) {
|
||||
new Notice(t('error.file_not_found', {file: m[1]}));
|
||||
// Try on the attachment folder
|
||||
// @ts-ignore
|
||||
file = this.app.vault.getFileByPath(`${this.app.vault.getConfig("attachmentFolderPath").replace(/^\.\//, '')}/${m[1]}`);
|
||||
if (file === null) {
|
||||
new Notice(t('error.file_not_found', {file: m[1]}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (file.stat.size > (mimetype.startsWith('image') ? this.settings.serverMaxImage : this.settings.serverMaxVideo)) {
|
||||
new Notice(t('error.file_size_exceeded', {file: m[1]}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (file.stat.size > (mimetype.startsWith('image') ? this.settings.serverMaxImage : this.settings.serverMaxVideo)) {
|
||||
new Notice(t('error.file_size_exceeded', {file: m[1]}));
|
||||
let desc: string = '';
|
||||
if (m[4]) {
|
||||
desc = m[4].replace(/\n> ?/g, '\n')
|
||||
.replace(/^> ?/, '').trim();
|
||||
if (desc.length > this.settings.serverMaxDescription) {
|
||||
new Notice(t('error.alt_exceeded', {max: this.settings.serverMaxDescription}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!desc) {
|
||||
descriptions = false;
|
||||
}
|
||||
post.images.push({
|
||||
file: file,
|
||||
alt: desc,
|
||||
isimage: mimetype.startsWith('image'),
|
||||
});
|
||||
requests++;
|
||||
} else {
|
||||
new Notice(t('error.filetype_not_allowed'));
|
||||
return;
|
||||
}
|
||||
let desc: string = '';
|
||||
if (m[4]) {
|
||||
desc = m[4].replace(/\n> ?/g, '\n')
|
||||
.replace(/^> ?/, '').trim();
|
||||
if (desc.length > this.settings.serverMaxDescription) {
|
||||
new Notice(t('error.alt_exceeded', {max: this.settings.serverMaxDescription}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!desc) {
|
||||
descriptions = false;
|
||||
}
|
||||
post.images.push({
|
||||
file: file,
|
||||
alt: desc,
|
||||
isimage: mimetype.startsWith('image'),
|
||||
});
|
||||
requests++;
|
||||
}
|
||||
else {
|
||||
new Notice(t('error.filetype_not_allowed'));
|
||||
if (post.images.length > this.settings.serverMaxAttachments) {
|
||||
new Notice(t('error.attachment_exceeded', {max: this.settings.serverMaxAttachments}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (post.images.length > this.settings.serverMaxAttachments) {
|
||||
new Notice(t('error.attachment_exceeded', {max: this.settings.serverMaxAttachments}));
|
||||
return;
|
||||
}
|
||||
// Find content warning
|
||||
let found_warning = post.text.match(pattern_warning);
|
||||
if (found_warning) {
|
||||
post.warning = found_warning[1];
|
||||
}
|
||||
// Remove images from main text
|
||||
post.text = post.text.replace(pattern_image, ' ')
|
||||
// Simplify links
|
||||
.replace(pattern_url, '$1')
|
||||
// Remove warning blocks
|
||||
.replace(pattern_warning, '')
|
||||
// Remove quote blocks
|
||||
.replace(pattern_quote, '')
|
||||
// Finally, strip spaces
|
||||
.trim();
|
||||
// Add counter
|
||||
if (this.settings.postCounter && chunks.length > 1) {
|
||||
post.text += `\n[${++count}/${chunks.length}]`;
|
||||
}
|
||||
if (post.text.length > this.settings.maxPost) {
|
||||
new Notice(t('error.size_exceeded', {max: this.settings.maxPost}));
|
||||
return;
|
||||
}
|
||||
posts.push(post);
|
||||
requests++;
|
||||
}
|
||||
if (limit != null && requests > limit) {
|
||||
new Notice(t('error.rate_limit'));
|
||||
return;
|
||||
}
|
||||
new SendThreadModal(this.app, this, posts.length, async (language, visibility_first, visibility_rest) => {
|
||||
if (descriptions || confirm(t('modal.no_description'))) {
|
||||
try {
|
||||
let first = true;
|
||||
let id_link: string | null = null;
|
||||
for (const [i, p] of posts.entries()) {
|
||||
if (i % 10 === 0) {
|
||||
new Notice(t('ok.sending', {'n': (i==0? 1: i), 'total': posts.length}));
|
||||
}
|
||||
let media: string[] = [];
|
||||
for (let img of p.images) {
|
||||
let m = await this.getClient().v2.media.create({
|
||||
file: new Blob([await this.app.vault.readBinary(img.file)]),
|
||||
description: img.alt
|
||||
});
|
||||
media.push(m.id);
|
||||
}
|
||||
let status: mastodon.v1.Status = await this.getClient().v1.statuses.create({
|
||||
status: p.text,
|
||||
spoilerText: p.warning,
|
||||
visibility: (first ? visibility_first : visibility_rest),
|
||||
inReplyToId: id_link,
|
||||
mediaIds: media,
|
||||
language: language,
|
||||
});
|
||||
id_link = status.id;
|
||||
first = false;
|
||||
}
|
||||
new Notice(posts.length > 1 ? t('ok.thread_posted') : t('ok.message_posted'));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
new Notice(t('error.not_posted'));
|
||||
// Find content warning
|
||||
const found_warning = post.text.match(pattern_warning);
|
||||
if (found_warning) {
|
||||
post.warning = found_warning[1];
|
||||
}
|
||||
// Remove images and other patterns from main text
|
||||
post.text = post.text.replace(pattern_image, ' ')
|
||||
// Simplify links
|
||||
.replace(pattern_md_link, '$1')
|
||||
// Remove warning blocks
|
||||
.replace(pattern_warning, '')
|
||||
// Remove quote blocks
|
||||
.replace(pattern_quote, '')
|
||||
// Finally, strip spaces
|
||||
.trim();
|
||||
// Find quotes in links
|
||||
if (this.settings.serverSupportsQuotes && this.settings.quoteLinks) {
|
||||
const found_urls = post.text.match(pattern_url);
|
||||
if (found_urls) {
|
||||
// Iterate over URLs, searching for mastodon posts
|
||||
for (let url: string of found_urls) {
|
||||
const res = await this.getClient().v2.search.list({
|
||||
q: url, type: 'statuses', resolve: true
|
||||
});
|
||||
if (res.statuses.length === 1 &&
|
||||
(res.statuses[0].quoteApproval.currentUser === 'automatic' ||
|
||||
res.statuses[0].quoteApproval.currentUser === 'manual')) {
|
||||
post.quote = res.statuses[0].id;
|
||||
post.text = post.text.replace(url, '');
|
||||
break; // Only one quote per post
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add counter
|
||||
if (this.settings.postCounter && chunks.length > 1) {
|
||||
post.text += `\n[${++count}/${chunks.length}]`;
|
||||
}
|
||||
if (post.text.length > this.settings.maxPost) {
|
||||
new Notice(t('error.size_exceeded', {max: this.settings.maxPost}));
|
||||
return;
|
||||
}
|
||||
posts.push(post);
|
||||
requests++;
|
||||
}
|
||||
}).open();
|
||||
if (limit != null && requests > limit) {
|
||||
new Notice(t('error.rate_limit'));
|
||||
return;
|
||||
}
|
||||
new SendThreadModal(this.app, this, posts.length, async (language, visibility_first, visibility_rest) => {
|
||||
if (descriptions || confirm(t('modal.no_description'))) {
|
||||
try {
|
||||
let first = true;
|
||||
let id_link: string | null = null;
|
||||
for (const [i, p] of posts.entries()) {
|
||||
if (i % 10 === 0) {
|
||||
new Notice(t('ok.sending', {'n': (i == 0 ? 1 : i), 'total': posts.length}));
|
||||
}
|
||||
let media: string[] = [];
|
||||
for (let img of p.images) {
|
||||
let m = await this.getClient().v2.media.create({
|
||||
file: new Blob([await this.app.vault.readBinary(img.file)]),
|
||||
description: img.alt
|
||||
});
|
||||
media.push(m.id);
|
||||
}
|
||||
let status: mastodon.v1.Status = await this.getClient().v1.statuses.create({
|
||||
status: p.text,
|
||||
spoilerText: p.warning,
|
||||
visibility: (first ? visibility_first : visibility_rest),
|
||||
inReplyToId: id_link,
|
||||
mediaIds: media,
|
||||
language: language,
|
||||
quotedStatusId: p.quote,
|
||||
quoteApprovalPolicy: this.settings.quoteApproval === 'default'? null: this.settings.quoteApproval,
|
||||
});
|
||||
id_link = status.id;
|
||||
first = false;
|
||||
}
|
||||
new Notice(posts.length > 1 ? t('ok.thread_posted') : t('ok.message_posted'));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
new Notice(t('error.not_posted'));
|
||||
}
|
||||
}
|
||||
}).open();
|
||||
} else {
|
||||
new Notice(t('error.no_text'));
|
||||
}
|
||||
} else {
|
||||
new Notice(t('error.no_text'));
|
||||
new Notice(t('error.not_logged'));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
@ -379,7 +413,7 @@ export default class MastodonThreading extends Plugin {
|
|||
if (!text.startsWith('>')) {
|
||||
// Ignore images and shorten links
|
||||
text = text.replace(pattern_image, ' ')
|
||||
.replace(pattern_url, '$1');
|
||||
.replace(pattern_md_link, '$1');
|
||||
count += text.length + 1;
|
||||
if (count > this.settings.maxPost) {
|
||||
editor.replaceRange(SEPARATOR, {line: i, ch: 0});
|
||||
|
|
@ -443,7 +477,15 @@ export default class MastodonThreading extends Plugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async checkCredentials(): Promise<boolean> {
|
||||
const client = this.getClient();
|
||||
const app = await client.v1.apps.verifyCredentials();
|
||||
return (app.scopes.includes('read:search') &&
|
||||
app.scopes.includes('write:media') &&
|
||||
app.scopes.includes('write:statuses'));
|
||||
}
|
||||
|
||||
async getInstanceInfo() {
|
||||
let resp = await requestUrl(`https://${this.settings.server}/api/v2/instance`);
|
||||
if (resp.status == 200) {
|
||||
|
|
@ -454,6 +496,10 @@ export default class MastodonThreading extends Plugin {
|
|||
this.settings.serverMaxVideo = info.configuration.media_attachments.video_size_limit;
|
||||
this.settings.serverMaxAttachments = info.configuration.statuses.max_media_attachments;
|
||||
this.settings.serverMimeTypes = info.configuration.media_attachments.supported_mime_types;
|
||||
const version = info.version.split(".");
|
||||
const major = parseInt(version[0], 10);
|
||||
const minor = parseInt(version[1], 10);
|
||||
this.settings.serverSupportsQuotes = major > 4 || (major === 4 && minor >= 5);
|
||||
await this.saveSettings();
|
||||
}
|
||||
return parseInt(resp.headers['x-ratelimit-remaining']) || null;
|
||||
|
|
@ -523,15 +569,40 @@ class SendThreadModal extends Modal {
|
|||
class MastodonThreadingSettingTab extends PluginSettingTab {
|
||||
plugin: MastodonThreading;
|
||||
displayInterval?: unknown = null;
|
||||
lastRefresh: number;
|
||||
|
||||
constructor(app: App, plugin: MastodonThreading) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.lastRefresh = 0;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
|
||||
// Update server info and check credentials if necessary (and possible)
|
||||
// (one hour latency period, to avoid hammering)
|
||||
if (new Date().getTime() > this.lastRefresh + 3600000) {
|
||||
this.lastRefresh = new Date().getTime();
|
||||
try {
|
||||
this.plugin.getInstanceInfo().then(() => {
|
||||
this.display();
|
||||
});
|
||||
if (this.plugin.settings.server && this.plugin.settings.authToken) {
|
||||
this.plugin.checkCredentials().then(ok => {
|
||||
if (!ok) {
|
||||
this.plugin.settings.authToken = '';
|
||||
this.plugin.settings.clientId = '';
|
||||
this.plugin.settings.clientSecret = '';
|
||||
this.display();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
|
|
@ -660,7 +731,7 @@ class MastodonThreadingSettingTab extends PluginSettingTab {
|
|||
.addOption('public', t('settings.visibility.public'))
|
||||
.addOption('unlisted', t('settings.visibility.unlisted'))
|
||||
.addOption('private', t('settings.visibility.private'))
|
||||
.setValue(this.plugin.settings.visibilityFirst)
|
||||
.setValue(this.plugin.settings.visibilityFirst as string)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.visibilityFirst = value as StatusVisibility;
|
||||
await this.plugin.saveSettings();
|
||||
|
|
@ -680,13 +751,55 @@ class MastodonThreadingSettingTab extends PluginSettingTab {
|
|||
.addOption('public', t('settings.visibility.public_not_recommended'))
|
||||
.addOption('unlisted', t('settings.visibility.unlisted'))
|
||||
.addOption('private', t('settings.visibility.private'))
|
||||
.setValue(this.plugin.settings.visibilityRest)
|
||||
.setValue(this.plugin.settings.visibilityRest as string)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.visibilityRest = value as StatusVisibility;
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})
|
||||
);
|
||||
|
||||
let quote_approval_desc = t('settings.quote_approval_desc');
|
||||
if (!this.plugin.settings.serverSupportsQuotes) {
|
||||
quote_approval_desc = new DocumentFragment();
|
||||
const errortext1 = quote_approval_desc.createSpan();
|
||||
errortext1.textContent = t('settings.quotes_not_supported');
|
||||
errortext1.addClass('warning');
|
||||
}
|
||||
new Setting(containerEl)
|
||||
.setName(t('settings.quote_approval'))
|
||||
.setDesc(quote_approval_desc)
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('default', t('settings.quote.default'))
|
||||
.addOption('public', t('settings.quote.public'))
|
||||
.addOption('followers', t('settings.quote.followers'))
|
||||
.addOption('nobody', t('settings.quote.nobody'))
|
||||
.setValue(this.plugin.settings.serverSupportsQuotes? this.plugin.settings.quoteApproval as string : 'default')
|
||||
.setDisabled(!this.plugin.settings.serverSupportsQuotes)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.quoteApproval = value as QuoteApprovalPolicy | 'default';
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})
|
||||
);
|
||||
let quote_links_desc = t('settings.quote_links_desc');
|
||||
if (!this.plugin.settings.serverSupportsQuotes) {
|
||||
quote_links_desc = new DocumentFragment();
|
||||
const errortext2 = quote_links_desc.createSpan();
|
||||
errortext2.textContent = t('settings.quotes_not_supported');
|
||||
errortext2.addClass('warning');
|
||||
}
|
||||
new Setting(containerEl)
|
||||
.setName(t('settings.quote_links'))
|
||||
.setDesc(quote_links_desc)
|
||||
.addToggle(tg => tg
|
||||
.setValue(this.plugin.settings.serverSupportsQuotes? this.plugin.settings.quoteLinks : false)
|
||||
.setDisabled(!this.plugin.settings.serverSupportsQuotes)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.quoteLinks = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new Setting(containerEl)
|
||||
.setName(t('settings.post_counter'))
|
||||
.setDesc(t('settings.post_counter_desc'))
|
||||
|
|
|
|||
7
utils.ts
7
utils.ts
|
|
@ -6,7 +6,8 @@ import {MarkdownPostProcessor} from "obsidian";
|
|||
const SEPARATOR: string = '§'
|
||||
const CW: string = '!!'
|
||||
const pattern_image = /!\[\[(.*\.(.*?))(\|.*)*]]\s*?((\n>.*)*)/g;
|
||||
const pattern_url = /\[.*]\((https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))\)/g;
|
||||
const pattern_md_link = /\[.*]\((https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))\)/g;
|
||||
const pattern_url = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/g;
|
||||
const pattern_separator = new RegExp('^' + SEPARATOR, 'm');
|
||||
const pattern_warning = new RegExp('^' + CW + ' (.*)$', 'm');
|
||||
const pattern_quote = /^>.*\n/gm;
|
||||
|
|
@ -39,7 +40,7 @@ function calculate_size(text: string): number {
|
|||
// Calculate fragment size, removing links and code blocks
|
||||
return text
|
||||
.replace(pattern_image, '')
|
||||
.replace(pattern_url, '$1')
|
||||
.replace(pattern_md_link, '$1')
|
||||
.replace(pattern_separator, '').length;
|
||||
}
|
||||
|
||||
|
|
@ -125,4 +126,4 @@ function replaceSeparators(element: HTMLElement) {
|
|||
const separatorPostProcessor: MarkdownPostProcessor =
|
||||
(element, context) => replaceSeparators(element);
|
||||
|
||||
export {SEPARATOR, separatorField, separatorPostProcessor, pattern_url, pattern_image, pattern_quote, pattern_server, pattern_warning}
|
||||
export {SEPARATOR, separatorField, separatorPostProcessor, pattern_md_link, pattern_url, pattern_image, pattern_quote, pattern_server, pattern_warning}
|
||||
|
|
|
|||
Loading…
Reference in a new issue