Full styleguide now available via `yarn styleguide`
Due to a number of hacks, the style guide can be used to show Backbone views. This will allow a smooth path from the old way of doing things to the new.pull/1/head
							parent
							
								
									893fb1cb9e
								
							
						
					
					
						commit
						1326b26585
					
				@ -0,0 +1,6 @@
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
<util.MessageParents theme="android">
 | 
			
		||||
  <Message />
 | 
			
		||||
</util.MessageParents>
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A placeholder Message component, giving the structure of a plain message with none of
 | 
			
		||||
 * the dynamic functionality. We can build off of this going forward.
 | 
			
		||||
 */
 | 
			
		||||
export class Message extends React.Component<{}, {}> {
 | 
			
		||||
  public render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <li className="entry outgoing sent delivered">
 | 
			
		||||
        <span className="avatar" />
 | 
			
		||||
        <div className="bubble">
 | 
			
		||||
          <div className="sender" dir="auto" />
 | 
			
		||||
          <div className="attachments" />
 | 
			
		||||
          <p className="content" dir="auto">
 | 
			
		||||
            <span className="body">
 | 
			
		||||
              Hi there. How are you doing? Feeling pretty good? Awesome.
 | 
			
		||||
            </span>
 | 
			
		||||
          </p>
 | 
			
		||||
          <div className="meta">
 | 
			
		||||
            <span
 | 
			
		||||
              className="timestamp"
 | 
			
		||||
              data-timestamp="1522800995425"
 | 
			
		||||
              title="Tue, Apr 3, 2018 5:16 PM"
 | 
			
		||||
            >
 | 
			
		||||
              1 minute ago
 | 
			
		||||
            </span>
 | 
			
		||||
            <span className="status hide" />
 | 
			
		||||
            <span className="timer" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </li>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,2 @@
 | 
			
		||||
 | 
			
		||||
This is Reply.md.
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface IProps { name: string; }
 | 
			
		||||
 | 
			
		||||
interface IState { count: number; }
 | 
			
		||||
 | 
			
		||||
export class Reply extends React.Component<IProps, IState> {
 | 
			
		||||
  public render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div>Placeholder</div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,38 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
interface IProps { name: string; }
 | 
			
		||||
 | 
			
