@squiz/formatted-text-editor 1.21.1-alpha.3 → 1.21.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/EditorToolbar/Toolbar.js +3 -1
- package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -2
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +17 -0
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +84 -0
- package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +67 -0
- package/lib/EditorToolbar/Tools/Image/ImageModal.d.ts +8 -0
- package/lib/EditorToolbar/Tools/Image/ImageModal.js +19 -0
- package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -2
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +5 -5
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +2 -2
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -2
- package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.js +2 -2
- package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -2
- package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -2
- package/lib/Extensions/Extensions.d.ts +2 -1
- package/lib/Extensions/Extensions.js +2 -0
- package/lib/Extensions/ImageExtension/ImageExtension.d.ts +3 -0
- package/lib/Extensions/ImageExtension/ImageExtension.js +7 -0
- package/lib/index.css +128 -74
- package/lib/ui/Button/Button.d.ts +11 -0
- package/lib/ui/Button/Button.js +14 -0
- package/lib/ui/Fields/Input/Input.d.ts +4 -0
- package/lib/ui/{Inputs/Text/TextInput.js → Fields/Input/Input.js} +4 -4
- package/package.json +4 -3
- package/src/Editor/_editor.scss +2 -49
- package/src/EditorToolbar/Toolbar.tsx +2 -0
- package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +23 -0
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +92 -0
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +79 -0
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +57 -0
- package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +83 -0
- package/src/EditorToolbar/Tools/Image/ImageModal.tsx +29 -0
- package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +5 -5
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -2
- package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -2
- package/src/EditorToolbar/_floating-toolbar.scss +6 -0
- package/src/EditorToolbar/_toolbar.scss +11 -5
- package/src/Extensions/Extensions.ts +2 -0
- package/src/Extensions/ImageExtension/ImageExtension.ts +3 -0
- package/src/index.scss +2 -2
- package/src/ui/Button/Button.spec.tsx +44 -0
- package/src/ui/Button/Button.tsx +31 -0
- package/src/ui/{_buttons.scss → Button/_button.scss} +19 -1
- package/src/ui/{Inputs/Text/TextInput.spec.tsx → Fields/Input/Input.spec.tsx} +8 -8
- package/src/ui/{Inputs/Text/TextInput.tsx → Fields/Input/Input.tsx} +4 -4
- package/src/ui/_typography.scss +46 -0
- package/lib/ui/Inputs/Text/TextInput.d.ts +0 -4
- package/lib/ui/ToolbarButton/ToolbarButton.d.ts +0 -10
- package/lib/ui/ToolbarButton/ToolbarButton.js +0 -10
- package/src/ui/ToolbarButton/ToolbarButton.tsx +0 -26
- package/src/ui/ToolbarButton/_toolbar-button.scss +0 -17
- /package/lib/ui/{Inputs → Fields}/Select/Select.d.ts +0 -0
- /package/lib/ui/{Inputs → Fields}/Select/Select.js +0 -0
- /package/src/ui/{Inputs → Fields}/Select/Select.spec.tsx +0 -0
- /package/src/ui/{Inputs → Fields}/Select/Select.tsx +0 -0
package/lib/index.css
CHANGED
@@ -376,6 +376,10 @@
|
|
376
376
|
.squiz-fte-scope .z-40 {
|
377
377
|
z-index: 40 !important;
|
378
378
|
}
|
379
|
+
.squiz-fte-scope .mx-1 {
|
380
|
+
margin-left: 0.25rem !important;
|
381
|
+
margin-right: 0.25rem !important;
|
382
|
+
}
|
379
383
|
.squiz-fte-scope .mx-auto {
|
380
384
|
margin-left: auto !important;
|
381
385
|
margin-right: auto !important;
|
@@ -437,6 +441,12 @@
|
|
437
441
|
-moz-user-select: none !important;
|
438
442
|
user-select: none !important;
|
439
443
|
}
|
444
|
+
.squiz-fte-scope .flex-row {
|
445
|
+
flex-direction: row !important;
|
446
|
+
}
|
447
|
+
.squiz-fte-scope .items-end {
|
448
|
+
align-items: flex-end !important;
|
449
|
+
}
|
440
450
|
.squiz-fte-scope .items-center {
|
441
451
|
align-items: center !important;
|
442
452
|
}
|
@@ -566,6 +576,47 @@
|
|
566
576
|
--tw-blur: blur(8px) !important;
|
567
577
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important;
|
568
578
|
}
|
579
|
+
.squiz-fte-scope a {
|
580
|
+
--tw-text-opacity: 1;
|
581
|
+
color: rgb(7 116 210 / var(--tw-text-opacity));
|
582
|
+
text-decoration: underline;
|
583
|
+
}
|
584
|
+
.squiz-fte-scope h1 {
|
585
|
+
font-size: 1.625rem;
|
586
|
+
font-weight: 600;
|
587
|
+
letter-spacing: -0.2px;
|
588
|
+
line-height: 2rem;
|
589
|
+
}
|
590
|
+
.squiz-fte-scope h2 {
|
591
|
+
font-size: 1.25rem;
|
592
|
+
font-weight: 600;
|
593
|
+
letter-spacing: -0.5px;
|
594
|
+
line-height: 1.5rem;
|
595
|
+
}
|
596
|
+
.squiz-fte-scope h3 {
|
597
|
+
font-size: 1.125rem;
|
598
|
+
font-weight: 600;
|
599
|
+
letter-spacing: -0.2px;
|
600
|
+
line-height: 1.375rem;
|
601
|
+
}
|
602
|
+
.squiz-fte-scope h4 {
|
603
|
+
font-size: 1rem;
|
604
|
+
font-weight: 700;
|
605
|
+
letter-spacing: -0.2px;
|
606
|
+
line-height: 1.25rem;
|
607
|
+
}
|
608
|
+
.squiz-fte-scope h5 {
|
609
|
+
font-size: 1rem;
|
610
|
+
font-weight: 600;
|
611
|
+
letter-spacing: -0.2px;
|
612
|
+
line-height: 1.25rem;
|
613
|
+
}
|
614
|
+
.squiz-fte-scope h6 {
|
615
|
+
font-size: 0.875rem;
|
616
|
+
font-weight: 600;
|
617
|
+
letter-spacing: -0.2px;
|
618
|
+
line-height: 1.25rem;
|
619
|
+
}
|
569
620
|
.squiz-fte-scope .squiz-fte-form-group {
|
570
621
|
display: flex;
|
571
622
|
flex-direction: column;
|
@@ -611,29 +662,6 @@
|
|
611
662
|
.squiz-fte-scope .squiz-fte-form-control:active {
|
612
663
|
box-shadow: none;
|
613
664
|
}
|
614
|
-
.squiz-fte-scope .squiz-fte-btn {
|
615
|
-
border-radius: 4px;
|
616
|
-
font-weight: 400;
|
617
|
-
transition-property: all;
|
618
|
-
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
619
|
-
transition-duration: 150ms;
|
620
|
-
transition-timing-function: linear;
|
621
|
-
display: flex;
|
622
|
-
align-items: center;
|
623
|
-
text-align: center;
|
624
|
-
white-space: nowrap;
|
625
|
-
vertical-align: middle;
|
626
|
-
touch-action: manipulation;
|
627
|
-
cursor: pointer;
|
628
|
-
background-image: none;
|
629
|
-
border: 1px solid transparent;
|
630
|
-
padding: 6px 12px;
|
631
|
-
}
|
632
|
-
.squiz-fte-scope .squiz-fte-btn.disabled,
|
633
|
-
.squiz-fte-scope .squiz-fte-btn[disabled] {
|
634
|
-
cursor: not-allowed;
|
635
|
-
opacity: 0.5;
|
636
|
-
}
|
637
665
|
.squiz-fte-scope .formatted-text-editor {
|
638
666
|
font-family: "Open Sans" !important;
|
639
667
|
}
|
@@ -649,7 +677,7 @@
|
|
649
677
|
.squiz-fte-scope .formatted-text-editor .remirror-editor-wrapper {
|
650
678
|
padding-top: 0px;
|
651
679
|
--tw-text-opacity: 1;
|
652
|
-
color: rgb(
|
680
|
+
color: rgb(61 61 61 / var(--tw-text-opacity));
|
653
681
|
}
|
654
682
|
.squiz-fte-scope .formatted-text-editor .remirror-editor {
|
655
683
|
border-bottom-right-radius: 4px;
|
@@ -663,7 +691,7 @@
|
|
663
691
|
var(--tw-ring-offset-shadow, 0 0 #0000),
|
664
692
|
var(--tw-ring-shadow, 0 0 #0000),
|
665
693
|
var(--tw-shadow);
|
666
|
-
|
694
|
+
overflow: auto;
|
667
695
|
}
|
668
696
|
.squiz-fte-scope .formatted-text-editor .remirror-editor:active,
|
669
697
|
.squiz-fte-scope .formatted-text-editor .remirror-editor:focus {
|
@@ -681,47 +709,6 @@
|
|
681
709
|
--tw-text-opacity: 1;
|
682
710
|
color: rgb(148 148 148 / var(--tw-text-opacity));
|
683
711
|
}
|
684
|
-
.squiz-fte-scope a {
|
685
|
-
--tw-text-opacity: 1;
|
686
|
-
color: rgb(7 116 210 / var(--tw-text-opacity));
|
687
|
-
text-decoration: underline;
|
688
|
-
}
|
689
|
-
.squiz-fte-scope .remirror-theme h1 {
|
690
|
-
font-size: 1.625rem;
|
691
|
-
font-weight: 600;
|
692
|
-
letter-spacing: -0.2px;
|
693
|
-
line-height: 2rem;
|
694
|
-
}
|
695
|
-
.squiz-fte-scope .remirror-theme h2 {
|
696
|
-
font-size: 1.25rem;
|
697
|
-
font-weight: 600;
|
698
|
-
letter-spacing: -0.5px;
|
699
|
-
line-height: 1.5rem;
|
700
|
-
}
|
701
|
-
.squiz-fte-scope .remirror-theme h3 {
|
702
|
-
font-size: 1.125rem;
|
703
|
-
font-weight: 600;
|
704
|
-
letter-spacing: -0.2px;
|
705
|
-
line-height: 1.375rem;
|
706
|
-
}
|
707
|
-
.squiz-fte-scope .remirror-theme h4 {
|
708
|
-
font-size: 1rem;
|
709
|
-
font-weight: 700;
|
710
|
-
letter-spacing: -0.2px;
|
711
|
-
line-height: 1.25rem;
|
712
|
-
}
|
713
|
-
.squiz-fte-scope .remirror-theme h5 {
|
714
|
-
font-size: 1rem;
|
715
|
-
font-weight: 600;
|
716
|
-
letter-spacing: -0.2px;
|
717
|
-
line-height: 1.25rem;
|
718
|
-
}
|
719
|
-
.squiz-fte-scope .remirror-theme h6 {
|
720
|
-
font-size: 0.875rem;
|
721
|
-
font-weight: 600;
|
722
|
-
letter-spacing: -0.2px;
|
723
|
-
line-height: 1.25rem;
|
724
|
-
}
|
725
712
|
.squiz-fte-scope .editor-toolbar {
|
726
713
|
border-bottom-width: 2px;
|
727
714
|
border-style: solid;
|
@@ -736,7 +723,7 @@
|
|
736
723
|
.squiz-fte-scope .editor-toolbar > *:not(:first-child, .editor-divider) {
|
737
724
|
margin: 0 0 0 2px;
|
738
725
|
}
|
739
|
-
.squiz-fte-scope .editor-divider {
|
726
|
+
.squiz-fte-scope .editor-toolbar .editor-divider {
|
740
727
|
margin-top: -0.25rem;
|
741
728
|
margin-bottom: -0.25rem;
|
742
729
|
margin-left: 0.25rem;
|
@@ -745,6 +732,12 @@
|
|
745
732
|
margin-right: 2px;
|
746
733
|
height: auto;
|
747
734
|
}
|
735
|
+
.squiz-fte-scope .editor-toolbar .squiz-fte-btn {
|
736
|
+
padding: 0.25rem;
|
737
|
+
}
|
738
|
+
.squiz-fte-scope .editor-toolbar .squiz-fte-btn ~ .squiz-fte-btn {
|
739
|
+
margin-left: 2px;
|
740
|
+
}
|
748
741
|
.squiz-fte-scope__floating-popover {
|
749
742
|
display: flex;
|
750
743
|
border-radius: 6px;
|
@@ -761,22 +754,51 @@
|
|
761
754
|
var(--tw-ring-shadow, 0 0 #0000),
|
762
755
|
var(--tw-shadow);
|
763
756
|
}
|
764
|
-
.squiz-fte-scope .
|
757
|
+
.squiz-fte-scope .squiz-fte-scope__floating-popover .squiz-fte-btn {
|
758
|
+
padding: 0.25rem;
|
759
|
+
}
|
760
|
+
.squiz-fte-scope .squiz-fte-scope__floating-popover .squiz-fte-btn ~ .squiz-fte-btn {
|
761
|
+
margin-left: 2px;
|
762
|
+
}
|
763
|
+
.squiz-fte-scope .squiz-fte-btn {
|
764
|
+
border-radius: 4px;
|
765
765
|
--tw-bg-opacity: 1;
|
766
766
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
767
|
-
|
767
|
+
font-weight: 400;
|
768
768
|
--tw-text-opacity: 1;
|
769
769
|
color: rgb(112 112 112 / var(--tw-text-opacity));
|
770
|
+
transition-property: all;
|
771
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
772
|
+
transition-duration: 150ms;
|
773
|
+
transition-timing-function: linear;
|
774
|
+
display: flex;
|
775
|
+
align-items: center;
|
776
|
+
text-align: center;
|
777
|
+
white-space: nowrap;
|
778
|
+
vertical-align: middle;
|
779
|
+
touch-action: manipulation;
|
780
|
+
cursor: pointer;
|
781
|
+
background-image: none;
|
782
|
+
border: 1px solid transparent;
|
783
|
+
padding: 6px 12px;
|
770
784
|
}
|
771
|
-
.squiz-fte-scope .
|
785
|
+
.squiz-fte-scope .squiz-fte-btn--is-icon {
|
786
|
+
padding: 6px;
|
787
|
+
}
|
788
|
+
.squiz-fte-scope .squiz-fte-btn ~ .squiz-fte-btn {
|
772
789
|
margin-left: 2px;
|
773
790
|
}
|
774
|
-
.squiz-fte-scope .
|
775
|
-
.squiz-fte-scope .
|
791
|
+
.squiz-fte-scope .squiz-fte-btn.disabled,
|
792
|
+
.squiz-fte-scope .squiz-fte-btn[disabled] {
|
793
|
+
cursor: not-allowed;
|
794
|
+
opacity: 0.5;
|
795
|
+
}
|
796
|
+
.squiz-fte-scope .squiz-fte-btn:hover,
|
797
|
+
.squiz-fte-scope .squiz-fte-btn:focus {
|
776
798
|
background-color: rgba(0, 0, 0, 0.04);
|
777
799
|
}
|
778
|
-
.squiz-fte-scope .
|
779
|
-
.squiz-fte-scope .
|
800
|
+
.squiz-fte-scope .squiz-fte-btn--is-active,
|
801
|
+
.squiz-fte-scope .squiz-fte-btn:active {
|
780
802
|
--tw-bg-opacity: 1;
|
781
803
|
background-color: rgb(230 241 250 / var(--tw-bg-opacity));
|
782
804
|
--tw-text-opacity: 1;
|
@@ -899,8 +921,27 @@
|
|
899
921
|
}
|
900
922
|
.squiz-fte-scope .squiz-fte-modal-footer__button {
|
901
923
|
font-size: 0.875rem;
|
924
|
+
font-weight: 700;
|
925
|
+
}
|
926
|
+
.squiz-fte-scope .editor-toolbar .squiz-fte-modal-footer__button {
|
927
|
+
padding: 0.25rem;
|
928
|
+
}
|
929
|
+
.squiz-fte-scope .editor-toolbar .squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
930
|
+
margin-left: 2px;
|
931
|
+
}
|
932
|
+
.squiz-fte-scope .squiz-fte-scope__floating-popover .squiz-fte-modal-footer__button {
|
933
|
+
padding: 0.25rem;
|
934
|
+
}
|
935
|
+
.squiz-fte-scope .squiz-fte-scope__floating-popover .squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
936
|
+
margin-left: 2px;
|
937
|
+
}
|
938
|
+
.squiz-fte-scope .squiz-fte-modal-footer__button {
|
902
939
|
border-radius: 4px;
|
940
|
+
--tw-bg-opacity: 1;
|
941
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
903
942
|
font-weight: 400;
|
943
|
+
--tw-text-opacity: 1;
|
944
|
+
color: rgb(112 112 112 / var(--tw-text-opacity));
|
904
945
|
transition-property: all;
|
905
946
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
906
947
|
transition-duration: 150ms;
|
@@ -916,11 +957,24 @@
|
|
916
957
|
border: 1px solid transparent;
|
917
958
|
padding: 6px 12px;
|
918
959
|
}
|
960
|
+
.squiz-fte-scope .squiz-fte-modal-footer__button ~ .squiz-fte-btn {
|
961
|
+
margin-left: 2px;
|
962
|
+
}
|
919
963
|
.squiz-fte-scope .squiz-fte-modal-footer__button.disabled,
|
920
964
|
.squiz-fte-scope .squiz-fte-modal-footer__button[disabled] {
|
921
965
|
cursor: not-allowed;
|
922
966
|
opacity: 0.5;
|
923
967
|
}
|
968
|
+
.squiz-fte-scope .squiz-fte-modal-footer__button:hover,
|
969
|
+
.squiz-fte-scope .squiz-fte-modal-footer__button:focus {
|
970
|
+
background-color: rgba(0, 0, 0, 0.04);
|
971
|
+
}
|
972
|
+
.squiz-fte-scope .squiz-fte-modal-footer__button:active {
|
973
|
+
--tw-bg-opacity: 1;
|
974
|
+
background-color: rgb(230 241 250 / var(--tw-bg-opacity));
|
975
|
+
--tw-text-opacity: 1;
|
976
|
+
color: rgb(7 116 210 / var(--tw-text-opacity));
|
977
|
+
}
|
924
978
|
.squiz-fte-scope .hover\:bg-blue-400:hover {
|
925
979
|
--tw-bg-opacity: 1 !important;
|
926
980
|
background-color: rgb(4 73 133 / var(--tw-bg-opacity)) !important;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { ReactElement } from 'react';
|
2
|
+
type ButtonProps = {
|
3
|
+
handleOnClick: () => void;
|
4
|
+
isDisabled?: boolean;
|
5
|
+
isActive: boolean;
|
6
|
+
label: string;
|
7
|
+
text?: string;
|
8
|
+
icon?: ReactElement;
|
9
|
+
};
|
10
|
+
declare const Button: ({ handleOnClick, isDisabled, isActive, label, text, icon }: ButtonProps) => JSX.Element;
|
11
|
+
export default Button;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const react_1 = __importDefault(require("react"));
|
7
|
+
const clsx_1 = __importDefault(require("clsx"));
|
8
|
+
const Button = ({ handleOnClick, isDisabled, isActive, label, text, icon }) => {
|
9
|
+
return (react_1.default.createElement("button", { "aria-label": label, title: label, type: "button", onClick: handleOnClick, disabled: isDisabled, className: (0, clsx_1.default)('squiz-fte-btn', isActive && 'squiz-fte-btn--is-active', icon && ' squiz-fte-btn--is-icon') },
|
10
|
+
react_1.default.createElement(react_1.default.Fragment, null,
|
11
|
+
text && react_1.default.createElement("span", null, text),
|
12
|
+
icon && icon)));
|
13
|
+
};
|
14
|
+
exports.default = Button;
|
@@ -23,11 +23,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
23
|
return result;
|
24
24
|
};
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
-
exports.
|
26
|
+
exports.Input = void 0;
|
27
27
|
const react_1 = __importStar(require("react"));
|
28
|
-
const
|
28
|
+
const InputInternal = ({ name, label, type = 'text', ...rest }, ref) => {
|
29
29
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
30
30
|
label && (react_1.default.createElement("label", { htmlFor: name, className: "squiz-fte-form-label" }, label)),
|
31
|
-
react_1.default.createElement("input", { ref: ref, id: name, name: name, type:
|
31
|
+
react_1.default.createElement("input", { ref: ref, id: name, name: name, type: type, className: "squiz-fte-form-control", ...rest })));
|
32
32
|
};
|
33
|
-
exports.
|
33
|
+
exports.Input = (0, react_1.forwardRef)(InputInternal);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.21.1-alpha.
|
3
|
+
"version": "1.21.1-alpha.4",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -21,7 +21,8 @@
|
|
21
21
|
"@mui/icons-material": "5.11.0",
|
22
22
|
"@remirror/react": "2.0.25",
|
23
23
|
"clsx": "1.2.1",
|
24
|
-
"react-hook-form": "7.43.2"
|
24
|
+
"react-hook-form": "7.43.2",
|
25
|
+
"react-image-size": "2.0.0"
|
25
26
|
},
|
26
27
|
"devDependencies": {
|
27
28
|
"@testing-library/cypress": "9.0.0",
|
@@ -67,5 +68,5 @@
|
|
67
68
|
"volta": {
|
68
69
|
"node": "16.19.0"
|
69
70
|
},
|
70
|
-
"gitHead": "
|
71
|
+
"gitHead": "94a6842c87ee50bde3fa7dc52da7ab0c5f5ad456"
|
71
72
|
}
|
package/src/Editor/_editor.scss
CHANGED
@@ -6,12 +6,12 @@
|
|
6
6
|
}
|
7
7
|
|
8
8
|
.remirror-editor-wrapper {
|
9
|
-
@apply text-gray-
|
9
|
+
@apply text-gray-800 pt-0;
|
10
10
|
}
|
11
11
|
|
12
12
|
.remirror-editor {
|
13
13
|
@apply bg-white shadow-none rounded-b p-3;
|
14
|
-
|
14
|
+
overflow: auto;
|
15
15
|
|
16
16
|
&:active,
|
17
17
|
&:focus {
|
@@ -33,50 +33,3 @@
|
|
33
33
|
@apply text-gray-500;
|
34
34
|
}
|
35
35
|
}
|
36
|
-
|
37
|
-
a {
|
38
|
-
@apply text-blue-300;
|
39
|
-
text-decoration: underline;
|
40
|
-
}
|
41
|
-
|
42
|
-
.remirror-theme h1 {
|
43
|
-
font-size: 1.625rem;
|
44
|
-
font-weight: 600;
|
45
|
-
letter-spacing: -0.2px;
|
46
|
-
line-height: 2rem;
|
47
|
-
}
|
48
|
-
|
49
|
-
.remirror-theme h2 {
|
50
|
-
font-size: 1.25rem;
|
51
|
-
font-weight: 600;
|
52
|
-
letter-spacing: -0.5px;
|
53
|
-
line-height: 1.5rem;
|
54
|
-
}
|
55
|
-
|
56
|
-
.remirror-theme h3 {
|
57
|
-
font-size: 1.125rem;
|
58
|
-
font-weight: 600;
|
59
|
-
letter-spacing: -0.2px;
|
60
|
-
line-height: 1.375rem;
|
61
|
-
}
|
62
|
-
|
63
|
-
.remirror-theme h4 {
|
64
|
-
font-size: 1rem;
|
65
|
-
font-weight: 700;
|
66
|
-
letter-spacing: -0.2px;
|
67
|
-
line-height: 1.25rem;
|
68
|
-
}
|
69
|
-
|
70
|
-
.remirror-theme h5 {
|
71
|
-
font-size: 1rem;
|
72
|
-
font-weight: 600;
|
73
|
-
letter-spacing: -0.2px;
|
74
|
-
line-height: 1.25rem;
|
75
|
-
}
|
76
|
-
|
77
|
-
.remirror-theme h6 {
|
78
|
-
font-size: 0.875rem;
|
79
|
-
font-weight: 600;
|
80
|
-
letter-spacing: -0.2px;
|
81
|
-
line-height: 1.25rem;
|
82
|
-
}
|
@@ -9,6 +9,7 @@ import RedoButton from './Tools/Redo/RedoButton';
|
|
9
9
|
import TextTypeDropdown from './Tools/TextType/TextTypeDropdown';
|
10
10
|
import { useExtensionNames } from '../hooks';
|
11
11
|
import LinkButton from './Tools/Link/LinkButton';
|
12
|
+
import ImageButton from './Tools/Image/ImageButton';
|
12
13
|
|
13
14
|
export const Toolbar = () => {
|
14
15
|
const extensionNames = useExtensionNames();
|
@@ -28,6 +29,7 @@ export const Toolbar = () => {
|
|
28
29
|
{extensionNames.underline && <UnderlineButton />}
|
29
30
|
{extensionNames.nodeFormatting && <TextAlignButtons />}
|
30
31
|
{extensionNames.link && <LinkButton />}
|
32
|
+
{extensionNames.image && <ImageButton />}
|
31
33
|
</RemirrorToolbar>
|
32
34
|
);
|
33
35
|
};
|
@@ -14,6 +14,6 @@ describe('Bold button', () => {
|
|
14
14
|
expect(screen.getByRole('button', { name: 'Bold (cmd+B)' }).classList.contains('squiz-fte-btn')).toBeTruthy();
|
15
15
|
const bold = screen.getByRole('button', { name: 'Bold (cmd+B)' });
|
16
16
|
fireEvent.click(bold);
|
17
|
-
expect(bold.classList.contains('is-active')).toBeTruthy();
|
17
|
+
expect(bold.classList.contains('squiz-fte-btn--is-active')).toBeTruthy();
|
18
18
|
});
|
19
19
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { useCommands, useActive, useChainedCommands } from '@remirror/react';
|
3
3
|
import { BoldExtension } from '@remirror/extension-bold';
|
4
|
-
import
|
4
|
+
import Button from '../../../ui/Button/Button';
|
5
5
|
import FormatBoldRoundedIcon from '@mui/icons-material/FormatBoldRounded';
|
6
6
|
|
7
7
|
const BoldButton = () => {
|
@@ -17,7 +17,7 @@ const BoldButton = () => {
|
|
17
17
|
};
|
18
18
|
|
19
19
|
return (
|
20
|
-
<
|
20
|
+
<Button
|
21
21
|
handleOnClick={handleSelect}
|
22
22
|
isDisabled={!enabled}
|
23
23
|
isActive={active.bold()}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { render, screen } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import ImageForm from './ImageForm';
|
5
|
+
|
6
|
+
describe('Image Form', () => {
|
7
|
+
const handleSubmit = jest.fn();
|
8
|
+
const data = {
|
9
|
+
src: 'https://httpcats.com/302.jpg',
|
10
|
+
alt: 'Cat with mouse in mouth',
|
11
|
+
width: 1600,
|
12
|
+
height: 1400,
|
13
|
+
};
|
14
|
+
|
15
|
+
it('Renders the form with the relevant fields', () => {
|
16
|
+
render(<ImageForm data={data} onSubmit={handleSubmit} />);
|
17
|
+
|
18
|
+
expect(screen.getByLabelText('Source')).toHaveValue('https://httpcats.com/302.jpg');
|
19
|
+
expect(screen.getByLabelText('Alternative description')).toHaveValue('Cat with mouse in mouth');
|
20
|
+
expect(screen.getByLabelText('Width')).toHaveValue(1600);
|
21
|
+
expect(screen.getByLabelText('Height')).toHaveValue(1400);
|
22
|
+
});
|
23
|
+
});
|
@@ -0,0 +1,92 @@
|
|
1
|
+
import React, { ReactElement, useState } from 'react';
|
2
|
+
import { Input } from '../../../../ui/Fields/Input/Input';
|
3
|
+
import { SubmitHandler, useForm } from 'react-hook-form';
|
4
|
+
import { getImageSize } from 'react-image-size';
|
5
|
+
import { ImageAttributes } from '@remirror/extension-image/dist-types/image-extension';
|
6
|
+
import Button from '../../../../ui/Button/Button';
|
7
|
+
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
8
|
+
import InsertLinkRoundedIcon from '@mui/icons-material/InsertLinkRounded';
|
9
|
+
|
10
|
+
export type UpdateImageOptions = ImageAttributes & {
|
11
|
+
src: string;
|
12
|
+
alt: string;
|
13
|
+
width: number;
|
14
|
+
height: number;
|
15
|
+
};
|
16
|
+
export type ImageFormData = Pick<UpdateImageOptions, 'src' | 'alt' | 'width' | 'height'>;
|
17
|
+
|
18
|
+
export type FormProps = {
|
19
|
+
data: Partial<ImageFormData>;
|
20
|
+
onSubmit: SubmitHandler<ImageFormData>;
|
21
|
+
};
|
22
|
+
export type Dimensions = 'width' | 'height';
|
23
|
+
|
24
|
+
const ImageForm = ({ data, onSubmit }: FormProps): ReactElement => {
|
25
|
+
const { register, handleSubmit, setValue } = useForm<ImageFormData>({
|
26
|
+
defaultValues: data,
|
27
|
+
});
|
28
|
+
const [aspectRatioFromWidth, setAspectRatioFromWidth] = useState(9 / 16);
|
29
|
+
const [aspectRatioFromHeight, setAspectRatioFromHeight] = useState(16 / 9);
|
30
|
+
const [aspectRatioLocked, setAspectRatioLocked] = useState(true);
|
31
|
+
|
32
|
+
const setDimensionsFromURL = (e: { target: { value: string } }) => {
|
33
|
+
// get the new url, calculate the width and height and set those fields
|
34
|
+
getImageSize(e.target.value)
|
35
|
+
.then(({ width, height }) => {
|
36
|
+
setValue('width', width);
|
37
|
+
setValue('height', height);
|
38
|
+
setAspectRatioFromWidth(height / width);
|
39
|
+
setAspectRatioFromHeight(width / height);
|
40
|
+
})
|
41
|
+
.catch((errorMessage) => {
|
42
|
+
// TODO: we will use this when we add validation in a follow-up ticket
|
43
|
+
console.log(errorMessage);
|
44
|
+
});
|
45
|
+
};
|
46
|
+
|
47
|
+
const calculateDimensions = () => {
|
48
|
+
if (aspectRatioLocked) {
|
49
|
+
const currentTarget = event?.target as HTMLInputElement;
|
50
|
+
const type = currentTarget.name as Dimensions;
|
51
|
+
const currentValue = currentTarget.value as string;
|
52
|
+
const otherValue = type === 'width' ? 'height' : 'width';
|
53
|
+
const aspectRatio = type === 'width' ? aspectRatioFromWidth : aspectRatioFromHeight;
|
54
|
+
const newValue = Math.round(aspectRatio * Number(currentValue) * 100) / 100;
|
55
|
+
setValue(otherValue, newValue);
|
56
|
+
}
|
57
|
+
};
|
58
|
+
|
59
|
+
const toggleAspectRatio = () => {
|
60
|
+
setAspectRatioLocked(!aspectRatioLocked);
|
61
|
+
};
|
62
|
+
|
63
|
+
return (
|
64
|
+
<form className="squiz-fte-form" onSubmit={handleSubmit(onSubmit)}>
|
65
|
+
<div className="squiz-fte-form-group mb-2">
|
66
|
+
<Input label="Source" {...register('src')} onBlur={setDimensionsFromURL} />
|
67
|
+
</div>
|
68
|
+
<div className="squiz-fte-form-group mb-2">
|
69
|
+
<Input label="Alternative description" {...register('alt')} />
|
70
|
+
</div>
|
71
|
+
<div className="flex flex-row items-end">
|
72
|
+
<div className="squiz-fte-form-group mb-2">
|
73
|
+
<Input label="Width" {...register('width')} type="number" name="width" onChange={calculateDimensions} />
|
74
|
+
</div>
|
75
|
+
<div className="flex mx-1 mb-2">
|
76
|
+
<Button
|
77
|
+
handleOnClick={toggleAspectRatio}
|
78
|
+
isActive={false}
|
79
|
+
icon={aspectRatioLocked ? <InsertLinkRoundedIcon /> : <LinkOffIcon />}
|
80
|
+
label="Constrain properties"
|
81
|
+
isDisabled={false}
|
82
|
+
/>
|
83
|
+
</div>
|
84
|
+
<div className="squiz-fte-form-group mb-2">
|
85
|
+
<Input label="Height" {...register('height')} type="number" name="height" onChange={calculateDimensions} />
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
</form>
|
89
|
+
);
|
90
|
+
};
|
91
|
+
|
92
|
+
export default ImageForm;
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import '@testing-library/jest-dom';
|
2
|
+
import { screen, fireEvent, waitForElementToBeRemoved, act } from '@testing-library/react';
|
3
|
+
import React from 'react';
|
4
|
+
import { renderWithEditor } from '../../../../tests';
|
5
|
+
import ImageButton from './ImageButton';
|
6
|
+
|
7
|
+
describe('ImageButton', () => {
|
8
|
+
const openModal = async () => {
|
9
|
+
fireEvent.click(screen.getByRole('button', { name: 'Image (cmd+L)' }));
|
10
|
+
await screen.findByRole('button', { name: 'Apply' });
|
11
|
+
};
|
12
|
+
|
13
|
+
it('Opens the modal when clicking on the image button', async () => {
|
14
|
+
await renderWithEditor(<ImageButton />);
|
15
|
+
|
16
|
+
// open the modal and assert it is visible.
|
17
|
+
await openModal();
|
18
|
+
const modalHeading = screen.getByRole('heading', { name: 'Image' });
|
19
|
+
expect(modalHeading).toBeInTheDocument();
|
20
|
+
});
|
21
|
+
|
22
|
+
it('Opens the modal when clicking the keyboard shortcut', async () => {
|
23
|
+
const { editor, elements } = await renderWithEditor(<ImageButton />);
|
24
|
+
|
25
|
+
// press the keyboard shortcut.
|
26
|
+
fireEvent.keyDown(elements.editor, { key: 'l', ctrlKey: true });
|
27
|
+
|
28
|
+
// verify the modal opens
|
29
|
+
await act(() => editor.selectText(2));
|
30
|
+
expect(await screen.findByLabelText('Source')).toHaveValue('');
|
31
|
+
});
|
32
|
+
|
33
|
+
it('Adds a new image', async () => {
|
34
|
+
const { getJsonContent } = await renderWithEditor(<ImageButton />, { content: 'Some nonsense content here' });
|
35
|
+
|
36
|
+
// open the modal and add an image.
|
37
|
+
await openModal();
|
38
|
+
fireEvent.change(screen.getByLabelText('Source'), { target: { value: 'https://httpcats.com/529.jpg' } });
|
39
|
+
fireEvent.change(screen.getByLabelText('Alternative description'), { target: { value: 'Many cats' } });
|
40
|
+
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
41
|
+
|
42
|
+
await waitForElementToBeRemoved(() => screen.getByRole('button', { name: 'Apply' }));
|
43
|
+
|
44
|
+
expect(getJsonContent()).toEqual({
|
45
|
+
type: 'paragraph',
|
46
|
+
attrs: expect.any(Object),
|
47
|
+
content: [
|
48
|
+
{
|
49
|
+
type: 'image',
|
50
|
+
attrs: {
|
51
|
+
alt: 'Many cats',
|
52
|
+
crop: null,
|
53
|
+
height: '',
|
54
|
+
width: '',
|
55
|
+
rotate: null,
|
56
|
+
src: 'https://httpcats.com/529.jpg',
|
57
|
+
title: '',
|
58
|
+
fileName: null,
|
59
|
+
resizable: false,
|
60
|
+
},
|
61
|
+
},
|
62
|
+
{ type: 'text', text: 'Some nonsense content here' },
|
63
|
+
],
|
64
|
+
});
|
65
|
+
});
|
66
|
+
|
67
|
+
it('Closes the modal when clicking on the cancel button', async () => {
|
68
|
+
await renderWithEditor(<ImageButton />);
|
69
|
+
|
70
|
+
// open the modal and assert it is visible.
|
71
|
+
await openModal();
|
72
|
+
const modalHeading = screen.getByRole('heading', { name: 'Image' });
|
73
|
+
expect(modalHeading).toBeInTheDocument();
|
74
|
+
|
75
|
+
// close the modal and assert it has disappeared.
|
76
|
+
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
77
|
+
expect(modalHeading).not.toBeInTheDocument();
|
78
|
+
});
|
79
|
+
});
|