mirror of
https://code.castopod.org/adaures/castopod
synced 2025-05-12 01:05:47 +00:00

- add "ActivityPub" library to handle server to server federation and basic client to server protocols using activitypub: - add webfinger endpoint to look for actor - add actor definition with inbox / outbox / followers - remote follow an actor - create notes with possible preview cards - interract with favourites, reblogs and replies - block incoming actors and/or domains - broadcast/schedule activities to fediverse followers using a cron task - For castopod, the podcast is the actor: - overwrite the activitypub library for castopod's specific needs - perform basic interactions administrating a podcast to interact with fediverse users: - create notes with episode attachment - favourite and share a note + reply - add specific castopod_namespaces for podcasts and episodes definitions - overwrite CodeIgniter's Route service to include alternate-content option for activitystream requests - update episode publication logic: - remove publication inputs in create / edit episode form - publish / schedule or unpublish an episode after creation - the podcaster publishes a note when publishing an episode - Javascript / Typescript modules: - fix Dropdown.ts to keep dropdown menu in foreground - add Modal.ts for funding links modal - add Toggler.ts to toggle various css states in ui - User Interface: - update tailwindcss to v2 - use castopod's pine and rose colors - update public layout to a 3 column layout - add pages in public for podcast activity, episode list and notes - update episode page to include linked notes - remove previous and next episodes from episode pages - show different public views depending on whether user is authenticated or not - use Kumbh Sans and Montserrat fonts - update CodeIgniter's config files - with CodeIgniter's new requirements, update docker environments are now based on php v7.3 image - move Image entity to Libraries - update composer and npm packages to latest versions closes #69 #65 #85, fixes #51 #91 #92 #88
160 lines
4.3 KiB
TypeScript
160 lines
4.3 KiB
TypeScript
import { exampleSetup } from "prosemirror-example-setup";
|
|
import "prosemirror-example-setup/style/style.css";
|
|
import {
|
|
defaultMarkdownParser,
|
|
defaultMarkdownSerializer,
|
|
schema,
|
|
} from "prosemirror-markdown";
|
|
import "prosemirror-menu/style/menu.css";
|
|
import { EditorState } from "prosemirror-state";
|
|
import { EditorView } from "prosemirror-view";
|
|
import "prosemirror-view/style/prosemirror.css";
|
|
|
|
class MarkdownView {
|
|
textarea: HTMLTextAreaElement;
|
|
|
|
constructor(target: HTMLTextAreaElement) {
|
|
this.textarea = target;
|
|
this.textarea.classList.add("w-full", "h-full");
|
|
}
|
|
|
|
get content() {
|
|
return this.textarea.innerHTML;
|
|
}
|
|
focus() {
|
|
this.textarea.focus();
|
|
}
|
|
show() {
|
|
this.textarea.classList.remove("hidden");
|
|
}
|
|
hide() {
|
|
this.textarea.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
class ProseMirrorView {
|
|
editorContainer: HTMLDivElement;
|
|
view: EditorView;
|
|
|
|
constructor(target: HTMLTextAreaElement, content: string) {
|
|
this.editorContainer = document.createElement("div");
|
|
this.editorContainer.classList.add("bg-white", "border");
|
|
this.editorContainer.style.minHeight = "200px";
|
|
const editor = target.parentNode?.insertBefore(
|
|
this.editorContainer,
|
|
target.nextSibling
|
|
);
|
|
|
|
this.view = new EditorView(editor, {
|
|
state: EditorState.create({
|
|
doc: defaultMarkdownParser.parse(content),
|
|
plugins: exampleSetup({ schema }),
|
|
}),
|
|
dispatchTransaction: (transaction) => {
|
|
const newState = this.view.state.apply(transaction);
|
|
this.view.updateState(newState);
|
|
|
|
if (transaction.docChanged) {
|
|
target.innerHTML = this.content;
|
|
}
|
|
},
|
|
attributes: {
|
|
class: "prose-sm px-3 py-2 overflow-y-auto focus:ring",
|
|
style: "min-height: 200px; max-height: 500px",
|
|
},
|
|
});
|
|
}
|
|
|
|
get content() {
|
|
return defaultMarkdownSerializer.serialize(this.view.state.doc);
|
|
}
|
|
focus() {
|
|
this.view.focus();
|
|
}
|
|
show() {
|
|
this.editorContainer.classList.remove("hidden");
|
|
}
|
|
hide() {
|
|
this.editorContainer.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
const MarkdownEditor = (): void => {
|
|
const targets: NodeListOf<HTMLTextAreaElement> = document.querySelectorAll(
|
|
"textarea[data-editor='markdown']"
|
|
);
|
|
const activeClass = "font-semibold";
|
|
|
|
for (let i = 0; i < targets.length; i++) {
|
|
const target = targets[i];
|
|
|
|
const wysiwygBtn = document.createElement("button");
|
|
wysiwygBtn.classList.add(
|
|
activeClass,
|
|
"py-1",
|
|
"px-2",
|
|
"bg-white",
|
|
"border",
|
|
"text-xs",
|
|
"outline-none",
|
|
"focus:ring"
|
|
);
|
|
wysiwygBtn.setAttribute("type", "button");
|
|
wysiwygBtn.innerHTML = "Wysiwyg";
|
|
const markdownBtn = document.createElement("button");
|
|
markdownBtn.classList.add(
|
|
"py-1",
|
|
"px-2",
|
|
"bg-white",
|
|
"border",
|
|
"text-xs",
|
|
"outline-none",
|
|
"focus:ring"
|
|
);
|
|
markdownBtn.setAttribute("type", "button");
|
|
markdownBtn.innerHTML = "Markdown";
|
|
|
|
const viewButtons = document.createElement("div");
|
|
viewButtons.appendChild(wysiwygBtn);
|
|
viewButtons.appendChild(markdownBtn);
|
|
viewButtons.classList.add(
|
|
"inline-flex",
|
|
"absolute",
|
|
"top-0",
|
|
"right-0",
|
|
"-mt-6"
|
|
);
|
|
|
|
const markdownEditorContainer = document.createElement("div");
|
|
markdownEditorContainer.classList.add("relative");
|
|
markdownEditorContainer.style.minHeight = "200px";
|
|
target.parentNode?.appendChild(markdownEditorContainer);
|
|
markdownEditorContainer.appendChild(target);
|
|
|
|
// show WYSIWYG editor by default
|
|
target.classList.add("hidden");
|
|
const markdownView = new MarkdownView(target);
|
|
const wysiwygView = new ProseMirrorView(target, markdownView.content);
|
|
|
|
markdownEditorContainer.appendChild(viewButtons);
|
|
|
|
markdownBtn.addEventListener("click", () => {
|
|
if (markdownBtn.classList.contains(activeClass)) return;
|
|
markdownBtn.classList.add(activeClass);
|
|
wysiwygBtn.classList.remove(activeClass);
|
|
wysiwygView.hide();
|
|
markdownView.show();
|
|
});
|
|
|
|
wysiwygBtn.addEventListener("click", () => {
|
|
if (wysiwygBtn.classList.contains(activeClass)) return;
|
|
wysiwygBtn.classList.add(activeClass);
|
|
markdownBtn.classList.remove(activeClass);
|
|
markdownView.hide();
|
|
wysiwygView.show();
|
|
});
|
|
}
|
|
};
|
|
|
|
export default MarkdownEditor;
|