		||||
interface IState { count: number; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const items = [
 | 
			
		||||
  'one',
 | 
			
		||||
  'two',
 | 
			
		||||
  'three',
 | 
			
		||||
  'four',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export class InlineReply extends React.Component<IProps, IState> {
 | 
			
		||||
  constructor(props: IProps) {
 | 
			
		||||
    super(props);
 | 
			
		||||
 | 
			
		||||
    this.state = {
 | 
			
		||||
      count: 0,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public render() {
 | 
			
		||||
    const { name } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        This is a basic component. Hi there, {name}!
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function greeter2(person: any) {
 | 
			
		||||
    // console.log(items);
 | 
			
		||||
    return `Hello, ${person}`;
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +0,0 @@
 | 
			
		||||
import { InlineReply } from './sub/test2';
 | 
			
		||||
 | 
			
		||||
// console.log(InlineReply);
 | 
			
		||||
 | 
			
		||||
export function greeter(person: any) {
 | 
			
		||||
    return 'Hello, ' + person;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
Rendering a real `Whisper.MessageView` using `<util.MessageParents />` and
 | 
			
		||||
`<util.BackboneWrapper />`.
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
const model = new Whisper.Message({
 | 
			
		||||
  type: 'outgoing',
 | 
			
		||||
  body: 'text',
 | 
			
		||||
  sent_at: Date.now() - 5000,
 | 
			
		||||
})
 | 
			
		||||
const View = Whisper.MessageView;
 | 
			
		||||
const options = {
 | 
			
		||||
  model,
 | 
			
		||||
};
 | 
			
		||||
<util.MessageParents theme="android">
 | 
			
		||||
  <util.BackboneWrapper
 | 
			
		||||
    View={View}
 | 
			
		||||
    options={options}
 | 
			
		||||
  />
 | 
			
		||||
</util.MessageParents>
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
  /** The View class, which will be instantiated then treated like a Backbone View */
 | 
			
		||||
  readonly View: IBackboneViewConstructor;
 | 
			
		||||
  /** Options to be passed along to the view when constructed */
 | 
			
		||||
  readonly options: object;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IBackboneView {
 | 
			
		||||
  remove: () => void;
 | 
			
		||||
  render: () => void;
 | 
			
		||||
  el: HTMLElement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IBackboneViewConstructor {
 | 
			
		||||
    new (options: object): IBackboneView;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Allows Backbone Views to be rendered inside of React (primarily for the styleguide)
 | 
			
		||||
 * while we slowly replace the internals of a given Backbone view with React.
 | 
			
		||||
 */
 | 
			
		||||
export class BackboneWrapper extends React.Component<IProps, {}> {
 | 
			
		||||
  protected el: Element | null;
 | 
			
		||||
  protected view: IBackboneView | null;
 | 
			
		||||
  protected setEl: (element: HTMLDivElement | null) => void;
 | 
			
		||||
 | 
			
		||||
  constructor(props: IProps) {
 | 
			
		||||
    super(props);
 | 
			
		||||
 | 
			
		||||
    this.el = null;
 | 
			
		||||
    this.view = null;
 | 
			
		||||
 | 
			
		||||
    this.setEl = (element: HTMLDivElement | null) => {
 | 
			
		||||
      this.el = element;
 | 
			
		||||
      this.setup();
 | 
			
		||||
    };
 | 
			
		||||
    this.setup = this.setup.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setup() {
 | 
			
		||||
    const { el } = this;
 | 
			
		||||
    const { View, options } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!el) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.view = new View(options);
 | 
			
		||||
    this.view.render();
 | 
			
		||||
 | 
			
		||||
    // It's important to let the view create its own root DOM element. This ensures that
 | 
			
		||||
    //   its tagName property actually takes effect.
 | 
			
		||||
    el.appendChild(this.view.el);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public teardown() {
 | 
			
		||||
    if (!this.view) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.view.remove();
 | 
			
		||||
    this.view = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public componentWillUnmount() {
 | 
			
		||||
    this.teardown();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public shouldComponentUpdate() {
 | 
			
		||||
    // we're handling all updates manually
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public render() {
 | 
			
		||||
    return <div ref={this.setEl} />;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
 | 
			
		||||
The simplest example of using the `<MessagesParents />` component:
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
<util.MessageParents theme="android">
 | 
			
		||||
  <div>Just a plain bit of text</div>
 | 
			
		||||
</util.MessageParents>
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
  /**
 | 
			
		||||
   * Corresponds to the theme setting in the app, and the class added to the root element.
 | 
			
		||||
   */
 | 
			
		||||
  theme: "ios" | "android" | "android-dark";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides the parent elements necessary to allow the main Signal Desktop stylesheet to
 | 
			
		||||
 * apply (with no changes) to messages in this context.
 | 
			
		||||
 */
 | 
			
		||||
export class MessageParents extends React.Component<IProps, {}> {
 | 
			
		||||
  public render() {
 | 
			
		||||
    const { theme } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={theme}>
 | 
			
		||||
        <div className="conversation">
 | 
			
		||||
          <div className="discussion-container" style={{padding: '0.5em'}}>
 | 
			
		||||
            <ul className="message-list">
 | 
			
		||||
              {this.props.children}
 | 
			
		||||
            </ul>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
// Helper components used in the styleguide, exposed at 'util' in the global scope via the
 | 
			
		||||
//   context option in reaat-styleguidist.
 | 
			
		||||
 | 
			
		||||
export { MessageParents } from './MessageParents';
 | 
			
		||||
export { BackboneWrapper } from './BackboneWrapper';
 | 
			
		||||
 | 
			
		||||
// Here we can make things inside Webpack available to Backbone views like preload.js.
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom';
 | 
			
		||||
 | 
			
		||||
import { Message } from '../conversation/Message';
 | 
			
		||||
import { Reply } from '../conversation/Reply';
 | 
			
		||||
 | 
			
		||||
// Required, or TypeScript complains about adding keys to window
 | 
			
		||||
const parent = window as any;
 | 
			
		||||
 | 
			
		||||
parent.React = React;
 | 
			
		||||
parent.ReactDOM = ReactDOM;
 | 
			
		||||
 | 
			
		||||
const SignalReact = parent.Signal.React = parent.Signal.React || {};
 | 
			
		||||
 | 
			
		||||
SignalReact.Message = Message;
 | 
			
		||||
SignalReact.Reply = Reply;
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
/* global Backbone: false */
 | 
			
		||||
 | 
			
		||||
// Additional globals used:
 | 
			
		||||
//   window.React
 | 
			
		||||
//   window.ReactDOM
 | 
			
		||||
//   window.i18n
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line func-names
 | 
			
		||||
(function () {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  window.Whisper = window.Whisper || {};
 | 
			
		||||
 | 
			
		||||
  window.Whisper.ReactWrapper = Backbone.View.extend({
 | 
			
		||||
    className: 'react-wrapper',
 | 
			
		||||
    initialize(options) {
 | 
			
		||||
      const { Component, props, onClose } = options;
 | 
			
		||||
      this.render();
 | 
			
		||||
 | 
			
		||||
      this.Component = Component;
 | 
			
		||||
      this.onClose = onClose;
 | 
			
		||||
 | 
			
		||||
      this.update(props);
 | 
			
		||||
    },
 | 
			
		||||
    update(props) {
 | 
			
		||||
      const updatedProps = this.augmentProps(props);
 | 
			
		||||
      const element = window.React.createElement(this.Component, updatedProps);
 | 
			
		||||
      window.ReactDOM.render(element, this.el);
 | 
			
		||||
    },
 | 
			
		||||
    augmentProps(props) {
 | 
			
		||||
      return Object.assign({}, props, {
 | 
			
		||||
        close: () => {
 | 
			
		||||
          if (this.onClose) {
 | 
			
		||||
            this.onClose();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          this.remove();
 | 
			
		||||
        },
 | 
			
		||||
        i18n: window.i18n,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    remove() {
 | 
			
		||||
      window.ReactDOM.unmountComponentAtNode(this.el);
 | 
			
		||||
      Backbone.View.prototype.remove.call(this);
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
}());
 | 
			
		||||
@ -0,0 +1,158 @@
 | 
			
		||||
const webpack = require('webpack');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const typescriptSupport = require('react-docgen-typescript');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const propsParser = typescriptSupport.withCustomConfig('./tsconfig.json').parse;
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  sections: [
 | 
			
		||||
    {
 | 
			
		||||
      name: 'Conversation',
 | 
			
		||||
      description: 'Everything necessary to render a conversation',
 | 
			
		||||
      components: 'js/react/conversation/*.tsx',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'Utility',
 | 
			
		||||
      description: 'Utility components only used for testing',
 | 
			
		||||
      components: 'js/react/util/*.tsx',
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  context: {
 | 
			
		||||
    // Exposes necessary utilities in the global scope for all readme code snippets
 | 
			
		||||
    util: 'js/react/util',
 | 
			
		||||
  },
 | 
			
		||||
  // We don't want one long, single page
 | 
			
		||||
  pagePerSection: true,
 | 
			
		||||
  // Expose entire repository to the styleguidist server, primarily for stylesheets
 | 
			
		||||
  assetsDir: './',
 | 
			
		||||
  // Add top-level elements to the HTML:
 | 
			
		||||
  //   docs: https://github.com/vxna/mini-html-webpack-template
 | 
			
		||||
  //   https://react-styleguidist.js.org/docs/configuration.html#template
 | 
			
		||||
  template: {
 | 
			
		||||
    head: {
 | 
			
		||||
      links: [{
 | 
			
		||||
        rel: 'stylesheet',
 | 
			
		||||
        type: 'text/css',
 | 
			
		||||
        href: '/stylesheets/manifest.css',
 | 
			
		||||
      }],
 | 
			
		||||
    },
 | 
			
		||||
    body: {
 | 
			
		||||
      // Brings in all the necessary components to boostrap Backbone views
 | 
			
		||||
      // Mirrors the order used in background.js.
 | 
			
		||||
      scripts: [
 | 
			
		||||
        {
 | 
			
		||||
          src: 'test/legacy_bridge.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'node_modules/moment/min/moment-with-locales.min.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/components.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/reliable_trigger.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/database.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/storage.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/signal_protocol_store.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/libtextsecure.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/focus_listener.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/notifications.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/delivery_receipts.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/read_receipts.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/read_syncs.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/libphonenumber-util.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/models/messages.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/models/conversations.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/models/blockedNumbers.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/expiring_messages.js',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/chromium.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/registration.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/expire.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/conversation_controller.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/emoji_util.js',
 | 
			
		||||
        },
 | 
			
		||||
        // Select Backbone views
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/views/whisper_view.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/views/timestamp_view.js',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          src: 'js/views/message_view.js',
 | 
			
		||||
        },
 | 
			
		||||
        // Hacky way of including templates for Backbone components
 | 
			
		||||
        {
 | 
			
		||||
          src: 'test/legacy_templates.js',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  propsParser,
 | 
			
		||||
  webpackConfig: {
 | 
			
		||||
    devtool: 'source-map',
 | 
			
		||||
 | 
			
		||||
    resolve: {
 | 
			
		||||
      // Necessary to enable the absolute path used in the context option above
 | 
			
		||||
      modules: [
 | 
			
		||||
        __dirname,
 | 
			
		||||
        path.join(__dirname, 'node_modules'),
 | 
			
		||||
      ],
 | 
			
		||||
      extensions: ['.tsx'],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    module: {
 | 
			
		||||
      rules: [
 | 
			
		||||
        {
 | 
			
		||||
          test: /\.tsx?$/,
 | 
			
		||||
          loader: 'ts-loader'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          // To test handling of attachments, we need arraybuffers in memory
 | 
			
		||||
          test: /\.(gif|mp3|mp4)$/,
 | 
			
		||||
          loader: 'arraybuffer-loader',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
 | 
			
		||||
// Because we aren't hosting the styleguide in Electron, we can't rely on preload.js
 | 
			
		||||
//   to set things up for us. This gives us the minimum bar shims for everything it
 | 
			
		||||
//   provdes.
 | 
			
		||||
//
 | 
			
		||||
// Remember, the idea here is just to enable visual testing, no full functionality. Most
 | 
			
		||||
//   of thise can be very simple.
 | 
			
		||||
 | 
			
		||||
window.PROTO_ROOT = '/protos';
 | 
			
		||||
window.nodeSetImmediate = () => {};
 | 
			
		||||
 | 
			
		||||
window.Signal = {};
 | 
			
		||||
window.Signal.Backup = {};
 | 
			
		||||
window.Signal.Crypto = {};
 | 
			
		||||
window.Signal.Logs = {};
 | 
			
		||||
window.Signal.Migrations = {};
 | 
			
		||||
 | 
			
		||||
window.Signal.React = window.Signal.React = {};
 | 
			
		||||
 | 
			
		||||
window.EmojiConvertor = function EmojiConvertor() {};
 | 
			
		||||
window.EmojiConvertor.prototype.init_colons = () => {}
 | 
			
		||||
window.EmojiConvertor.prototype.signalReplace = html => html;
 | 
			
		||||
window.EmojiConvertor.prototype.replace_unified = string => string;
 | 
			
		||||
window.EmojiConvertor.prototype.img_sets = {
 | 
			
		||||
  apple: {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.i18n = () => '';
 | 
			
		||||
 | 
			
		||||
window.Signal.Migrations.V17 = {};
 | 
			
		||||
window.Signal.OS = {};
 | 
			
		||||
window.Signal.Types = {};
 | 
			
		||||
window.Signal.Types.Attachment = {};
 | 
			
		||||
window.Signal.Types.Errors = {};
 | 
			
		||||
window.Signal.Types.Message = {
 | 
			
		||||
  initializeSchemaVersion: attributes => attributes,
 | 
			
		||||
};
 | 
			
		||||
window.Signal.Types.MIME = {};
 | 
			
		||||
window.Signal.Types.Settings = {};
 | 
			
		||||
window.Signal.Views = {};
 | 
			
		||||
window.Signal.Views.Initialization = {};
 | 
			
		||||
window.Signal.Workflow = {};
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
 | 
			
		||||
// Taken from background.html.
 | 
			
		||||
// Templates are here solely to support the Backbone views rendered in the styleguide.
 | 
			
		||||
 | 
			
		||||
window.Whisper.View.Templates = {
 | 
			
		||||
  hasRetry: `
 | 
			
		||||
    {{ messageNotSent }}
 | 
			
		||||
    <span href='#' class='retry'>{{ resend }}</span>
 | 
			
		||||
  `,
 | 
			
		||||
  'some-failed': `
 | 
			
		||||
    {{ someFailed }}
 | 
			
		||||
  `,
 | 
			
		||||
  keychange: `
 | 
			
		||||
    <span class='content' dir='auto'><span class='shield icon'></span> {{ content }}</span>
 | 
			
		||||
  `,
 | 
			
		||||
  'verified-change': `
 | 
			
		||||
    <span class='content' dir='auto'><span class='{{ icon }} icon'></span> {{ content }}</span>
 | 
			
		||||
  `,
 | 
			
		||||
  message: `
 | 
			
		||||
    {{> avatar }}
 | 
			
		||||
    <div class='bubble {{ avatar.color }}'>
 | 
			
		||||
        <div class='sender' dir='auto'>
 | 
			
		||||
          {{ sender }}
 | 
			
		||||
          {{ #profileName }}
 | 
			
		||||
            <span class='profileName'>{{ profileName }} </span>
 | 
			
		||||
          {{ /profileName }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class='attachments'></div>
 | 
			
		||||
        <p class='content' dir='auto'>
 | 
			
		||||
          {{ #message }}<span class='body'>{{ message }}</span>{{ /message }}
 | 
			
		||||
        </p>
 | 
			
		||||
        <div class='meta'>
 | 
			
		||||
          <span class='timestamp' data-timestamp={{ timestamp }}></span>
 | 
			
		||||
          <span class='status hide'></span>
 | 
			
		||||
          <span class='timer'></span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  `,
 | 
			
		||||
  hourglass: `
 | 
			
		||||
    <span class='hourglass'><span class='sand'></span></span>
 | 
			
		||||
  `,
 | 
			
		||||
  expirationTimerUpdate: `
 | 
			
		||||
    <span class='content'><span class='icon clock'></span> {{ content }}</span>
 | 
			
		||||
  `
 | 
			
		||||
};
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue