index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import React from "react"
  2. import Code from "./actions/code"
  3. import Emphasis from "./actions/emphasis"
  4. import Hr from "./actions/hr"
  5. import Image from "./actions/image"
  6. import Link from "./actions/link"
  7. import Striketrough from "./actions/striketrough"
  8. import Strong from "./actions/strong"
  9. import Quote from "./actions/quote"
  10. import AttachmentsEditor from "./attachments"
  11. import Upload from "./attachments/upload-button"
  12. import MarkupPreview from "./markup-preview"
  13. import * as textUtils from "./textutils"
  14. import Button from "misago/components/button"
  15. import misago from "misago"
  16. import ajax from "misago/services/ajax"
  17. import modal from "misago/services/modal"
  18. import snackbar from "misago/services/snackbar"
  19. export default class extends React.Component {
  20. constructor(props) {
  21. super(props)
  22. this.state = {
  23. isPreviewLoading: false
  24. }
  25. }
  26. componentDidMount() {
  27. $("#editor-textarea").atwho({
  28. at: "@",
  29. displayTpl: '<li><img src="${avatar}" alt="">${username}</li>',
  30. insertTpl: "@${username}",
  31. searchKey: "username",
  32. callbacks: {
  33. remoteFilter: function(query, callback) {
  34. $.getJSON(misago.get("MENTION_API"), { q: query }, callback)
  35. }
  36. }
  37. })
  38. $("#editor-textarea").on("inserted.atwho", (event, flag, query) => {
  39. this.props.onChange(event)
  40. })
  41. }
  42. onPreviewClick = () => {
  43. if (this.state.isPreviewLoading) {
  44. return
  45. }
  46. this.setState({
  47. isPreviewLoading: true
  48. })
  49. ajax.post(misago.get("PARSE_MARKUP_API"), { post: this.props.value }).then(
  50. data => {
  51. modal.show(<MarkupPreview markup={data.parsed} />)
  52. this.setState({
  53. isPreviewLoading: false
  54. })
  55. },
  56. rejection => {
  57. if (rejection.status === 400) {
  58. snackbar.error(rejection.detail)
  59. } else {
  60. snackbar.apiError(rejection)
  61. }
  62. this.setState({
  63. isPreviewLoading: false
  64. })
  65. }
  66. )
  67. }
  68. replaceSelection = operation => {
  69. operation(textUtils.getSelectionText(), this._replaceSelection)
  70. }
  71. _replaceSelection = newValue => {
  72. this.props.onChange({
  73. target: {
  74. value: textUtils.replace(newValue)
  75. }
  76. })
  77. }
  78. render() {
  79. return (
  80. <div className="editor-border">
  81. <textarea
  82. className="form-control"
  83. value={this.props.value}
  84. disabled={this.props.loading}
  85. id="editor-textarea"
  86. onChange={this.props.onChange}
  87. rows="9"
  88. />
  89. <div className="editor-footer">
  90. <div className="buttons-list pull-left">
  91. <Strong
  92. className="btn-default btn-sm pull-left"
  93. disabled={this.props.loading || this.state.isPreviewLoading}
  94. replaceSelection={this.replaceSelection}
  95. />
  96. <Emphasis
  97. className="btn-default btn-sm pull-left"
  98. disabled={this.props.loading || this.state.isPreviewLoading}
  99. replaceSelection={this.replaceSelection}
  100. />
  101. <Striketrough
  102. className="btn-default btn-sm pull-left"
  103. disabled={this.props.loading || this.state.isPreviewLoading}
  104. replaceSelection={this.replaceSelection}
  105. />
  106. <Hr
  107. className="btn-default btn-sm pull-left"
  108. disabled={this.props.loading || this.state.isPreviewLoading}
  109. replaceSelection={this.replaceSelection}
  110. />
  111. <Link
  112. className="btn-default btn-sm pull-left"
  113. disabled={this.props.loading || this.state.isPreviewLoading}
  114. replaceSelection={this.replaceSelection}
  115. />
  116. <Image
  117. className="btn-default btn-sm pull-left"
  118. disabled={this.props.loading || this.state.isPreviewLoading}
  119. replaceSelection={this.replaceSelection}
  120. />
  121. <Quote
  122. className="btn-default btn-sm pull-left"
  123. disabled={this.props.loading || this.state.isPreviewLoading}
  124. replaceSelection={this.replaceSelection}
  125. />
  126. <Code
  127. className="btn-default btn-sm pull-left"
  128. disabled={this.props.loading || this.state.isPreviewLoading}
  129. replaceSelection={this.replaceSelection}
  130. />
  131. <Upload
  132. className="btn-default btn-sm pull-left"
  133. disabled={this.props.loading || this.state.isPreviewLoading}
  134. />
  135. </div>
  136. <Button
  137. className="btn-default btn-sm pull-left"
  138. disabled={this.props.loading || this.state.isPreviewLoading}
  139. onClick={this.onPreviewClick}
  140. type="button"
  141. >
  142. {gettext("Preview")}
  143. </Button>
  144. <Button
  145. className="btn-primary btn-sm pull-right"
  146. loading={this.props.loading}
  147. >
  148. {this.props.submitLabel || gettext("Post")}
  149. </Button>
  150. <button
  151. className="btn btn-default btn-sm pull-right"
  152. disabled={this.props.loading}
  153. onClick={this.props.onCancel}
  154. type="button"
  155. >
  156. {gettext("Cancel")}
  157. </button>
  158. <div className="clearfix visible-xs-block" />
  159. <Protect
  160. canProtect={this.props.canProtect}
  161. disabled={this.props.loading}
  162. onProtect={this.props.onProtect}
  163. onUnprotect={this.props.onUnprotect}
  164. protect={this.props.protect}
  165. />
  166. </div>
  167. <AttachmentsEditor
  168. attachments={this.props.attachments}
  169. onAttachmentsChange={this.props.onAttachmentsChange}
  170. placeholder={this.props.placeholder}
  171. replaceSelection={this.replaceSelection}
  172. />
  173. </div>
  174. )
  175. }
  176. }
  177. export function Protect(props) {
  178. if (!props.canProtect) return null
  179. const label = props.protect ? gettext("Protected") : gettext("Protect")
  180. return (
  181. <button
  182. className="btn btn-icon btn-default btn-protect btn-sm pull-right"
  183. disabled={props.disabled}
  184. onClick={props.protect ? props.onUnprotect : props.onProtect}
  185. title={label}
  186. type="button"
  187. >
  188. <span className="material-icon">
  189. {props.protect ? "lock" : "lock_outline"}
  190. </span>
  191. <span className="btn-text hidden-md hidden-lg">{label}</span>
  192. </button>
  193. )
  194. }