elpamplina_mastodon-threading/utils.ts
elpamplina d9c4a06af9 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.
2025-12-10 17:57:37 +01:00

129 lines
4.3 KiB
TypeScript

import {Extension, RangeSetBuilder, StateField, Transaction,} from '@codemirror/state';
import {Decoration, DecorationSet, EditorView, WidgetType,} from '@codemirror/view';
import MastodonThreading from "./main";
import {MarkdownPostProcessor} from "obsidian";
const SEPARATOR: string = '§'
const CW: string = '!!'
const pattern_image = /!\[\[(.*\.(.*?))(\|.*)*]]\s*?((\n>.*)*)/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;
const pattern_server = /(https?:\/\/)?([-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b.*$/gm;
class SeparatorWidget extends WidgetType {
count: number
size: number
limit: number
constructor(count: number, size: number, limit: number) {
super();
this.count = count;
this.size = size;
this.limit = limit;
}
toDOM(view: EditorView): HTMLElement {
const div = document.createElement('div');
div.classList.add('separator');
if (this.size >= this.limit) {
div.classList.add('warning');
}
const counters = document.createElement('span');
counters.textContent = `${this.count} (${this.size})`;
div.appendChild(counters);
return div;
}
}
function calculate_size(text: string): number {
// Calculate fragment size, removing links and code blocks
return text
.replace(pattern_image, '')
.replace(pattern_md_link, '$1')
.replace(pattern_separator, '').length;
}
function separatorField(plugin: MastodonThreading) {
return StateField.define<DecorationSet>({
create(state): DecorationSet {
return Decoration.none;
},
update(oldState: DecorationSet, transaction: Transaction): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
let pos = 0;
let count = 0;
let last_start_pos = -1;
let last_end_pos = -1;
let text = '';
for (let lin of transaction.state.doc) {
if (lin.startsWith(SEPARATOR)) {
if (last_start_pos !== -1) {
if (last_start_pos === last_end_pos) {
builder.add(last_start_pos, last_end_pos,
Decoration.widget({
widget: new SeparatorWidget(
++count, calculate_size(text), plugin.settings.maxPost)
}));
}
else {
builder.add(last_start_pos, last_end_pos,
Decoration.replace({
widget: new SeparatorWidget(
++count, calculate_size(text), plugin.settings.maxPost)
}));
}
}
last_start_pos = pos;
last_end_pos = pos + SEPARATOR.length
text = lin;
}
else {
if (pos === 0) {
// First default separator
last_start_pos = 0;
last_end_pos = 0;
}
// Quote and warning blocks ignored
if (!lin.startsWith('>') && !lin.startsWith(CW)) {
text += lin;
}
}
pos += lin.length;
}
// Append last separator, only if it's a real separator
if (last_start_pos !== -1 && last_start_pos !== last_end_pos) {
builder.add(last_start_pos, last_end_pos,
Decoration.replace({
widget: new SeparatorWidget(++count, calculate_size(text), plugin.settings.maxPost)
}))
}
return builder.finish();
},
provide(field: StateField<DecorationSet>): Extension {
return EditorView.decorations.from(field);
},
});
}
function replaceSeparators(element: HTMLElement) {
for (let i = 0; i < element.childNodes.length; i++) {
const node = element.childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent?.trimStart();
if (text && text.startsWith(SEPARATOR)) {
element.insertBefore(document.createElement('hr'), node);
element.insertBefore(
document.createTextNode(text.replace(new RegExp(`^${SEPARATOR}`), '')), node);
element.removeChild(node);
}
} else {
replaceSeparators(node as HTMLElement);
}
}
}
const separatorPostProcessor: MarkdownPostProcessor =
(element, context) => replaceSeparators(element);
export {SEPARATOR, separatorField, separatorPostProcessor, pattern_md_link, pattern_url, pattern_image, pattern_quote, pattern_server, pattern_warning}