mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-21 08:11:24 +02:00
Better editor updating
This commit is contained in:
@@ -76,7 +76,7 @@ async fn send_request(
|
|||||||
headers.insert("x-foo-bar", HeaderValue::from_static("hi mom"));
|
headers.insert("x-foo-bar", HeaderValue::from_static("hi mom"));
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-api-key"),
|
HeaderName::from_static("x-api-key"),
|
||||||
HeaderValue::from_static("123-123-123"),
|
HeaderValue::from_str(models::generate_id("x").as_str()).expect("Failed to create header"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let m = Method::from_bytes(req.method.to_uppercase().as_bytes()).unwrap();
|
let m = Method::from_bytes(req.method.to_uppercase().as_bytes()).unwrap();
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ pub async fn delete_all_responses(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_id(prefix: &str) -> String {
|
pub fn generate_id(prefix: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{prefix}_{}",
|
"{prefix}_{}",
|
||||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 10)
|
Alphanumeric.sample_string(&mut rand::thread_rng(), 10)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import './Editor.css';
|
import './Editor.css';
|
||||||
import { HTMLAttributes, useEffect, useMemo, useRef } from 'react';
|
import { HTMLAttributes, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import { baseExtensions, multiLineExtensions, syntaxExtension } from './extensions';
|
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||||
import { EditorState, Transaction, EditorSelection } from '@codemirror/state';
|
|
||||||
import type { TransactionSpec } from '@codemirror/state';
|
import type { TransactionSpec } from '@codemirror/state';
|
||||||
|
import { Compartment, EditorSelection, EditorState, Transaction } from '@codemirror/state';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { autocompletion } from '@codemirror/autocomplete';
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
|
|
||||||
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
||||||
contentType: string;
|
contentType: string;
|
||||||
|
valueKey?: string;
|
||||||
useTemplating?: boolean;
|
useTemplating?: boolean;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
@@ -17,6 +18,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|||||||
|
|
||||||
export default function Editor({
|
export default function Editor({
|
||||||
contentType,
|
contentType,
|
||||||
|
valueKey,
|
||||||
useTemplating,
|
useTemplating,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -25,84 +27,53 @@ export default function Editor({
|
|||||||
singleLine,
|
singleLine,
|
||||||
...props
|
...props
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const [cm, setCm] = useState<{ view: EditorView; langHolder: Compartment } | null>(null);
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const extensions = useMemo(() => {
|
const extensions = useMemo(
|
||||||
const ext = syntaxExtension({ contentType, useTemplating });
|
() => getExtensions({ onSubmit, singleLine, onChange, contentType, useTemplating }),
|
||||||
return [
|
[contentType],
|
||||||
autocompletion(),
|
);
|
||||||
...(singleLine
|
|
||||||
? [
|
|
||||||
EditorView.domEventHandlers({
|
|
||||||
keydown: (e) => {
|
|
||||||
// TODO: Figure out how to not have this mess up autocomplete
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
onSubmit?.();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
EditorState.transactionFilter.of(
|
|
||||||
(tr: Transaction): TransactionSpec | TransactionSpec[] => {
|
|
||||||
if (!tr.isUserEvent('input.paste')) {
|
|
||||||
return tr;
|
|
||||||
}
|
|
||||||
console.log('GOT PASTE', tr);
|
|
||||||
|
|
||||||
// let addedNewline = false;
|
const newState = (langHolder: Compartment) => {
|
||||||
const trs: TransactionSpec[] = [];
|
const langExt = getLanguageExtension({ contentType, useTemplating });
|
||||||
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
return EditorState.create({
|
||||||
// console.log('CHANGE', { fromA, toA }, { fromB, toB }, inserted);
|
doc: `${defaultValue ?? ''}`,
|
||||||
let insert = '';
|
extensions: [...extensions, langHolder.of(langExt)],
|
||||||
for (const line of inserted) {
|
});
|
||||||
insert += line.replace('\n', '');
|
};
|
||||||
}
|
|
||||||
trs.push({
|
|
||||||
...tr,
|
|
||||||
selection: undefined,
|
|
||||||
changes: [{ from: fromB, to: toA, insert }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
|
||||||
// console.log('TRS', trs, tr);
|
|
||||||
trs.push({
|
|
||||||
selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
|
||||||
});
|
|
||||||
return trs;
|
|
||||||
// return addedNewline ? [] : tr;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...baseExtensions,
|
|
||||||
...(!singleLine ? [multiLineExtensions] : []),
|
|
||||||
...(ext ? [ext] : []),
|
|
||||||
EditorView.updateListener.of((update) => {
|
|
||||||
if (typeof onChange === 'function') {
|
|
||||||
onChange(update.state.doc.toString());
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}, [contentType]);
|
|
||||||
|
|
||||||
|
// Create codemirror instance when ref initializes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current === null) return;
|
if (ref.current === null) return;
|
||||||
|
let view: EditorView | null = null;
|
||||||
let view: EditorView;
|
|
||||||
try {
|
try {
|
||||||
|
const langHolder = new Compartment();
|
||||||
view = new EditorView({
|
view = new EditorView({
|
||||||
state: EditorState.create({
|
state: newState(langHolder),
|
||||||
doc: `${defaultValue ?? ''}`,
|
|
||||||
extensions: extensions,
|
|
||||||
}),
|
|
||||||
parent: ref.current,
|
parent: ref.current,
|
||||||
});
|
});
|
||||||
|
setCm({ view, langHolder });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log('Failed to initialize Codemirror', e);
|
||||||
}
|
}
|
||||||
return () => view?.destroy();
|
return () => view?.destroy();
|
||||||
}, [ref.current]);
|
}, [ref.current]);
|
||||||
|
|
||||||
|
// Update value when valueKey changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (cm === null) return;
|
||||||
|
cm.view.dispatch({
|
||||||
|
changes: { from: 0, to: cm.view.state.doc.length, insert: `${defaultValue ?? ''}` },
|
||||||
|
});
|
||||||
|
}, [valueKey]);
|
||||||
|
|
||||||
|
// Update language extension when contentType changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (cm === null) return;
|
||||||
|
const ext = getLanguageExtension({ contentType, useTemplating });
|
||||||
|
cm.view.dispatch({ effects: cm.langHolder.reconfigure(ext) });
|
||||||
|
}, [contentType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -115,3 +86,67 @@ export default function Editor({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExtensions({
|
||||||
|
singleLine,
|
||||||
|
onChange,
|
||||||
|
onSubmit,
|
||||||
|
contentType,
|
||||||
|
useTemplating,
|
||||||
|
}: Pick<Props, 'singleLine' | 'onChange' | 'onSubmit' | 'contentType' | 'useTemplating'>) {
|
||||||
|
const ext = getLanguageExtension({ contentType, useTemplating });
|
||||||
|
return [
|
||||||
|
autocompletion(),
|
||||||
|
...(singleLine
|
||||||
|
? [
|
||||||
|
EditorView.domEventHandlers({
|
||||||
|
keydown: (e) => {
|
||||||
|
// TODO: Figure out how to not have this mess up autocomplete
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
EditorState.transactionFilter.of(
|
||||||
|
(tr: Transaction): TransactionSpec | TransactionSpec[] => {
|
||||||
|
if (!tr.isUserEvent('input.paste')) {
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let addedNewline = false;
|
||||||
|
const trs: TransactionSpec[] = [];
|
||||||
|
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
||||||
|
// console.log('CHANGE', { fromA, toA }, { fromB, toB }, inserted);
|
||||||
|
let insert = '';
|
||||||
|
for (const line of inserted) {
|
||||||
|
insert += line.replace('\n', '');
|
||||||
|
}
|
||||||
|
trs.push({
|
||||||
|
...tr,
|
||||||
|
selection: undefined,
|
||||||
|
changes: [{ from: fromB, to: toA, insert }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
||||||
|
// console.log('TRS', trs, tr);
|
||||||
|
trs.push({
|
||||||
|
selection: EditorSelection.create([EditorSelection.cursor(8)], 1),
|
||||||
|
});
|
||||||
|
return trs;
|
||||||
|
// return addedNewline ? [] : tr;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...baseExtensions,
|
||||||
|
...(!singleLine ? [multiLineExtensions] : []),
|
||||||
|
...(ext ? [ext] : []),
|
||||||
|
EditorView.updateListener.of((update) => {
|
||||||
|
if (typeof onChange === 'function') {
|
||||||
|
onChange(update.state.doc.toString());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const syntaxExtensions: Record<string, { base: LanguageSupport; ext: any[] }> =
|
|||||||
url: { base: url(), ext: [] },
|
url: { base: url(), ext: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function syntaxExtension({
|
export function getLanguageExtension({
|
||||||
contentType,
|
contentType,
|
||||||
useTemplating,
|
useTemplating,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function ResponsePane({ requestId, error }: Props) {
|
|||||||
/>
|
/>
|
||||||
) : response?.body ? (
|
) : response?.body ? (
|
||||||
<Editor
|
<Editor
|
||||||
key={response.body}
|
valueKey={response.id}
|
||||||
defaultValue={response?.body}
|
defaultValue={response?.body}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user