react-trix
Version:
React wrapper around Basecamp's Trix editor.
1 lines • 9.59 kB
Source Map (JSON)
{"mappings":";AAGA;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;IACE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;CACvB;AAED;IACE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAE5B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED;IACE,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;CACvB;AAED;IACE,gBAAgB,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACjD,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,0BAA0B,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,KAAK,IAAI,CAAC;IACxE,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;IACE,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uBAAwB,SAAQ,KAAK,CAAC,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;gBAKnE,KAAK,EAAE,eAAe;IAmBlC,iBAAiB;IA0BjB,oBAAoB;IA2HpB,MAAM;CAyCP","sources":["src/src/react-trix.tsx","src/react-trix.tsx"],"sourcesContent":[null,"import * as React from \"react\";\nimport { BoxSizingProperty } from \"csstype\";\n\nexport interface MergeTag {\n tag: string;\n name: string;\n}\n\nexport interface MergeTags {\n trigger: string;\n tags: Array<MergeTag>;\n}\n\nexport interface TrixEditorProps {\n className?: string;\n autoFocus?: boolean;\n placeholder?: string;\n toolbar?: string;\n value?: string;\n uploadURL?: string;\n uploadData?: { [key: string]: string };\n fileParamName?: string;\n\n /* list of available merge tag */\n mergeTags: Array<MergeTags>;\n\n onEditorReady?: (editor: any) => void;\n onChange: (html: string, text: string) => void;\n}\n\nexport interface TrixEditorState {\n showMergeTags: boolean;\n tags: Array<MergeTag>;\n}\n\nexport interface Editor {\n getSelectedRange: () => Array<number>;\n setSelectedRange: (range: Array<number>) => void;\n getClientRectAtPosition: (pos: number) => Rect;\n expandSelectionInDirection: (direction: \"forward\" | \"backward\") => void;\n insertString: (s: string) => void;\n}\n\nexport interface Rect {\n top: number;\n left: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n}\n\nexport class TrixEditor extends React.Component<TrixEditorProps, TrixEditorState> {\n private id: string;\n private container: any = null;\n private editor: Editor = null;\n private d: HTMLDivElement = null;\n constructor(props: TrixEditorProps) {\n super(props);\n\n this.id = this.generateId();\n\n this.state = {\n showMergeTags: false,\n tags: []\n }\n }\n private generateId(): string {\n let dt = new Date().getTime();\n let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n let r = (dt + Math.random()*16)%16 | 0;\n dt = Math.floor(dt/16);\n return (c=='x' ? r :(r&0x3|0x8)).toString(16);\n });\n return \"T\" + uuid;\n }\n componentDidMount() {\n let props = this.props;\n\n this.container = document.getElementById(`editor-${this.id}`);\n //this.container = this.d && this.d.children && this.d.children.length >= 2 ? this.d.children[1] : null;\n //this.editor = this.d;\n if (this.container) {\n this.container.addEventListener(\"trix-initialize\", () => {\n this.editor = this.container.editor;\n if (!this.editor) {\n console.error(\"cannot find trix editor\");\n }\n\n if (props.onEditorReady && typeof props.onEditorReady == \"function\") {\n props.onEditorReady(this.editor);\n }\n }, false);\n this.container.addEventListener('trix-change', this.handleChange.bind(this), false);\n\n if (props.uploadURL) {\n this.container.addEventListener(\"trix-attachment-add\", this.handleUpload.bind(this));\n }\n } else {\n console.error(\"editor not found\");\n }\n }\n componentWillUnmount() {\n this.container.removeEventListener(\"trix-initialize\", this.handleChange);\n this.container.removeEventListener(\"trix-change\", this.handleChange);\n\n if (this.props.uploadURL) {\n this.container.removeEventListener(\"trix-attachment-add\", this.handleUpload);\n }\n }\n private handleChange(e) {\n const props = this.props;\n let state: TrixEditorState = this.state;\n const text: string = e.target.innerText;\n\n if (props.onChange) {\n props.onChange(e.target.innerHTML, text);\n }\n\n const range = this.editor.getSelectedRange();\n\n // if we have a collapse selection\n if (text && range[0] == range[1]) {\n // if we have a merge tag mergeTagTrigger\n if (props.mergeTags) {\n // get the cursor position and compare the last character with our mergeTagTrigger\n const lastChar = range[0] - 1;\n if (lastChar >= 0 && lastChar < text.length) {\n const trigger = text[lastChar];\n for (let i = 0; i < props.mergeTags.length; i++) {\n if (trigger == props.mergeTags[i].trigger) {\n state.showMergeTags = true;\n state.tags = props.mergeTags[i].tags;\n this.setState(state);\n break;\n }\n }\n }\n }\n }\n }\n private handleUpload(e: any) {\n var attachment = e.attachment;\n if (attachment.file) {\n return this.uploadAttachment(attachment);\n }\n }\n private uploadAttachment(attachment: any) {\n var file, form, xhr;\n file = attachment.file;\n form = new FormData();\n // add any custom data that were passed\n if (this.props.uploadData) {\n for (var k in this.props.uploadData) {\n form.append(k, this.props.uploadData[k]);\n }\n }\n \n //form.append(\"Content-Type\", \"multipart/form-data\");\n form.append((this.props.fileParamName || \"file\"), file);\n xhr = new XMLHttpRequest();\n xhr.open(\"POST\", this.props.uploadURL, true);\n xhr.upload.onprogress = (event) => {\n var progress = event.loaded / event.total * 100;\n return attachment.setUploadProgress(progress);\n };\n xhr.onload = () => {\n var href, url;\n if (xhr.status >= 200 && xhr.status < 300) {\n url = href = xhr.responseText;\n return attachment.setAttributes({\n url: url,\n href: href\n });\n }\n };\n return xhr.send(form);\n }\n private handleTagSelected(t: MergeTag, e: React.MouseEvent<HTMLAnchorElement>): void {\n e.preventDefault();\n\n let state: TrixEditorState = this.state;\n state.showMergeTags = false;\n this.setState(state);\n\n this.editor.expandSelectionInDirection(\"backward\");\n this.editor.insertString(t.tag);\n }\n private renderTagSelector(tags: Array<MergeTag>): React.ReactNode {\n if (!tags || !this.editor) {\n return null;\n }\n\n const editorPosition = document.getElementById(\"trix-editor-top-level-\" + this.id).getBoundingClientRect();\n\n // current cursor position\n const rect = this.editor.getClientRectAtPosition(this.editor.getSelectedRange()[0]);\n const boxStyle = {\n \"position\": \"absolute\" as \"absolute\",\n \"top\": rect.top + 25 - editorPosition.top,\n \"left\": rect.left + 25 - editorPosition.left,\n \"width\": \"250px\",\n \"boxSizing\": \"border-box\" as BoxSizingProperty,\n \"padding\": 0,\n \"margin\": \".2em 0 0\",\n \"backgroundColor\": \"hsla(0,0%,100%,.9)\",\n \"borderRadius\": \".3em\",\n \"background\": \"linear-gradient(to bottom right, white, hsla(0,0%,100%,.8))\",\n \"border\": \"1px solid rgba(0,0,0,.3)\",\n \"boxShadow\": \".05em .2em .6em rgba(0,0,0,.2)\",\n\t \"textShadow\": \"none\"\n };\n const tagStyle = {\n \"display\": \"block\",\n \"padding\": \".2em .5em\",\n \"cursor\": \"pointer\"\n }\n return (\n <div style={boxStyle} className=\"react-trix-suggestions\">\n {tags.map((t) => {\n return <a key={t.name} style={tagStyle} href=\"#\" onClick={this.handleTagSelected.bind(this, t)}>{t.name}</a>\n })}\n </div>\n );\n }\n render() {\n let state: TrixEditorState = this.state;\n let props = this.props;\n\n var attributes: { [key: string]: string } = {\n \"id\": `editor-${this.id}`,\n \"input\": `input-${this.id}`\n };\n\n if (props.className) {\n attributes[\"class\"] = props.className;\n }\n\n if (props.autoFocus) {\n attributes[\"autofocus\"] = props.autoFocus.toString();\n }\n\n if (props.placeholder) {\n attributes[\"placeholder\"] = props.placeholder;\n }\n\n if (props.toolbar) {\n attributes[\"toolbar\"] = props.toolbar;\n }\n\n let mergetags: React.ReactNode = null;\n if (state.showMergeTags) {\n mergetags = this.renderTagSelector(state.tags);\n }\n return (\n <div id={\"trix-editor-top-level-\" + this.id} ref={(d) => this.d = d} style={{ \"position\": \"relative\" }}>\n {React.createElement(\"trix-editor\", attributes)}\n <input\n type=\"hidden\"\n id={`input-${this.id}`}\n value={this.props.value}\n />\n {mergetags}\n </div>\n );\n }\n}\n"],"names":[],"version":3,"file":"react-trix.d.ts.map"}