@@ -2,7 +2,7 @@ import React, { useCallback, useState } from "react"
22import Markdown from "markdown-to-jsx"
33import { EditorContent , Editor } from "@tiptap/react"
44import { StarterKit } from "@tiptap/starter-kit"
5- import { Link as LinkExtension } from "@tiptap/extension-link "
5+ import { CharacterCount } from "@tiptap/extension-character-count "
66import BoldIcon from "@heroicons/react/16/solid/BoldIcon"
77import BulletListIcon from "@heroicons/react/16/solid/ListBulletIcon"
88import OrderedListIcon from "@heroicons/react/16/solid/NumberedListIcon"
@@ -14,48 +14,49 @@ import { t } from "@bloom-housing/ui-components"
1414import styles from "./TextEditor.module.scss"
1515
1616export const EditorExtensions = [
17+ CharacterCount ,
1718 StarterKit . configure ( {
1819 heading : false ,
1920 code : false ,
2021 blockquote : false ,
2122 codeBlock : false ,
2223 italic : false ,
23- } ) ,
24- LinkExtension . configure ( {
25- openOnClick : true ,
26- autolink : true ,
27- defaultProtocol : "https" ,
28- protocols : [
29- "http" ,
30- "https" ,
31- { scheme : "mailto" , optionalSlashes : true } ,
32- { scheme : "tel" , optionalSlashes : true } ,
33- ] ,
34- isAllowedUri : ( url , ctx ) => {
35- try {
36- // construct URL
37- const parsedUrl = url . includes ( ":" )
38- ? new URL ( url )
39- : new URL ( `${ ctx . defaultProtocol } ://${ url } ` )
24+ link : {
25+ openOnClick : true ,
26+ autolink : true ,
27+ defaultProtocol : "https" ,
28+ protocols : [
29+ "http" ,
30+ "https" ,
31+ { scheme : "mailto" , optionalSlashes : true } ,
32+ { scheme : "tel" , optionalSlashes : true } ,
33+ ] ,
34+ isAllowedUri : ( url , ctx ) => {
35+ try {
36+ // construct URL
37+ const parsedUrl = url . includes ( ":" )
38+ ? new URL ( url )
39+ : new URL ( `${ ctx . defaultProtocol } ://${ url } ` )
4040
41- // use default validation
42- if ( ! ctx . defaultValidate ( parsedUrl . href ) ) {
43- return false
44- }
41+ // use default validation
42+ if ( ! ctx . defaultValidate ( parsedUrl . href ) ) {
43+ return false
44+ }
45+
46+ const protocol = parsedUrl . protocol . replace ( ":" , "" )
4547
46- const protocol = parsedUrl . protocol . replace ( ":" , "" )
48+ // only allow protocols specified in ctx.protocols
49+ const allowedProtocols = ctx . protocols . map ( ( p ) => ( typeof p === "string" ? p : p . scheme ) )
4750
48- // only allow protocols specified in ctx.protocols
49- const allowedProtocols = ctx . protocols . map ( ( p ) => ( typeof p === "string" ? p : p . scheme ) )
51+ if ( ! allowedProtocols . includes ( protocol ) ) {
52+ return false
53+ }
5054
51- if ( ! allowedProtocols . includes ( protocol ) ) {
55+ return true
56+ } catch {
5257 return false
5358 }
54-
55- return true
56- } catch {
57- return false
58- }
59+ } ,
5960 } ,
6061 } ) ,
6162]
@@ -77,15 +78,18 @@ const MenuBar = ({ editor }) => {
7778 return
7879 }
7980
81+ // Prepend https:// if the user typed a bare URL with no protocol.
82+ const href = / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : / i. test ( url ) ? url : `https://${ url } `
83+
8084 // update link
8185 try {
82- editor ?. chain ( ) . focus ( ) . extendMarkRange ( "link" ) . setLink ( { href : url } ) . run ( )
86+ editor ?. chain ( ) . focus ( ) . extendMarkRange ( "link" ) . setLink ( { href } ) . run ( )
8387 } catch ( e ) {
8488 alert ( e . message )
8589 }
8690 } , [ editor ] )
8791
88- if ( ! editor ) {
92+ if ( ! editor || editor . isDestroyed ) {
8993 return null
9094 }
9195
@@ -245,24 +249,26 @@ export const TextEditor = ({
245249 label,
246250} : TextEditorProps ) => {
247251 const [ errorState , setErrorState ] = useState ( error )
252+ const [ characterCount , setCharacterCount ] = useState (
253+ ( ) => editor ?. storage ?. characterCount ?. characters ( ) ?? 0
254+ )
248255
249256 const labelId = `${ editorId } Label`
250257
251- if ( ! editor ) {
258+ if ( ! editor || editor . isDestroyed ) {
252259 return null
253260 }
254261
255262 editor . on ( "update" , ( ) => {
256263 if ( errorState ) setErrorState ( false )
264+ setCharacterCount ( editor ?. storage ?. characterCount ?. characters ( ) ?? 0 )
257265 } )
258266
259267 editor . on ( "create" , ( ) => {
260268 editor ?. view . setProps ( {
261269 attributes : { "aria-labelledby" : labelId , role : "textbox" } ,
262270 } )
263271 } )
264-
265- const characterCount = editor ?. storage ?. characterCount ?. characters ( )
266272 const overLimit = characterCount > characterLimit
267273
268274 return (
0 commit comments