init: the first public commit for AFFiNE

This commit is contained in:
DarkSky
2022-07-22 15:49:21 +08:00
commit e3e3741393
1451 changed files with 108124 additions and 0 deletions
@@ -0,0 +1,154 @@
import React, { useEffect, useRef, useImperativeHandle } from 'react';
import { EditorState, EditorStateConfig, Extension } from '@codemirror/state';
import { EditorView, ViewUpdate } from '@codemirror/view';
import { useCodeMirror } from './use-code-mirror';
export * from './use-code-mirror';
export interface ReactCodeMirrorProps
extends Omit<EditorStateConfig, 'doc' | 'extensions'>,
Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'placeholder'> {
/** value of the auto created model in the editor. */
value?: string;
height?: string;
minHeight?: string;
maxHeight?: string;
width?: string;
minWidth?: string;
maxWidth?: string;
/** focus on the editor. */
autoFocus?: boolean;
/** Enables a placeholder—a piece of example content to show when the editor is empty. */
placeholder?: string | HTMLElement;
/**
* `light` / `dark` / `Extension` Defaults to `light`.
* @default light
*/
theme?: 'light' | 'dark' | Extension;
/**
* Whether to optional basicSetup by default
* @default true
*/
basicSetup?: boolean;
/**
* This disables editing of the editor content by the user.
* @default true
*/
editable?: boolean;
readOnly?: boolean;
/**
* Whether to optional basicSetup by default
* @default true
*/
indentWithTab?: boolean;
/** Fired whenever a change occurs to the document. */
onChange?(value: string, viewUpdate: ViewUpdate): void;
/** Fired whenever a change occurs to the document. There is a certain difference with `onChange`. */
onUpdate?(viewUpdate: ViewUpdate): void;
handleKeyArrowUp?: () => void;
handleKeyArrowDown?: () => void;
/**
* Extension values can be [provided](https://codemirror.net/6/docs/ref/#state.EditorStateConfig.extensions) when creating a state to attach various kinds of configuration and behavior information.
* They can either be built-in extension-providing objects,
* such as [state fields](https://codemirror.net/6/docs/ref/#state.StateField) or [facet providers](https://codemirror.net/6/docs/ref/#state.Facet.of),
* or objects with an extension in its `extension` property. Extensions can be nested in arrays arbitrarily deep—they will be flattened when processed.
*/
extensions?: Extension[];
/**
* If the view is going to be mounted in a shadow root or document other than the one held by the global variable document (the default), you should pass it here.
* Originally from the [config of EditorView](https://codemirror.net/6/docs/ref/#view.EditorView.constructor%5Econfig.root)
*/
root?: ShadowRoot | Document;
}
export interface ReactCodeMirrorRef {
editor?: HTMLDivElement | null;
state?: EditorState;
view?: EditorView;
}
const ReactCodeMirror = React.forwardRef<
ReactCodeMirrorRef,
ReactCodeMirrorProps
>((props, ref) => {
const {
className,
value = '',
selection,
extensions = [],
onChange,
onUpdate,
handleKeyArrowUp,
handleKeyArrowDown,
autoFocus,
theme = 'light',
height,
minHeight,
maxHeight,
width,
minWidth,
maxWidth,
basicSetup,
placeholder,
indentWithTab,
editable,
readOnly,
root,
...other
} = props;
const editor = useRef<HTMLDivElement>(null);
const { state, view, container, setContainer } = useCodeMirror({
container: editor.current,
root,
value,
autoFocus,
theme,
height,
minHeight,
maxHeight,
width,
minWidth,
maxWidth,
basicSetup,
placeholder,
indentWithTab,
editable,
readOnly,
selection,
onChange,
onUpdate,
extensions,
handleKeyArrowUp,
handleKeyArrowDown,
});
useImperativeHandle(ref, () => ({ editor: container, state, view }), [
container,
state,
view,
]);
useEffect(() => {
setContainer(editor.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// check type of value
if (typeof value !== 'string') {
throw new Error(`value must be typeof string but got ${typeof value}`);
}
const defaultClassNames =
typeof theme === 'string' ? `cm-theme-${theme}` : 'cm-theme';
return (
<div
ref={editor}
className={`${defaultClassNames}${
className ? ` ${className}` : ''
}`}
{...other}
/>
);
});
ReactCodeMirror.displayName = 'CodeMirror';
export default ReactCodeMirror;
@@ -0,0 +1,220 @@
import { FC, useState, useMemo, useRef, useEffect } from 'react';
import { StyleWithAtRules } from 'style9';
import { CreateView } from '@toeverything/framework/virgo';
import CodeMirror from './CodeMirror';
import { styled } from '@toeverything/components/ui';
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
import { javascript } from '@codemirror/lang-javascript';
import { html } from '@codemirror/lang-html';
import { css } from '@codemirror/lang-css';
import { json } from '@codemirror/lang-json';
import { python } from '@codemirror/lang-python';
import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import { sql, MySQL, PostgreSQL } from '@codemirror/lang-sql';
import { java } from '@codemirror/lang-java';
import { rust } from '@codemirror/lang-rust';
import { cpp } from '@codemirror/lang-cpp';
import { lezer } from '@codemirror/lang-lezer';
import { php } from '@codemirror/lang-php';
import { StreamLanguage } from '@codemirror/language';
import { go } from '@codemirror/legacy-modes/mode/go';
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { lua } from '@codemirror/legacy-modes/mode/lua';
import { swift } from '@codemirror/legacy-modes/mode/swift';
import { tcl } from '@codemirror/legacy-modes/mode/tcl';
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import { vb } from '@codemirror/legacy-modes/mode/vb';
import { powerShell } from '@codemirror/legacy-modes/mode/powershell';
import { brainfuck } from '@codemirror/legacy-modes/mode/brainfuck';
import { stylus } from '@codemirror/legacy-modes/mode/stylus';
import { erlang } from '@codemirror/legacy-modes/mode/erlang';
import { nginx } from '@codemirror/legacy-modes/mode/nginx';
import { perl } from '@codemirror/legacy-modes/mode/perl';
import { pascal } from '@codemirror/legacy-modes/mode/pascal';
import { liveScript } from '@codemirror/legacy-modes/mode/livescript';
import { scheme } from '@codemirror/legacy-modes/mode/scheme';
import { toml } from '@codemirror/legacy-modes/mode/toml';
import { vbScript } from '@codemirror/legacy-modes/mode/vbscript';
import { clojure } from '@codemirror/legacy-modes/mode/clojure';
import { coffeeScript } from '@codemirror/legacy-modes/mode/coffeescript';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { julia } from '@codemirror/legacy-modes/mode/julia';
import { r } from '@codemirror/legacy-modes/mode/r';
import { Extension } from '@codemirror/state';
import { Select } from '../../components/select';
import {
useOnSelect,
WrapperWithPendantAndDragDrop,
} from '@toeverything/components/editor-core';
interface CreateCodeView extends CreateView {
style9?: StyleWithAtRules;
containerClassName?: string;
}
const langs: Record<string, any> = {
javascript,
jsx: () => javascript({ jsx: true }),
typescript: () => javascript({ typescript: true }),
tsx: () => javascript({ jsx: true, typescript: true }),
json,
html,
css,
python,
markdown,
xml,
sql,
mysql: () => sql({ dialect: MySQL }),
pgsql: () => sql({ dialect: PostgreSQL }),
java,
rust,
cpp,
lezer,
php,
go: () => StreamLanguage.define(go),
shell: () => StreamLanguage.define(shell),
lua: () => StreamLanguage.define(lua),
swift: () => StreamLanguage.define(swift),
tcl: () => StreamLanguage.define(tcl),
yaml: () => StreamLanguage.define(yaml),
vb: () => StreamLanguage.define(vb),
powershell: () => StreamLanguage.define(powerShell),
brainfuck: () => StreamLanguage.define(brainfuck),
stylus: () => StreamLanguage.define(stylus),
erlang: () => StreamLanguage.define(erlang),
nginx: () => StreamLanguage.define(nginx),
perl: () => StreamLanguage.define(perl),
ruby: () => StreamLanguage.define(ruby),
pascal: () => StreamLanguage.define(pascal),
livescript: () => StreamLanguage.define(liveScript),
scheme: () => StreamLanguage.define(scheme),
toml: () => StreamLanguage.define(toml),
vbscript: () => StreamLanguage.define(vbScript),
clojure: () => StreamLanguage.define(clojure),
coffeescript: () => StreamLanguage.define(coffeeScript),
julia: () => StreamLanguage.define(julia),
dockerfile: () => StreamLanguage.define(dockerFile),
r: () => StreamLanguage.define(r),
// clike: () => StreamLanguage.define(clike),
// clike: () => clike({ }),
};
const CodeBlock = styled('div')(({ theme }) => ({
backgroundColor: '#f8f9fa',
border: '1px solid #e0e0e0',
'&:hover': {
'.operation': {
display: 'flex',
},
},
'.operation': {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
borderBottom: '1px solid #e0e0e0',
padding: '0 8px 0px',
},
'.delete-block': {
padding: '10px 4px 10px 10px',
},
'.cm-focused': {
outline: 'none !important',
},
'.select': {
marginTop: '10px',
select: {
border: 'none',
outline: 'none',
height: '22px',
lineHeight: theme.affine.typography.body1.lineHeight,
paddingLeft: '10px',
maxHeight: '400px',
background: '#f1f1f1',
borderRadius: '6px',
},
},
}));
export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
const init_value: string = block.getProperty('text')?.value?.[0]?.text;
const lang_type: string = block.getProperty('lang')?.value?.[0]?.text;
const [mode, setMode] = useState('javascript');
const [extensions, setExtensions] = useState<Extension[]>();
const codeMirror = useRef();
useOnSelect(block.id, (is_select: boolean) => {
if (codeMirror.current) {
//@ts-ignore
codeMirror?.current?.view?.focus();
}
});
const onChange = (value: any, codeEditor: any) => {
console.log(value);
block.setProperty('text', {
value: [{ text: value }],
});
};
useEffect(() => {
handleLangChange(lang_type ? lang_type : 'javascript');
}, []);
function handleLangChange(lang: string) {
block.setProperty('lang', lang);
setMode(lang);
setExtensions([langs[lang]()]);
}
const handle_remove_block = () => {
block.remove();
};
const handleKeyArrowDown = () => {
editor.selectionManager.activeNextNode(block.id, 'start');
};
const handleKeyArrowUp = () => {
editor.selectionManager.activePreviousNode(block.id, 'start');
};
return (
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
<CodeBlock
onKeyDown={e => {
e.stopPropagation();
}}
>
<div className="operation">
<div className="select">
<Select
label="Lang"
options={Object.keys(langs)}
value={mode}
onChange={evn => handleLangChange(evn.target.value)}
/>
</div>
<div>
<div
className="delete-block"
onClick={handle_remove_block}
>
<DeleteSweepOutlinedIcon
className="delete-icon"
fontSize="small"
sx={{
color: 'rgba(0,0,0,.5)',
cursor: 'pointer',
'&:hover': { color: 'rgba(0,0,0,.9)' },
}}
/>
</div>
</div>
</div>
<CodeMirror
ref={codeMirror}
value={init_value}
height={'auto'}
extensions={extensions}
onChange={onChange}
handleKeyArrowDown={handleKeyArrowDown}
handleKeyArrowUp={handleKeyArrowUp}
/>
</CodeBlock>
</WrapperWithPendantAndDragDrop>
);
};
@@ -0,0 +1,83 @@
import {
BaseView,
AsyncBlock,
getTextProperties,
CreateView,
SelectBlock,
getTextHtml,
} from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
import { CodeView } from './CodeView';
import { ComponentType } from 'react';
export class CodeBlock extends BaseView {
type = Protocol.Block.Type.code;
public override selectable = true;
public override activatable = false;
// View = CodeView;
View = CodeView;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
}
// TODO: internal format not implemented yet
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'CODE') {
const childNodes = el.childNodes;
let text_value = '';
for (let i = 0; i < childNodes.length; i++) {
const blocks_info = parseEl(childNodes[i] as Element);
for (let j = 0; j < blocks_info.length; j++) {
if (blocks_info[j].type === 'text') {
const block_texts =
blocks_info[j].properties.text.value;
if (block_texts.length > 0) {
text_value += block_texts[0].text;
}
}
}
}
return [
{
type: this.type,
properties: {
text: { value: [{ text: text_value }] },
},
children: [],
},
];
}
return null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
let content = getTextHtml(block);
content += await generateHtml(children);
return `<code>${content}</code>`;
}
}
@@ -0,0 +1,252 @@
import { useEffect, useState } from 'react';
// import { basicSetup, minimalSetup } from 'codemirror';
import { EditorState, StateEffect, Extension } from '@codemirror/state';
import {
indentWithTab,
defaultKeymap,
history,
historyKeymap,
} from '@codemirror/commands';
import {
EditorView,
keymap,
ViewUpdate,
placeholder,
highlightSpecialChars,
drawSelection,
highlightActiveLine,
dropCursor,
rectangularSelection,
crosshairCursor,
lineNumbers,
highlightActiveLineGutter,
} from '@codemirror/view';
import { oneDark } from '@codemirror/theme-one-dark';
import { ReactCodeMirrorProps } from './CodeMirror';
import {
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
} from '@codemirror/language';
// import {searchKeymap, highlightSelectionMatches} from "@codemirror/search"
// import {autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap} from "@codemirror/autocomplete"
// import {lintKeymap} from "@codemirror/lint"
export interface UseCodeMirror extends ReactCodeMirrorProps {
container?: HTMLDivElement | null;
}
const basicSetup: Extension = [
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
keymap.of([...defaultKeymap, ...historyKeymap, ...foldKeymap]),
];
export function useCodeMirror(props: UseCodeMirror) {
const {
value,
selection,
onChange,
onUpdate,
handleKeyArrowUp,
handleKeyArrowDown,
extensions = [],
autoFocus,
theme = 'light',
height = '',
minHeight = '',
maxHeight = '',
placeholder: placeholderStr = '',
width = '',
minWidth = '',
maxWidth = '',
editable = true,
readOnly = false,
indentWithTab: defaultIndentWithTab = true,
basicSetup: defaultBasicSetup = true,
root,
} = props;
const [container, setContainer] = useState(props.container);
const [view, setView] = useState<EditorView>();
const [state, setState] = useState<EditorState>();
const defaultLightThemeOption = EditorView.theme(
{
'&': {
backgroundColor: '#fff',
},
},
{
dark: false,
}
);
const defaultThemeOption = EditorView.theme({
'&': {
height,
minHeight,
maxHeight,
width,
minWidth,
maxWidth,
},
});
const updateListener = EditorView.updateListener.of((vu: ViewUpdate) => {
if (vu.docChanged && typeof onChange === 'function') {
const doc = vu.state.doc;
const value = doc.toString();
onChange(value, vu);
}
});
// keymap.of([{''}])
// keymap.of()
const upFunction = [
{
key: 'ArrowUp',
run: () => {
handleKeyArrowUp();
return false;
},
},
{
key: 'ArrowDown',
run: (e: any) => {
handleKeyArrowDown();
return false;
},
},
];
// completionKeymap
let getExtensions = [updateListener, keymap.of(upFunction)];
if (defaultIndentWithTab) {
getExtensions.unshift(keymap.of([indentWithTab]));
}
if (defaultBasicSetup) {
getExtensions.unshift(basicSetup);
}
if (placeholderStr) {
getExtensions.unshift(placeholder(placeholderStr));
}
switch (theme) {
case 'light':
getExtensions.push(defaultLightThemeOption);
break;
case 'dark':
getExtensions.push(oneDark);
break;
default:
getExtensions.push(theme);
break;
}
if (editable === false) {
getExtensions.push(EditorView.editable.of(false));
}
if (readOnly) {
getExtensions.push(EditorState.readOnly.of(true));
}
if (onUpdate && typeof onUpdate === 'function') {
getExtensions.push(EditorView.updateListener.of(onUpdate));
}
getExtensions.unshift(keymap.of([indentWithTab]));
getExtensions = getExtensions.concat(extensions);
useEffect(() => {
if (container && !state) {
const stateCurrent = EditorState.create({
doc: value,
selection,
extensions: getExtensions,
});
setState(stateCurrent);
if (!view) {
const viewCurrent = new EditorView({
state: stateCurrent,
parent: container,
root,
});
setView(viewCurrent);
}
}
return () => {
if (view) {
setView(undefined);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [container, state]);
useEffect(
() => () => {
if (view) {
view.destroy();
setView(undefined);
}
},
[view]
);
useEffect(() => {
if (autoFocus && view) {
view.focus();
}
}, [autoFocus, view]);
useEffect(() => {
const currentValue = view ? view.state.doc.toString() : '';
if (view && value !== currentValue) {
view.dispatch({
changes: {
from: 0,
to: currentValue.length,
insert: value || '',
},
});
}
}, [value, view]);
useEffect(() => {
if (view) {
view.dispatch({
effects: StateEffect.reconfigure.of(getExtensions),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
theme,
extensions,
height,
minHeight,
maxHeight,
width,
placeholderStr,
minWidth,
maxWidth,
editable,
defaultIndentWithTab,
defaultBasicSetup,
]);
return { state, setState, view, setView, container, setContainer };
}