Files
classroomio/packages/ui/src/custom/editor/extensions/audio/AudioExtension.ts
T
Rotimi Bestandtunny17 f5a4156c8b feat: migrate dashboard to svelte 5 (#577)
* chore: migration to SvelteKit 2

* stage 1: migrate to svelte 5 and fix rerendering issues

* stage 2: fix more $: reactivity across dashboard

* fix merge conflicts

* chore: migrate code in main to svelte 5

* chore: update preventDefault

* chore: simplify tsconfig and remove unused mobileMenuRef in MobileMenu.svelte

* chore: enforce .ts across dashboard

* rename from classroomio-com to website

* stage-3: Replace carbon icon svelte with lucide icon

* update lock file

* chore: finish icon replacement

* fix: settings and setup page multiple rerender

* stage 3: More icon and rerendering fixes

* feat: checkpoint

* feat: update editor

* [not working] connect everything with ui build

* storybook + ui + dashboard all working independently

* add bg-black to +page.svelte

* feat: add editor to storybook

* chore: animate lucid icons (#590)

* v1 animations

* feat: change some course navigation icons & animate org navigation icons

* chore: change settings icon

* chore: use css instead of js for hover

* rename animations

---------

Co-authored-by: tunny17 <[email protected]>

* chore: update path to node_modules types in api tsconfig

* fix: update import path for DropdownMenu component

* fix: add rollupOptions to build configuration in vite.config.ts

* fix: remove rollupOptions from build configuration and add @floating-ui/dom dependency

* install

---------

Co-authored-by: tunny17 <[email protected]>
2025-11-01 16:18:29 +01:00

148 lines
3.0 KiB
TypeScript

import { Node, nodeInputRule } from '@tiptap/core';
import { Plugin, PluginKey } from '@tiptap/pm/state';
export interface AudioOptions {
HTMLAttributes: Record<string, unknown>;
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
audio: {
/**
* Set a audio node
*/
setAudio: (src: string) => ReturnType;
/**
* Toggle a audio
*/
toggleAudio: (src: string) => ReturnType;
/**
* Remove a audio
*/
removeAudio: () => ReturnType;
};
}
}
const AUDIO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
export const Audio = Node.create<AudioOptions>({
name: 'audio',
group: 'block',
draggable: true,
isolating: true,
atom: true,
addOptions() {
return {
HTMLAttributes: {}
};
},
addAttributes() {
return {
src: {
default: null,
parseHTML: (el) => (el as HTMLSpanElement).getAttribute('src'),
renderHTML: (attrs) => ({ src: attrs.src })
}
};
},
parseHTML() {
return [
{
tag: 'audio',
getAttrs: (el) => ({ src: (el as HTMLAudioElement).getAttribute('src') })
}
];
},
renderHTML({ HTMLAttributes }) {
return [
'audio',
{ controls: 'true', style: 'width: 100%;', ...HTMLAttributes },
['source', HTMLAttributes]
];
},
addCommands() {
return {
setAudio:
(src: string) =>
({ commands }) =>
commands.insertContent(
`<audio controls autoplay="false" style="width: 100%;" src="${src}"></audio>`
),
toggleAudio:
() =>
({ commands }) =>
commands.toggleNode(this.name, 'paragraph'),
removeAudio:
() =>
({ commands }) =>
commands.deleteNode(this.name)
};
},
addInputRules() {
return [
nodeInputRule({
find: AUDIO_INPUT_REGEX,
type: this.type,
getAttributes: (match) => {
const [, , src] = match;
return { src };
}
})
];
},
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('audioDropPlugin'),
props: {
handleDOMEvents: {
drop(view, event) {
const {
state: { schema, tr },
dispatch
} = view;
const hasFiles =
event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length;
if (!hasFiles) return false;
const audios = Array.from(event.dataTransfer.files).filter((file) =>
/audio/i.test(file.type)
);
if (audios.length === 0) return false;
event.preventDefault();
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
audios.forEach((audio) => {
const reader = new FileReader();
reader.onload = (readerEvent) => {
const node = schema.nodes.audio.create({ src: readerEvent.target?.result });
if (coordinates && typeof coordinates.pos === 'number') {
const transaction = tr.insert(coordinates?.pos, node);
dispatch(transaction);
}
};
reader.readAsDataURL(audio);
});
return true;
}
}
}
})
];
}
});