Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F101418005
index.tsx
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Mon, Feb 10, 06:27
Size
17 KB
Mime Type
text/x-java
Expires
Wed, Feb 12, 06:27 (1 d, 10 h)
Engine
blob
Format
Raw Data
Handle
24158473
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
index.tsx
View Options
import * as React from 'react';
type Padding<T> = T | { top?: T; right?: T; bottom?: T; left?: T };
type Props = React.HTMLAttributes<HTMLDivElement> & {
// Props for the component
value: string;
onValueChange: (value: string) => void;
highlight: (value: string) => string | React.ReactNode;
tabSize: number;
insertSpaces: boolean;
ignoreTabKey: boolean;
padding: Padding<number | string>;
style?: React.CSSProperties;
// Props for the textarea
textareaId?: string;
textareaClassName?: string;
autoFocus?: boolean;
disabled?: boolean;
form?: string;
maxLength?: number;
minLength?: number;
name?: string;
placeholder?: string;
readOnly?: boolean;
required?: boolean;
onClick?: React.MouseEventHandler<HTMLTextAreaElement>;
onFocus?: React.FocusEventHandler<HTMLTextAreaElement>;
onBlur?: React.FocusEventHandler<HTMLTextAreaElement>;
onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
// Props for the hightlighted code’s pre element
preClassName?: string;
};
type State = {
capture: boolean;
};
type Record = {
value: string;
selectionStart: number;
selectionEnd: number;
};
type History = {
stack: (Record & { timestamp: number })[];
offset: number;
};
const KEYCODE_ENTER = 13;
const KEYCODE_TAB = 9;
const KEYCODE_BACKSPACE = 8;
const KEYCODE_Y = 89;
const KEYCODE_Z = 90;
const KEYCODE_M = 77;
const KEYCODE_PARENS = 57;
const KEYCODE_BRACKETS = 219;
const KEYCODE_QUOTE = 222;
const KEYCODE_BACK_QUOTE = 192;
const KEYCODE_ESCAPE = 27;
const HISTORY_LIMIT = 100;
const HISTORY_TIME_GAP = 3000;
const isWindows =
typeof window !== 'undefined' &&
'navigator' in window &&
/Win/i.test(navigator.platform);
const isMacLike =
typeof window !== 'undefined' &&
'navigator' in window &&
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
const className = 'npm__react-simple-code-editor__textarea';
const cssText = /* CSS */ `
/**
* Reset the text fill color so that placeholder is visible
*/
.${className}:empty {
-webkit-text-fill-color: inherit !important;
}
/**
* Hack to apply on some CSS on IE10 and IE11
*/
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
/**
* IE doesn't support '-webkit-text-fill-color'
* So we use 'color: transparent' to make the text transparent on IE
* Unlike other browsers, it doesn't affect caret color in IE
*/
.${className} {
color: transparent !important;
}
.${className}::selection {
background-color: #accef7 !important;
color: transparent !important;
}
}
`;
export default class Editor extends React.Component<Props, State> {
static defaultProps = {
tabSize: 2,
insertSpaces: true,
ignoreTabKey: false,
padding: 0,
};
state = {
capture: true,
};
componentDidMount() {
this._recordCurrentState();
}
private _recordCurrentState = () => {
const input = this._input;
if (!input) return;
// Save current state of the input
const { value, selectionStart, selectionEnd } = input;
this._recordChange({
value,
selectionStart,
selectionEnd,
});
};
private _getLines = (text: string, position: number) =>
text.substring(0, position).split('\n');
private _recordChange = (record: Record, overwrite: boolean = false) => {
const { stack, offset } = this._history;
if (stack.length && offset > -1) {
// When something updates, drop the redo operations
this._history.stack = stack.slice(0, offset + 1);
// Limit the number of operations to 100
const count = this._history.stack.length;
if (count > HISTORY_LIMIT) {
const extras = count - HISTORY_LIMIT;
this._history.stack = stack.slice(extras, count);
this._history.offset = Math.max(this._history.offset - extras, 0);
}
}
const timestamp = Date.now();
if (overwrite) {
const last = this._history.stack[this._history.offset];
if (last && timestamp - last.timestamp < HISTORY_TIME_GAP) {
// A previous entry exists and was in short interval
// Match the last word in the line
const re = /[^a-z0-9]([a-z0-9]+)$/i;
// Get the previous line
const previous = this._getLines(last.value, last.selectionStart)
.pop()
?.match(re);
// Get the current line
const current = this._getLines(record.value, record.selectionStart)
.pop()
?.match(re);
if (previous?.[1] && current?.[1]?.startsWith(previous[1])) {
// The last word of the previous line and current line match
// Overwrite previous entry so that undo will remove whole word
this._history.stack[this._history.offset] = { ...record, timestamp };
return;
}
}
}
// Add the new operation to the stack
this._history.stack.push({ ...record, timestamp });
this._history.offset++;
};
private _updateInput = (record: Record) => {
const input = this._input;
if (!input) return;
// Update values and selection state
input.value = record.value;
input.selectionStart = record.selectionStart;
input.selectionEnd = record.selectionEnd;
this.props.onValueChange(record.value);
};
private _applyEdits = (record: Record) => {
// Save last selection state
const input = this._input;
const last = this._history.stack[this._history.offset];
if (last && input) {
this._history.stack[this._history.offset] = {
...last,
selectionStart: input.selectionStart,
selectionEnd: input.selectionEnd,
};
}
// Save the changes
this._recordChange(record);
this._updateInput(record);
};
private _undoEdit = () => {
const { stack, offset } = this._history;
// Get the previous edit
const record = stack[offset - 1];
if (record) {
// Apply the changes and update the offset
this._updateInput(record);
this._history.offset = Math.max(offset - 1, 0);
}
};
private _redoEdit = () => {
const { stack, offset } = this._history;
// Get the next edit
const record = stack[offset + 1];
if (record) {
// Apply the changes and update the offset
this._updateInput(record);
this._history.offset = Math.min(offset + 1, stack.length - 1);
}
};
private _handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
const { tabSize, insertSpaces, ignoreTabKey, onKeyDown } = this.props;
if (onKeyDown) {
onKeyDown(e);
if (e.defaultPrevented) {
return;
}
}
if (e.keyCode === KEYCODE_ESCAPE) {
e.currentTarget.blur();
}
const { value, selectionStart, selectionEnd } = e.currentTarget;
const tabCharacter = (insertSpaces ? ' ' : '\t').repeat(tabSize);
if (e.keyCode === KEYCODE_TAB && !ignoreTabKey && this.state.capture) {
// Prevent focus change
e.preventDefault();
if (e.shiftKey) {
// Unindent selected lines
const linesBeforeCaret = this._getLines(value, selectionStart);
const startLine = linesBeforeCaret.length - 1;
const endLine = this._getLines(value, selectionEnd).length - 1;
const nextValue = value
.split('\n')
.map((line, i) => {
if (
i >= startLine &&
i <= endLine &&
line.startsWith(tabCharacter)
) {
return line.substring(tabCharacter.length);
}
return line;
})
.join('\n');
if (value !== nextValue) {
const startLineText = linesBeforeCaret[startLine];
this._applyEdits({
value: nextValue,
// Move the start cursor if first line in selection was modified
// It was modified only if it started with a tab
selectionStart: startLineText?.startsWith(tabCharacter)
? selectionStart - tabCharacter.length
: selectionStart,
// Move the end cursor by total number of characters removed
selectionEnd: selectionEnd - (value.length - nextValue.length),
});
}
} else if (selectionStart !== selectionEnd) {
// Indent selected lines
const linesBeforeCaret = this._getLines(value, selectionStart);
const startLine = linesBeforeCaret.length - 1;
const endLine = this._getLines(value, selectionEnd).length - 1;
const startLineText = linesBeforeCaret[startLine];
this._applyEdits({
value: value
.split('\n')
.map((line, i) => {
if (i >= startLine && i <= endLine) {
return tabCharacter + line;
}
return line;
})
.join('\n'),
// Move the start cursor by number of characters added in first line of selection
// Don't move it if it there was no text before cursor
selectionStart:
startLineText && /\S/.test(startLineText)
? selectionStart + tabCharacter.length
: selectionStart,
// Move the end cursor by total number of characters added
selectionEnd:
selectionEnd + tabCharacter.length * (endLine - startLine + 1),
});
} else {
const updatedSelection = selectionStart + tabCharacter.length;
this._applyEdits({
// Insert tab character at caret
value:
value.substring(0, selectionStart) +
tabCharacter +
value.substring(selectionEnd),
// Update caret position
selectionStart: updatedSelection,
selectionEnd: updatedSelection,
});
}
} else if (e.keyCode === KEYCODE_BACKSPACE) {
const hasSelection = selectionStart !== selectionEnd;
const textBeforeCaret = value.substring(0, selectionStart);
if (textBeforeCaret.endsWith(tabCharacter) && !hasSelection) {
// Prevent default delete behaviour
e.preventDefault();
const updatedSelection = selectionStart - tabCharacter.length;
this._applyEdits({
// Remove tab character at caret
value:
value.substring(0, selectionStart - tabCharacter.length) +
value.substring(selectionEnd),
// Update caret position
selectionStart: updatedSelection,
selectionEnd: updatedSelection,
});
}
} else if (e.keyCode === KEYCODE_ENTER) {
// Ignore selections
if (selectionStart === selectionEnd) {
// Get the current line
const line = this._getLines(value, selectionStart).pop();
const matches = line?.match(/^\s+/);
if (matches?.[0]) {
e.preventDefault();
// Preserve indentation on inserting a new line
const indent = '\n' + matches[0];
const updatedSelection = selectionStart + indent.length;
this._applyEdits({
// Insert indentation character at caret
value:
value.substring(0, selectionStart) +
indent +
value.substring(selectionEnd),
// Update caret position
selectionStart: updatedSelection,
selectionEnd: updatedSelection,
});
}
}
} else if (
e.keyCode === KEYCODE_PARENS ||
e.keyCode === KEYCODE_BRACKETS ||
e.keyCode === KEYCODE_QUOTE ||
e.keyCode === KEYCODE_BACK_QUOTE
) {
let chars;
if (e.keyCode === KEYCODE_PARENS && e.shiftKey) {
chars = ['(', ')'];
} else if (e.keyCode === KEYCODE_BRACKETS) {
if (e.shiftKey) {
chars = ['{', '}'];
} else {
chars = ['[', ']'];
}
} else if (e.keyCode === KEYCODE_QUOTE) {
if (e.shiftKey) {
chars = ['"', '"'];
} else {
chars = ["'", "'"];
}
} else if (e.keyCode === KEYCODE_BACK_QUOTE && !e.shiftKey) {
chars = ['`', '`'];
}
// If text is selected, wrap them in the characters
if (selectionStart !== selectionEnd && chars) {
e.preventDefault();
this._applyEdits({
value:
value.substring(0, selectionStart) +
chars[0] +
value.substring(selectionStart, selectionEnd) +
chars[1] +
value.substring(selectionEnd),
// Update caret position
selectionStart,
selectionEnd: selectionEnd + 2,
});
}
} else if (
(isMacLike
? // Trigger undo with ⌘+Z on Mac
e.metaKey && e.keyCode === KEYCODE_Z
: // Trigger undo with Ctrl+Z on other platforms
e.ctrlKey && e.keyCode === KEYCODE_Z) &&
!e.shiftKey &&
!e.altKey
) {
e.preventDefault();
this._undoEdit();
} else if (
(isMacLike
? // Trigger redo with ⌘+Shift+Z on Mac
e.metaKey && e.keyCode === KEYCODE_Z && e.shiftKey
: isWindows
? // Trigger redo with Ctrl+Y on Windows
e.ctrlKey && e.keyCode === KEYCODE_Y
: // Trigger redo with Ctrl+Shift+Z on other platforms
e.ctrlKey && e.keyCode === KEYCODE_Z && e.shiftKey) &&
!e.altKey
) {
e.preventDefault();
this._redoEdit();
} else if (
e.keyCode === KEYCODE_M &&
e.ctrlKey &&
(isMacLike ? e.shiftKey : true)
) {
e.preventDefault();
// Toggle capturing tab key so users can focus away
this.setState((state) => ({
capture: !state.capture,
}));
}
};
private _handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const { value, selectionStart, selectionEnd } = e.currentTarget;
this._recordChange(
{
value,
selectionStart,
selectionEnd,
},
true
);
this.props.onValueChange(value);
};
private _history: History = {
stack: [],
offset: -1,
};
private _input: HTMLTextAreaElement | null = null;
get session() {
return {
history: this._history,
};
}
set session(session: { history: History }) {
this._history = session.history;
}
render() {
const {
value,
style,
padding,
highlight,
textareaId,
textareaClassName,
autoFocus,
disabled,
form,
maxLength,
minLength,
name,
placeholder,
readOnly,
required,
onClick,
onFocus,
onBlur,
onKeyUp,
/* eslint-disable @typescript-eslint/no-unused-vars */
onKeyDown,
onValueChange,
tabSize,
insertSpaces,
ignoreTabKey,
/* eslint-enable @typescript-eslint/no-unused-vars */
preClassName,
...rest
} = this.props;
const contentStyle = {
paddingTop: typeof padding === 'object' ? padding.top : padding,
paddingRight: typeof padding === 'object' ? padding.right : padding,
paddingBottom: typeof padding === 'object' ? padding.bottom : padding,
paddingLeft: typeof padding === 'object' ? padding.left : padding,
};
const highlighted = highlight(value);
return (
<div {...rest} style={{ ...styles.container, ...style }}>
<pre
className={preClassName}
aria-hidden="true"
style={{ ...styles.editor, ...styles.highlight, ...contentStyle }}
{...(typeof highlighted === 'string'
? { dangerouslySetInnerHTML: { __html: highlighted + '<br />' } }
: { children: highlighted })}
/>
<textarea
ref={(c) => (this._input = c)}
style={{
...styles.editor,
...styles.textarea,
...contentStyle,
}}
className={
className + (textareaClassName ? ` ${textareaClassName}` : '')
}
id={textareaId}
value={value}
onChange={this._handleChange}
onKeyDown={this._handleKeyDown}
onClick={onClick}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}
disabled={disabled}
form={form}
maxLength={maxLength}
minLength={minLength}
name={name}
placeholder={placeholder}
readOnly={readOnly}
required={required}
autoFocus={autoFocus}
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
data-gramm={false}
/>
{/* eslint-disable-next-line react/no-danger */}
<style dangerouslySetInnerHTML={{ __html: cssText }} />
</div>
);
}
}
const styles = {
container: {
position: 'relative',
textAlign: 'left',
boxSizing: 'border-box',
padding: 0,
overflow: 'hidden',
},
textarea: {
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: '100%',
resize: 'none',
color: 'inherit',
overflow: 'hidden',
MozOsxFontSmoothing: 'grayscale',
WebkitFontSmoothing: 'antialiased',
WebkitTextFillColor: 'transparent',
},
highlight: {
position: 'relative',
pointerEvents: 'none',
},
editor: {
margin: 0,
border: 0,
background: 'none',
boxSizing: 'inherit',
display: 'inherit',
fontFamily: 'inherit',
fontSize: 'inherit',
fontStyle: 'inherit',
fontVariantLigatures: 'inherit',
fontWeight: 'inherit',
letterSpacing: 'inherit',
lineHeight: 'inherit',
tabSize: 'inherit',
textIndent: 'inherit',
textRendering: 'inherit',
textTransform: 'inherit',
whiteSpace: 'pre-wrap',
wordBreak: 'keep-all',
overflowWrap: 'break-word',
},
} as const;
Event Timeline
Log In to Comment