75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
import { type RefObject, useEffect, useState } from 'react';
|
|
import type { EditorView } from '@codemirror/view';
|
|
import type { Asset } from '../../../../lib/types';
|
|
|
|
interface Opts {
|
|
getView: () => EditorView | null;
|
|
editorRef: RefObject<HTMLDivElement | null>;
|
|
getCachedAssets: () => Promise<Asset[]>;
|
|
}
|
|
|
|
/** Inline `/` or `!` asset autocomplete dropdown. */
|
|
export function useAssetAutocomplete({ getView, editorRef, getCachedAssets }: Opts) {
|
|
const [showAutocomplete, setShowAutocomplete] = useState(false);
|
|
const [autocompleteAssets, setAutocompleteAssets] = useState<Asset[]>([]);
|
|
const [autocompletePos, setAutocompletePos] = useState({ top: 0, left: 0 });
|
|
|
|
async function triggerAutocomplete(view: EditorView) {
|
|
try {
|
|
const assets = await getCachedAssets();
|
|
setAutocompleteAssets(assets.slice(0, 8));
|
|
const pos = view.state.selection.main.head;
|
|
const coords = view.coordsAtPos(pos);
|
|
if (coords) {
|
|
const editorRect = editorRef.current?.getBoundingClientRect();
|
|
if (editorRect) {
|
|
setAutocompletePos({
|
|
top: coords.bottom - editorRect.top + 4,
|
|
left: coords.left - editorRect.left,
|
|
});
|
|
}
|
|
}
|
|
setShowAutocomplete(true);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
function insertAssetMarkdown(asset: Asset) {
|
|
const view = getView();
|
|
if (!view) return;
|
|
const isImage = /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(asset.name);
|
|
const md = isImage ? `` : `[${asset.name}](${asset.url})`;
|
|
|
|
const pos = view.state.selection.main.head;
|
|
const line = view.state.doc.lineAt(pos);
|
|
const textBefore = line.text.slice(0, pos - line.from);
|
|
const triggerIdx = Math.max(textBefore.lastIndexOf('/'), textBefore.lastIndexOf('!'));
|
|
|
|
if (triggerIdx !== -1) {
|
|
const from = line.from + triggerIdx;
|
|
view.dispatch({ changes: { from, to: pos, insert: md } });
|
|
} else {
|
|
view.dispatch({ changes: { from: pos, insert: md } });
|
|
}
|
|
view.focus();
|
|
setShowAutocomplete(false);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!showAutocomplete) return;
|
|
const handler = () => setShowAutocomplete(false);
|
|
window.addEventListener('click', handler);
|
|
return () => window.removeEventListener('click', handler);
|
|
}, [showAutocomplete]);
|
|
|
|
return {
|
|
showAutocomplete,
|
|
setShowAutocomplete,
|
|
autocompleteAssets,
|
|
autocompletePos,
|
|
triggerAutocomplete,
|
|
insertAssetMarkdown,
|
|
};
|
|
}
|