mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
chat-js: lint changed files
This commit is contained in:
parent
8e028ad255
commit
8277802027
@ -1,10 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import cn from 'classnames';
|
||||
import { UnControlled as CodeEditor } from 'react-codemirror2'
|
||||
import CodeMirror from 'codemirror';
|
||||
import { UnControlled as CodeEditor } from 'react-codemirror2';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
@ -12,48 +9,27 @@ import 'codemirror/addon/display/placeholder';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { ShipSearch } from '/components/lib/ship-search';
|
||||
|
||||
import { uuid, uxToHex, hexToRgba } from '/lib/util';
|
||||
import { uxToHex } from '/lib/util';
|
||||
|
||||
const MARKDOWN_CONFIG = {
|
||||
name: "markdown",
|
||||
name: 'markdown',
|
||||
tokenTypeOverrides: {
|
||||
header: "presentation",
|
||||
quote: "presentation",
|
||||
list1: "presentation",
|
||||
list2: "presentation",
|
||||
list3: "presentation",
|
||||
hr: "presentation",
|
||||
image: "presentation",
|
||||
imageAltText: "presentation",
|
||||
imageMarker: "presentation",
|
||||
formatting: "presentation",
|
||||
linkInline: "presentation",
|
||||
linkEmail: "presentation",
|
||||
linkText: "presentation",
|
||||
linkHref: "presentation",
|
||||
header: 'presentation',
|
||||
quote: 'presentation',
|
||||
list1: 'presentation',
|
||||
list2: 'presentation',
|
||||
list3: 'presentation',
|
||||
hr: 'presentation',
|
||||
image: 'presentation',
|
||||
imageAltText: 'presentation',
|
||||
imageMarker: 'presentation',
|
||||
formatting: 'presentation',
|
||||
linkInline: 'presentation',
|
||||
linkEmail: 'presentation',
|
||||
linkText: 'presentation',
|
||||
linkHref: 'presentation'
|
||||
}
|
||||
}
|
||||
|
||||
// line height
|
||||
const INPUT_LINE_HEIGHT = 28;
|
||||
|
||||
const INPUT_TOP_PADDING = 3;
|
||||
|
||||
|
||||
function getAdvance(a, b) {
|
||||
let res = '';
|
||||
if(!a) {
|
||||
return b;
|
||||
}
|
||||
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return res;
|
||||
}
|
||||
res = res.concat(a[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export class ChatInput extends Component {
|
||||
constructor(props) {
|
||||
@ -73,14 +49,12 @@ export class ChatInput extends Component {
|
||||
this.completePatp = this.completePatp.bind(this);
|
||||
this.clearSearch = this.clearSearch.bind(this);
|
||||
|
||||
|
||||
this.toggleCode = this.toggleCode.bind(this);
|
||||
|
||||
this.editor = null;
|
||||
|
||||
|
||||
// perf testing:
|
||||
/*let closure = () => {
|
||||
/* let closure = () => {
|
||||
let x = 0;
|
||||
for (var i = 0; i < 30; i++) {
|
||||
x++;
|
||||
@ -102,26 +76,25 @@ export class ChatInput extends Component {
|
||||
past: function(input) {
|
||||
return input === 'just now'
|
||||
? input
|
||||
: input + ' ago'
|
||||
: input + ' ago';
|
||||
},
|
||||
s : 'just now',
|
||||
future: "in %s",
|
||||
future: 'in %s',
|
||||
ss : '%d sec',
|
||||
m: "a minute",
|
||||
mm: "%d min",
|
||||
h: "an hr",
|
||||
hh: "%d hrs",
|
||||
d: "a day",
|
||||
dd: "%d days",
|
||||
M: "a month",
|
||||
MM: "%d months",
|
||||
y: "a year",
|
||||
yy: "%d years"
|
||||
m: 'a minute',
|
||||
mm: '%d min',
|
||||
h: 'an hr',
|
||||
hh: '%d hrs',
|
||||
d: 'a day',
|
||||
dd: '%d days',
|
||||
M: 'a month',
|
||||
MM: '%d months',
|
||||
y: 'a year',
|
||||
yy: '%d years'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
nextAutocompleteSuggestion(backward = false) {
|
||||
const { patpSuggestions } = this.state;
|
||||
let idx = patpSuggestions.findIndex(s => s === this.state.selectedSuggestion);
|
||||
@ -135,12 +108,11 @@ export class ChatInput extends Component {
|
||||
this.setState({ selectedSuggestion: patpSuggestions[idx] });
|
||||
}
|
||||
|
||||
|
||||
patpAutocomplete(message, fresh = false) {
|
||||
patpAutocomplete(message) {
|
||||
const match = /~([a-zA-Z\-]*)$/.exec(message);
|
||||
|
||||
if (!match ) {
|
||||
this.setState({ patpSearch: null })
|
||||
this.setState({ patpSearch: null });
|
||||
return;
|
||||
}
|
||||
this.setState({ patpSearch: match[1].toLowerCase() });
|
||||
@ -149,7 +121,7 @@ export class ChatInput extends Component {
|
||||
clearSearch() {
|
||||
this.setState({
|
||||
patpSearch: null
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
completePatp(suggestion) {
|
||||
@ -170,15 +142,12 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
messageChange(editor, data, value) {
|
||||
|
||||
const { patpSearch } = this.state;
|
||||
if(patpSearch !== null) {
|
||||
this.patpAutocomplete(value, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getLetterType(letter) {
|
||||
if (letter.startsWith('/me')) {
|
||||
letter = letter.slice(3);
|
||||
@ -190,22 +159,21 @@ export class ChatInput extends Component {
|
||||
|
||||
return {
|
||||
me: letter
|
||||
}
|
||||
};
|
||||
} else if (this.isUrl(letter)) {
|
||||
return {
|
||||
url: letter
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
text: letter
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
isUrl(string) {
|
||||
try {
|
||||
let websiteTest = new RegExp(''
|
||||
+ /((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source
|
||||
const websiteTest = new RegExp(String(/((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source)
|
||||
);
|
||||
return websiteTest.test(string);
|
||||
} catch (e) {
|
||||
@ -235,10 +203,10 @@ export class ChatInput extends Component {
|
||||
return;
|
||||
}
|
||||
let message = [];
|
||||
editorMessage.split(" ").map((each) => {
|
||||
editorMessage.split(' ').map((each) => {
|
||||
if (this.isUrl(each)) {
|
||||
if (message.length > 0) {
|
||||
message = message.join(" ");
|
||||
message = message.join(' ');
|
||||
message = this.getLetterType(message);
|
||||
props.api.chat.message(
|
||||
props.station,
|
||||
@ -248,22 +216,20 @@ export class ChatInput extends Component {
|
||||
);
|
||||
message = [];
|
||||
}
|
||||
let URL = this.getLetterType(each);
|
||||
const URL = this.getLetterType(each);
|
||||
props.api.chat.message(
|
||||
props.station,
|
||||
`~${window.ship}`,
|
||||
Date.now(),
|
||||
URL
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return message.push(each);
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
if (message.length > 0) {
|
||||
message = message.join(" ");
|
||||
message = message.join(' ');
|
||||
message = this.getLetterType(message);
|
||||
props.api.chat.message(
|
||||
props.station,
|
||||
@ -275,10 +241,9 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
// perf:
|
||||
//setTimeout(this.closure, 2000);
|
||||
// setTimeout(this.closure, 2000);
|
||||
|
||||
this.editor.setValue('');
|
||||
|
||||
}
|
||||
|
||||
toggleCode() {
|
||||
@ -289,7 +254,7 @@ export class ChatInput extends Component {
|
||||
} else {
|
||||
this.setState({ code: true });
|
||||
this.editor.setOption('mode', null);
|
||||
this.editor.setOption('placeholder', "Code...");
|
||||
this.editor.setOption('placeholder', 'Code...');
|
||||
}
|
||||
const value = this.editor.getValue();
|
||||
|
||||
@ -298,29 +263,26 @@ export class ChatInput extends Component {
|
||||
this.editor.setValue(' ');
|
||||
this.editor.setValue('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let color = !!props.ownerContact
|
||||
const color = props.ownerContact
|
||||
? uxToHex(props.ownerContact.color) : '000000';
|
||||
|
||||
let sigilClass = !!props.ownerContact
|
||||
? "" : "mix-blend-diff";
|
||||
const sigilClass = props.ownerContact
|
||||
? '' : 'mix-blend-diff';
|
||||
|
||||
const candidates = _.chain(this.props.envelopes)
|
||||
.defaultTo([])
|
||||
.map("author")
|
||||
.map('author')
|
||||
.uniq()
|
||||
.reverse()
|
||||
.value();
|
||||
|
||||
const codeTheme = state.code ? ' code' : '';
|
||||
|
||||
const completeActive = state.patpSearch !== null;
|
||||
|
||||
const options = {
|
||||
mode: MARKDOWN_CONFIG,
|
||||
theme: 'tlon' + codeTheme,
|
||||
@ -328,20 +290,21 @@ export class ChatInput extends Component {
|
||||
lineWrapping: true,
|
||||
scrollbarStyle: 'native',
|
||||
cursorHeight: 0.85,
|
||||
placeholder: state.code ? "Code..." : props.placeholder,
|
||||
placeholder: state.code ? 'Code...' : props.placeholder,
|
||||
extraKeys: {
|
||||
Tab: (cm) =>
|
||||
Tab: cm =>
|
||||
this.patpAutocomplete(cm.getValue(), true),
|
||||
'Enter': (cm) =>
|
||||
'Enter': cm =>
|
||||
this.messageSubmit(),
|
||||
'Shift-3': (cm) =>
|
||||
'Shift-3': cm =>
|
||||
this.toggleCode()
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pa3 cf flex black white-d bt b--gray4 b--gray1-d bg-white bg-gray0-d relative"
|
||||
style={{ flexGrow: 1 }}>
|
||||
style={{ flexGrow: 1 }}
|
||||
>
|
||||
<ShipSearch
|
||||
popover
|
||||
onSelect={this.completePatp}
|
||||
@ -358,7 +321,8 @@ export class ChatInput extends Component {
|
||||
marginTop: 6,
|
||||
flexBasis: 24,
|
||||
height: 24
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Sigil
|
||||
ship={window.ship}
|
||||
size={24}
|
||||
@ -368,10 +332,13 @@ export class ChatInput extends Component {
|
||||
</div>
|
||||
<div
|
||||
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
|
||||
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 48px)' }}>
|
||||
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 48px)' }}
|
||||
>
|
||||
<CodeEditor
|
||||
options={options}
|
||||
editorDidMount={editor => { this.editor = editor; }}
|
||||
editorDidMount={(editor) => {
|
||||
this.editor = editor;
|
||||
}}
|
||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,25 +1,26 @@
|
||||
import React, { Component } from "react";
|
||||
import _ from "lodash";
|
||||
import urbitOb from "urbit-ob";
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import Mousetrap from 'mousetrap';
|
||||
|
||||
import cn from "classnames";
|
||||
import { Sigil } from "/components/lib/icons/sigil";
|
||||
import { hexToRgba, uxToHex, deSig } from "/lib/util";
|
||||
import cn from 'classnames';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { hexToRgba, uxToHex, deSig } from '/lib/util';
|
||||
|
||||
function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
||||
let contact = contacts[ship];
|
||||
let color = "#000000";
|
||||
let sigilClass = "v-mid mix-blend-diff";
|
||||
const contact = contacts[ship];
|
||||
let color = '#000000';
|
||||
let sigilClass = 'v-mid mix-blend-diff';
|
||||
let nickname;
|
||||
let nameStyle = {};
|
||||
const nameStyle = {};
|
||||
const isSelected = ship === selected;
|
||||
if (contact) {
|
||||
const hex = uxToHex(contact.color);
|
||||
color = `#${hex}`;
|
||||
nameStyle.color = hexToRgba(hex, 0.7);
|
||||
nameStyle.textShadow = "0px 0px 0px #000";
|
||||
nameStyle.filter = "contrast(1.3) saturate(1.5)";
|
||||
sigilClass = "v-mid";
|
||||
nameStyle.textShadow = '0px 0px 0px #000';
|
||||
nameStyle.filter = 'contrast(1.3) saturate(1.5)';
|
||||
sigilClass = 'v-mid';
|
||||
nickname = contact.nickname;
|
||||
}
|
||||
|
||||
@ -27,21 +28,21 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
||||
<div
|
||||
onClick={() => onSelect(ship)}
|
||||
className={cn(
|
||||
"f8 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center",
|
||||
'f8 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center',
|
||||
{
|
||||
"white-d bg-gray0-d bg-white": !isSelected,
|
||||
"black-d bg-gray1-d bg-gray4": isSelected
|
||||
'white-d bg-gray0-d bg-white': !isSelected,
|
||||
'black-d bg-gray1-d bg-gray4': isSelected
|
||||
}
|
||||
)}
|
||||
key={ship}
|
||||
>
|
||||
<Sigil ship={"~" + ship} size={24} color={color} classes={sigilClass} />
|
||||
<Sigil ship={'~' + ship} size={24} color={color} classes={sigilClass} />
|
||||
{nickname && (
|
||||
<p style={nameStyle} className="dib ml4 b">
|
||||
{nickname}
|
||||
</p>
|
||||
)}
|
||||
<div className="mono gray2 gray4-d ml4">{"~" + ship}</div>
|
||||
<div className="mono gray2 gray4-d ml4">{'~' + ship}</div>
|
||||
<p className="nowrap ml4">{status}</p>
|
||||
</div>
|
||||
);
|
||||
@ -58,31 +59,30 @@ export class ShipSearch extends Component {
|
||||
};
|
||||
|
||||
this.keymap = {
|
||||
Tab: (cm) =>
|
||||
Tab: cm =>
|
||||
this.nextAutocompleteSuggestion(),
|
||||
'Shift-Tab': (cm) =>
|
||||
'Shift-Tab': cm =>
|
||||
this.nextAutocompleteSuggestion(true),
|
||||
'Up': (cm) =>
|
||||
'Up': cm =>
|
||||
this.nextAutocompleteSuggestion(true),
|
||||
'Escape': (cm) =>
|
||||
'Escape': cm =>
|
||||
this.props.onClear(),
|
||||
'Down': (cm) =>
|
||||
'Down': cm =>
|
||||
this.nextAutocompleteSuggestion(),
|
||||
'Enter': (cm) => {
|
||||
if(this.props.searchTerm !== null) {
|
||||
this.props.onSelect(this.state.selected);
|
||||
}
|
||||
},
|
||||
'Shift-3': (cm) =>
|
||||
'Shift-3': cm =>
|
||||
this.toggleCode()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(this.props.searchTerm !== null) {
|
||||
this.updateSuggestions(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@ -109,12 +109,11 @@ export class ShipSearch extends Component {
|
||||
} else if (prevProps.searchTerm !== props.searchTerm) {
|
||||
this.updateSuggestions(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateSuggestions(isStale = false) {
|
||||
const needle = this.props.searchTerm;
|
||||
const matchString = hay => {
|
||||
const matchString = (hay) => {
|
||||
hay = hay.toLowerCase();
|
||||
|
||||
return (
|
||||
@ -132,7 +131,7 @@ export class ShipSearch extends Component {
|
||||
.filter(
|
||||
({ nickname, ship }) => matchString(nickname) || matchString(ship)
|
||||
)
|
||||
.map("ship")
|
||||
.map('ship')
|
||||
.value();
|
||||
|
||||
const exactMatch = urbitOb.isValidPatp(`~${needle}`) ? [needle] : [];
|
||||
@ -179,7 +178,7 @@ export class ShipSearch extends Component {
|
||||
this.mousetrap = new Mousetrap(this.props.inputRef);
|
||||
}
|
||||
|
||||
this.mousetrap.bind("enter", e => {
|
||||
this.mousetrap.bind('enter', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@ -189,22 +188,22 @@ export class ShipSearch extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
this.mousetrap.bind("tab", e => {
|
||||
this.mousetrap.bind('tab', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextAutocompleteSuggestion(false);
|
||||
});
|
||||
this.mousetrap.bind(["up", "shift+tab"], e => {
|
||||
this.mousetrap.bind(['up', 'shift+tab'], (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextAutocompleteSuggestion(true);
|
||||
});
|
||||
this.mousetrap.bind("down", e => {
|
||||
this.mousetrap.bind('down', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextAutocompleteSuggestion(false);
|
||||
});
|
||||
this.mousetrap.bind("esc", e => {
|
||||
this.mousetrap.bind('esc', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onClear();
|
||||
@ -213,7 +212,7 @@ export class ShipSearch extends Component {
|
||||
|
||||
unbindShortcuts() {
|
||||
if(!this.props.inputRef) {
|
||||
this.unbindCmShortcuts()
|
||||
this.unbindCmShortcuts();
|
||||
}
|
||||
|
||||
if (!this.state.bound) {
|
||||
@ -221,11 +220,11 @@ export class ShipSearch extends Component {
|
||||
}
|
||||
|
||||
this.setState({ bound: false });
|
||||
this.mousetrap.unbind("enter");
|
||||
this.mousetrap.unbind("tab");
|
||||
this.mousetrap.unbind(["up", "shift+tab"]);
|
||||
this.mousetrap.unbind("down");
|
||||
this.mousetrap.unbind("esc");
|
||||
this.mousetrap.unbind('enter');
|
||||
this.mousetrap.unbind('tab');
|
||||
this.mousetrap.unbind(['up', 'shift+tab']);
|
||||
this.mousetrap.unbind('down');
|
||||
this.mousetrap.unbind('esc');
|
||||
}
|
||||
|
||||
nextAutocompleteSuggestion(backward = false) {
|
||||
@ -249,22 +248,22 @@ export class ShipSearch extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const popoverClasses = (popover && " absolute ") || " ";
|
||||
const popoverClasses = (popover && ' absolute ') || ' ';
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
popover
|
||||
? {
|
||||
bottom: "90%",
|
||||
left: "48px"
|
||||
bottom: '90%',
|
||||
left: '48px'
|
||||
}
|
||||
: {}
|
||||
}
|
||||
className={
|
||||
"black white-d bg-white bg-gray0-d " +
|
||||
"w7 pv3 z-1 mt1 ba b--gray1-d b--gray4" +
|
||||
'black white-d bg-white bg-gray0-d ' +
|
||||
'w7 pv3 z-1 mt1 ba b--gray1-d b--gray4' +
|
||||
popoverClasses +
|
||||
className || ""
|
||||
className || ''
|
||||
}
|
||||
>
|
||||
{suggestions.slice(0, 5).map(ship => (
|
||||
@ -285,7 +284,7 @@ export class ShipSearchInput extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
searchTerm: ""
|
||||
searchTerm: ''
|
||||
};
|
||||
|
||||
this.inputRef = null;
|
||||
@ -308,13 +307,13 @@ export class ShipSearchInput extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("mousedown", this.onClick);
|
||||
document.addEventListener("touchstart", this.onClick);
|
||||
document.addEventListener('mousedown', this.onClick);
|
||||
document.addEventListener('touchstart', this.onClick);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousedown", this.onClick);
|
||||
document.removeEventListener("touchstart", this.onClick);
|
||||
document.removeEventListener('mousedown', this.onClick);
|
||||
document.removeEventListener('touchstart', this.onClick);
|
||||
}
|
||||
|
||||
setInputRef(ref) {
|
||||
@ -337,11 +336,11 @@ export class ShipSearchInput extends Component {
|
||||
return (
|
||||
<div
|
||||
ref={ref => (this.popoverRef = ref)}
|
||||
style={{ top: "150%", left: "-80px" }}
|
||||
style={{ top: '150%', left: '-80px' }}
|
||||
className="b--gray2 b--solid ba absolute bg-white bg-gray0-d shadow-5"
|
||||
>
|
||||
<textarea
|
||||
style={{ resize: "none", maxWidth: '200px' }}
|
||||
style={{ resize: 'none', maxWidth: '200px' }}
|
||||
className="ma2 pa2 b--gray4 ba b--solid w7 db bg-gray0-d white-d"
|
||||
rows={1}
|
||||
autocapitalise="none"
|
||||
|
Loading…
Reference in New Issue
Block a user