Home Reference Source

application/components/common/rich-editor2/plugins/anchor.js

  1. import React from "react";
  2. import Dialog from "material-ui/Dialog";
  3. import TextField from "material-ui/TextField";
  4. import CheckBox from "material-ui/Checkbox";
  5. import FlatButton from "material-ui/FlatButton";
  6. import RaisedButton from "material-ui/RaisedButton";
  7. import ToolbarButton from "../common/toolbar-button";
  8.  
  9. export const SERIALIZER_RULES = [
  10. {
  11. deserialize(el, next) {
  12. if (el.tagName.toLowerCase() == "a") {
  13. const href = el.getAttribute("href");
  14. const target = el.getAttribute("target")
  15. ? el.getAttribute("target")
  16. : "";
  17.  
  18. return {
  19. object: "inline",
  20. type: "anchor",
  21. nodes: next(el.childNodes),
  22. data: {
  23. href: href,
  24. target: target,
  25. },
  26. };
  27. }
  28. },
  29. serialize(obj, children) {
  30. if (obj.object == "inline" && obj.type == "anchor") {
  31. return (
  32. <a
  33. href={obj.data.get("href")}
  34. target={obj.data.get("target")}
  35. >
  36. {children}
  37. </a>
  38. );
  39. }
  40. },
  41. },
  42. ];
  43.  
  44. class AnchorButton extends ToolbarButton {
  45. handleOnClick(e) {
  46. return anchorClickHandler.call(this.props.editor, e);
  47. }
  48.  
  49. getLabel() {
  50. return <i className="fa fa-link"></i>;
  51. }
  52. }
  53.  
  54. class AnchorNode extends React.Component {
  55. constructor(props) {
  56. super(props);
  57.  
  58. this.parentEditor = this.props.editor;
  59. }
  60.  
  61. render() {
  62. const { data } = this.props.node;
  63.  
  64. const handleClick = (e) => {
  65. e.preventDefault();
  66.  
  67. // open the modal and store the anchor data
  68. this.props.editor.setState({
  69. myAnchorModalOpen: true,
  70. myAnchorData: data,
  71. });
  72. };
  73. return (
  74. <a
  75. {...this.props.attributes}
  76. href={data.get("href")}
  77. target={data.get("target")}
  78. onClick={handleClick}
  79. >
  80. {this.props.children}
  81. </a>
  82. );
  83. }
  84. }
  85.  
  86. function anchorClickHandler(e, defaultUrl = "") {
  87. const { editor } = this;
  88. const { editorContent } = this.state;
  89. const hasLinks = this.hasInline("anchor");
  90.  
  91. if (hasLinks) {
  92. // user highlighted a link and clicked the button, so remove the link
  93. editor.unwrapInline("anchor");
  94. } else if (editorContent.selection.isExpanded) {
  95. // open the modal
  96. editor.setState({ myAnchorModalOpen: true });
  97. return;
  98. }
  99. }
  100.  
  101. class AnchorModal extends React.Component {
  102. constructor(props) {
  103. super(props);
  104.  
  105. this.state = {
  106. url: "",
  107. newWindow: false,
  108. };
  109. }
  110.  
  111. closeModal() {
  112. this.props.editor.setState({
  113. myAnchorModalOpen: false,
  114. myAnchorData: null,
  115. });
  116. this.setState({
  117. url: "",
  118. newWindow: false,
  119. });
  120. }
  121.  
  122. handleCancel(e) {
  123. this.closeModal.call(this);
  124. }
  125.  
  126. componentWillReceiveProps(newProps) {
  127. if (newProps.editor && newProps.editor.state.myAnchorData) {
  128. this.setState({
  129. url: newProps.editor.state.myAnchorData.get("href"),
  130. newWindow:
  131. newProps.editor.state.myAnchorData.get("target") == "_blank"
  132. ? true
  133. : false,
  134. });
  135. }
  136. }
  137.  
  138. handleSave(e) {
  139. let useUrl = this.state.url;
  140. if (useUrl.substring(useUrl.length - 1, useUrl.length) === "\\") {
  141. useUrl = useUrl.substring(0, useUrl.length - 1);
  142. }
  143.  
  144. // remove any existing links, expand the selection then replace the URL
  145. // if it's set
  146. this.props.editor
  147. .focus()
  148. .moveAnchorToStartOfInline()
  149. .moveFocusToEndOfInline()
  150. .unwrapInline("anchor")
  151. .wrapInline({
  152. type: "anchor",
  153. data: {
  154. href: useUrl,
  155. target: this.state.newWindow ? "_blank" : "",
  156. },
  157. });
  158. this.props.editor.moveToEnd();
  159.  
  160. setTimeout(() => {
  161. this.closeModal.call(this);
  162. }, 250);
  163. }
  164.  
  165. updateUrl(e) {
  166. this.setState({
  167. url: e.target.value,
  168. });
  169. }
  170.  
  171. updateTarget(e, checked) {
  172. this.setState({
  173. newWindow: checked,
  174. });
  175. }
  176.  
  177. handleKeyPress(e) {
  178. if (e.key == "Enter") {
  179. this.handleSave.call(this, e);
  180. }
  181. }
  182.  
  183. render() {
  184. const buttons = [
  185. <FlatButton
  186. label="Cancel"
  187. onClick={this.handleCancel.bind(this)}
  188. />,
  189. <RaisedButton
  190. label="Save"
  191. onClick={this.handleSave.bind(this)}
  192. primary={true}
  193. />,
  194. ];
  195.  
  196. return (
  197. <Dialog
  198. modal={true}
  199. actions={buttons}
  200. title="Edit Link"
  201. open={this.props.editor.state.myAnchorModalOpen ? true : false}
  202. >
  203. <TextField
  204. floatingLabelText="URL"
  205. value={this.state.url}
  206. onChange={this.updateUrl.bind(this)}
  207. onKeyPress={this.handleKeyPress.bind(this)}
  208. fullWidth={true}
  209. />
  210. <CheckBox
  211. label="Open in new window"
  212. labelPosition="right"
  213. checked={this.state.newWindow}
  214. onCheck={this.updateTarget.bind(this)}
  215. />
  216. </Dialog>
  217. );
  218. }
  219. }
  220.  
  221. function AnchorPlugin(options) {
  222. return {
  223. renderEditor(props, editor, next) {
  224. const children = next();
  225. return (
  226. <React.Fragment>
  227. {children}
  228. <AnchorModal editor={editor} />
  229. </React.Fragment>
  230. );
  231. },
  232. renderNode(props, editor, next) {
  233. const { attributes, children } = props;
  234.  
  235. switch (props.node.type) {
  236. case "anchor":
  237. return <AnchorNode {...props} editor={editor} />;
  238. default:
  239. return next();
  240. }
  241. },
  242. };
  243. }
  244. export { AnchorPlugin, AnchorButton, anchorClickHandler };