Advertisement
Guest User

Untitled

a guest
Aug 20th, 2019
110
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.45 KB | None | 0 0
  1. import React, { Component } from 'react';
  2. import _ from 'lodash';
  3. import moment from 'moment';
  4. import Mousetrap from 'mousetrap';
  5. import classnames from 'classnames';
  6.  
  7. import { Sigil } from '/components/lib/icons/sigil';
  8. import { IconSend } from '/components/lib/icons/icon-send';
  9.  
  10. import { uuid } from '/lib/util';
  11. import { storePendingMessage } from '/lib/util';
  12.  
  13.  
  14. export class ChatInput extends Component {
  15.  
  16. constructor(props) {
  17. super(props);
  18.  
  19. /*let closure = () => {
  20. let aud, sep;
  21. let wen = Date.now();
  22. let aut = window.ship;
  23.  
  24. let config = props.configs[props.station];
  25.  
  26. aud = [props.station];
  27. sep = {
  28. lin: {
  29. msg: Date.now().toString(),
  30. pat: false
  31. }
  32. }
  33.  
  34. let uid;
  35. let message;
  36.  
  37. for (var i = 0; i < 10; i++) {
  38. uid = uuid();
  39. message = {
  40. uid,
  41. aut,
  42. wen,
  43. aud,
  44. sep,
  45. };
  46.  
  47. props.api.hall({
  48. convey: [message]
  49. });
  50. }
  51.  
  52. setTimeout(closure, 1000);
  53. };
  54.  
  55. setTimeout(closure, 2000);*/
  56.  
  57. this.state = {
  58. message: '',
  59. messageType: 'lin',
  60. clipboard: null
  61. };
  62.  
  63. this.textareaRef = React.createRef();
  64.  
  65. this.messageSubmit = this.messageSubmit.bind(this);
  66. this.messageChange = this.messageChange.bind(this);
  67.  
  68. moment.updateLocale('en', {
  69. relativeTime : {
  70. past: function(input) {
  71. return input === 'just now'
  72. ? input
  73. : input + ' ago'
  74. },
  75. s : 'just now',
  76. future: "in %s",
  77. ss : '%d sec',
  78. m: "a minute",
  79. mm: "%d min",
  80. h: "an hr",
  81. hh: "%d hrs",
  82. d: "a day",
  83. dd: "%d days",
  84. M: "a month",
  85. MM: "%d months",
  86. y: "a year",
  87. yy: "%d years"
  88. }
  89. });
  90. }
  91.  
  92. componentDidMount() {
  93. this.bindShortcuts();
  94. }
  95.  
  96. bindShortcuts() {
  97. Mousetrap(this.textareaRef.current).bind('enter', e => {
  98. e.preventDefault();
  99. e.stopPropagation();
  100.  
  101. this.messageSubmit(e);
  102. });
  103. }
  104.  
  105. messageChange(event) {
  106. const input = event.target.value;
  107. const previous = this.state.message;
  108. //NOTE dumb hack to work around paste event flow oddities
  109. const pasted = (previous.length === 0 && input.length > 1);
  110. if (input !== this.state.clipboard) {
  111. this.setState({
  112. message: input,
  113. messageType: this.getSpeechType(input),
  114. clipboard: (pasted ? input : null)
  115. });
  116. }
  117. }
  118.  
  119. getSpeechType(input) {
  120. if (input[0] === '#') {
  121. return 'exp';
  122. } else if (input.indexOf('\n') >= 0) {
  123. return 'fat';
  124. } else if (input[0] === '@') {
  125. return 'lin@';
  126. } else if (this.isUrl(input)) {
  127. return 'url';
  128. } else {
  129. return 'lin';
  130. }
  131. }
  132.  
  133. getSpeechStyle(type, clipboard) {
  134. switch (type) {
  135. case 'lin@':
  136. return 'fs-italic';
  137. case 'url':
  138. return 'td-underline';
  139. case 'exp':
  140. return 'code';
  141. case 'fat':
  142. if (clipboard) return 'code';
  143. default:
  144. return '';
  145. }
  146. }
  147.  
  148. isUrl(string) {
  149. try {
  150. const urlObject = new URL(string);
  151. //NOTE we check for a host to ensure a url is actually being posted
  152. // to combat false positives for things like "marzod: ur cool".
  153. // this does mean you can't send "mailto:e@ma.il" as %url message,
  154. // but the desirability of that seems questionable anyway.
  155. return (urlObject.host !== '');
  156. } catch (e) {
  157. return false;
  158. }
  159. }
  160.  
  161. // turns select urls into arvo:// urls
  162. //
  163. // we detect app names from the url. if the app is known to handle requests
  164. // for remote data (instead of serving only from the host) we transfor the
  165. // url into a generic arvo:// one.
  166. // the app name format is pretty distinct and rare to find in the non-urbit
  167. // wild, but this could still result in false positives for older-school
  168. // websites serving pages under /~user paths.
  169. // we could match only on ship.arvo.network, but that would exclude those
  170. // running on localhost or under a custom domain.
  171. //
  172. //
  173. globalizeUrl(url) {
  174. const urlObject = new URL(url);
  175. const app = urlObject.pathname.split('/')[1];
  176. if (app === '~chat' ||
  177. app === '~publish') {
  178. //TODO send proper url speeches once hall starts using a url type that
  179. // supports non-http protocols.
  180. return { lin: {
  181. msg: 'arvo://' + url.slice(urlObject.origin.length),
  182. pat: false
  183. } };
  184. } else {
  185. return {url};
  186. }
  187. }
  188.  
  189. speechFromInput(content, type, clipboard) {
  190. switch (type) {
  191. case 'lin':
  192. return { lin: {
  193. msg: content,
  194. pat: false
  195. } };
  196. //
  197. case 'lin@':
  198. return { lin: {
  199. msg: content.slice(1),
  200. pat: true
  201. } };
  202. //
  203. case 'url':
  204. return this.globalizeUrl(content);
  205. //
  206. case 'exp':
  207. // remove leading #
  208. content = content.slice(1);
  209. // remove insignificant leading whitespace.
  210. // aces might be relevant to style.
  211. while (content[0] === '\n') {
  212. content = content.slice(1);
  213. }
  214. return { exp: {
  215. exp: content
  216. } };
  217. //
  218. case 'fat':
  219. // clipboard contents
  220. if (clipboard !== null) {
  221. return { fat: {
  222. sep: { lin: { msg: '', pat: false } },
  223. tac: { name: {
  224. nom: 'clipboard',
  225. tac: { text: content }
  226. } }
  227. } };
  228. // long-form message
  229. } else {
  230. const lines = content.split('\n');
  231. return { fat: {
  232. sep: { lin: {
  233. msg: lines[0],
  234. pat: false
  235. } },
  236. tac: { name: {
  237. nom: 'long-form',
  238. tac: { text: lines.slice(1).join('\n') }
  239. } },
  240. } };
  241. }
  242. //
  243. default:
  244. throw new Error('Unimplemented speech type', type);
  245. }
  246. }
  247.  
  248. messageSubmit() {
  249. const { props, state } = this;
  250.  
  251. if (state.message === '') {
  252. return;
  253. }
  254.  
  255. let message = {
  256. uid: uuid(),
  257. aut: window.ship,
  258. wen: Date.now(),
  259. aud: [props.station],
  260. sep: this.speechFromInput(
  261. state.message,
  262. state.messageType,
  263. state.clipboard
  264. )
  265. };
  266.  
  267. // fake api sends message to store,
  268. // with an injected pending: true here
  269.  
  270. props.api.addPendingMessage(message);
  271.  
  272. // reducer needs logic to remove 'pending', if it exists, from
  273. // incoming messages
  274.  
  275. props.api.hall(
  276. {
  277. convey: [message]
  278. }
  279. );
  280.  
  281. this.setState({
  282. message: '',
  283. messageType: 'lin'
  284. });
  285. }
  286.  
  287. readOnlyRender() {
  288. return (
  289. <div className="mt2 pa3 cf flex black bt o-50">
  290. <div className="fl" style={{ flexBasis: 35, height: 40 }}>
  291. <Sigil ship={window.ship} size={32} />
  292. </div>
  293. <div className="fr h-100 flex pa2" style={{ flexGrow: 1, height: 40 }}>
  294. <p>This chat is read only and you cannot post.</p>
  295. </div>
  296. </div>
  297. );
  298. }
  299.  
  300. render() {
  301. const { props, state } = this;
  302.  
  303. if (props.security && props.security.sec !== 'channel' &&
  304. !props.security.sis.includes(window.ship)) {
  305. return this.readOnlyRender();
  306. }
  307.  
  308. return (
  309. <div className="pa3 cf flex black bt b--black-30" style={{ flexGrow: 1 }}>
  310. <div className="fl" style={{
  311. marginTop: 4,
  312. flexBasis: 32,
  313. height: 36
  314. }}>
  315. <Sigil ship={window.ship} size={32} />
  316. </div>
  317. <div className="fr h-100 flex" style={{ flexGrow: 1 }}>
  318. <textarea
  319. className={'ml2 mt2 mr2 bn ' +
  320. this.getSpeechStyle(state.messageType, state.clipboard)
  321. }
  322. style={{ flexGrow: 1, height: 40, resize: 'none' }}
  323. ref={this.textareaRef}
  324. placeholder={props.placeholder}
  325. value={state.message}
  326. onChange={this.messageChange}
  327. autoFocus={true}
  328. />
  329. <div className="pointer" onClick={this.messageSubmit}>
  330. <IconSend />
  331. </div>
  332. </div>
  333. </div>
  334. );
  335. }
  336. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement