More analytics, and cancel requests

This commit is contained in:
Gregory Schier
2024-02-24 11:30:07 -08:00
parent 0857ef9afd
commit fd044005a6
43 changed files with 565 additions and 541 deletions

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import type { HotkeyAction } from '../../hooks/useHotKey';
import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
import { Icon } from './Icon';

View File

@@ -1,6 +1,5 @@
import { defaultKeymap } from '@codemirror/commands';
import { Compartment, EditorState, Transaction } from '@codemirror/state';
import type { ViewUpdate } from '@codemirror/view';
import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import classNames from 'classnames';
import { EditorView } from 'codemirror';
@@ -148,14 +147,6 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
}, [contentType, autocomplete, useTemplating, environment, workspace]);
useEffect(() => {
if (cm.current === null) return;
const { view } = cm.current;
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: defaultValue ?? '' } });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [forceUpdateKey]);
const classList = className?.split(/\s+/) ?? [];
const bgClassList = classList
.filter((c) => c.match(/(^|:)?bg-.+/)) // Find bg-* classes
@@ -163,57 +154,59 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
.map((c) => c.replace(/^dark:bg-/, 'dark:!bg-')); // !important
// Initialize the editor when ref mounts
const initEditorRef = useCallback((container: HTMLDivElement | null) => {
if (container === null) {
cm.current?.view.destroy();
cm.current = null;
return;
}
let view: EditorView;
try {
const languageCompartment = new Compartment();
const langExt = getLanguageExtension({
contentType,
useTemplating,
autocomplete,
environment,
workspace,
});
const state = EditorState.create({
doc: `${defaultValue ?? ''}`,
extensions: [
languageCompartment.of(langExt),
placeholderCompartment.current.of([]),
wrapLinesCompartment.current.of([]),
...getExtensions({
container,
readOnly,
singleLine,
onChange: handleChange,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
],
});
view = new EditorView({ state, parent: container });
cm.current = { view, languageCompartment };
syncGutterBg({ parent: container, bgClassList });
if (autoFocus) {
view.focus();
const initEditorRef = useCallback(
(container: HTMLDivElement | null) => {
if (container === null) {
cm.current?.view.destroy();
cm.current = null;
return;
}
if (autoSelect) {
view.dispatch({ selection: { anchor: 0, head: view.state.doc.length } });
}
} catch (e) {
console.log('Failed to initialize Codemirror', e);
}
let view: EditorView;
try {
const languageCompartment = new Compartment();
const langExt = getLanguageExtension({
contentType,
useTemplating,
autocomplete,
environment,
workspace,
});
const state = EditorState.create({
doc: `${defaultValue ?? ''}`,
extensions: [
languageCompartment.of(langExt),
placeholderCompartment.current.of([]),
wrapLinesCompartment.current.of([]),
...getExtensions({
container,
readOnly,
singleLine,
onChange: handleChange,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
}),
],
});
view = new EditorView({ state, parent: container });
cm.current = { view, languageCompartment };
syncGutterBg({ parent: container, bgClassList });
if (autoFocus) {
view.focus();
}
if (autoSelect) {
view.dispatch({ selection: { anchor: 0, head: view.state.doc.length } });
}
} catch (e) {
console.log('Failed to initialize Codemirror', e);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
[forceUpdateKey],
);
// Add bg classes to actions, so they appear over the text
const decoratedActions = useMemo(() => {
@@ -340,29 +333,13 @@ function getExtensions({
// Handle onChange
EditorView.updateListener.of((update) => {
// Only fire onChange if the document changed and the update was from user input. This prevents firing onChange when the document is updated when
// changing pages (one request to another in header editor)
if (onChange && update.docChanged && isViewUpdateFromUserInput(update)) {
if (onChange && update.docChanged) {
onChange.current?.(update.state.doc.toString());
}
}),
];
}
function isViewUpdateFromUserInput(viewUpdate: ViewUpdate) {
// Make sure document has changed, ensuring user events like selections don't count.
if (viewUpdate.docChanged) {
// Check transactions for any that are direct user input, not changes from Y.js or another extension.
for (const transaction of viewUpdate.transactions) {
// Not using Transaction.isUserEvent because that only checks for a specific User event type ( "input", "delete", etc.). Checking the annotation directly allows for any type of user event.
const userEventType = transaction.annotation(Transaction.userEvent);
if (userEventType) return userEventType;
}
}
return false;
}
const syncGutterBg = ({
parent,
bgClassList,

View File

@@ -11,7 +11,7 @@ interface Props {
export const HotKeyList = ({ hotkeys, bottomSlot }: Props) => {
return (
<div className="mx-auto h-full flex items-center text-gray-700 text-sm">
<div className="h-full flex items-center justify-center text-gray-700 text-sm">
<VStack space={2}>
{hotkeys.map((hotkey) => (
<HStack key={hotkey} className="grid grid-cols-2">

View File

@@ -1,6 +1,24 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { HStack } from './Stacks';
import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
export function KeyValueRows({
children,
}: {
children:
| ReactElement<HTMLAttributes<HTMLTableColElement>>
| ReactElement<HTMLAttributes<HTMLTableColElement>>[];
}) {
children = Array.isArray(children) ? children : [children];
return (
<table className="text-xs font-mono min-w-0 w-full mb-auto">
<tbody className="divide-highlightSecondary">
{children.map((child, i) => (
<tr key={i}>{child}</tr>
))}
</tbody>
</table>
);
}
interface Props {
label: ReactNode;
@@ -8,17 +26,13 @@ interface Props {
labelClassName?: string;
}
export function KeyValueRows({ children }: { children: ReactNode }) {
return <dl className="text-xs w-full font-mono divide-highlightSecondary">{children}</dl>;
}
export function KeyValueRow({ label, value, labelClassName }: Props) {
return (
<HStack space={3} className="py-0.5">
<dd className={classNames(labelClassName, 'w-1/3 text-gray-700 select-text cursor-text')}>
<>
<td className={classNames('py-1 pr-2 text-gray-700 select-text cursor-text', labelClassName)}>
{label}
</dd>
<dt className="w-2/3 select-text cursor-text break-all">{value}</dt>
</HStack>
</td>
<td className="py-1 cursor-text select-text break-all min-w-0">{value}</td>
</>
);
}