@gcforms/editor 0.0.1

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.
@@ -0,0 +1,69 @@
1
+ const basicTheme = {
2
+ ltr: "ltr",
3
+ rtl: "rtl",
4
+ placeholder: "editor-placeholder",
5
+ paragraph: "editor-paragraph",
6
+ quote: "editor-quote",
7
+ heading: {
8
+ h1: "editor-heading-h1",
9
+ h2: "editor-heading-h2",
10
+ h3: "editor-heading-h3",
11
+ h4: "editor-heading-h4",
12
+ h5: "editor-heading-h5"
13
+ },
14
+ list: {
15
+ nested: {
16
+ listitem: "editor-nested-listitem"
17
+ },
18
+ ol: "editor-list-ol",
19
+ ul: "editor-list-ul",
20
+ listitem: "editor-listitem"
21
+ },
22
+ image: "editor-image",
23
+ link: "editor-link",
24
+ text: {
25
+ bold: "editor-text-bold",
26
+ italic: "editor-text-italic",
27
+ overflowed: "editor-text-overflowed",
28
+ hashtag: "editor-text-hashtag",
29
+ underline: "editor-text-underline",
30
+ strikethrough: "editor-text-strikethrough",
31
+ underlineStrikethrough: "editor-text-underlineStrikethrough",
32
+ code: "editor-text-code"
33
+ },
34
+ code: "editor-code",
35
+ codeHighlight: {
36
+ atrule: "editor-tokenAttr",
37
+ attr: "editor-tokenAttr",
38
+ boolean: "editor-tokenProperty",
39
+ builtin: "editor-tokenSelector",
40
+ cdata: "editor-tokenComment",
41
+ char: "editor-tokenSelector",
42
+ class: "editor-tokenFunction",
43
+ "class-name": "editor-tokenFunction",
44
+ comment: "editor-tokenComment",
45
+ constant: "editor-tokenProperty",
46
+ deleted: "editor-tokenProperty",
47
+ doctype: "editor-tokenComment",
48
+ entity: "editor-tokenOperator",
49
+ function: "editor-tokenFunction",
50
+ important: "editor-tokenVariable",
51
+ inserted: "editor-tokenSelector",
52
+ keyword: "editor-tokenAttr",
53
+ namespace: "editor-tokenVariable",
54
+ number: "editor-tokenProperty",
55
+ operator: "editor-tokenOperator",
56
+ prolog: "editor-tokenComment",
57
+ property: "editor-tokenProperty",
58
+ punctuation: "editor-tokenPunctuation",
59
+ regex: "editor-tokenVariable",
60
+ selector: "editor-tokenSelector",
61
+ string: "editor-tokenSelector",
62
+ symbol: "editor-tokenProperty",
63
+ tag: "editor-tokenProperty",
64
+ url: "editor-tokenOperator",
65
+ variable: "editor-tokenVariable"
66
+ }
67
+ };
68
+
69
+ export default basicTheme;
@@ -0,0 +1,44 @@
1
+ .gc-contenteditable {
2
+ padding: 20px;
3
+
4
+ &:focus {
5
+ outline: 2px #303fc3 solid;
6
+ }
7
+
8
+ p:first-child {
9
+ margin-top: 0;
10
+ }
11
+
12
+ p {
13
+ margin: 40px 0;
14
+ }
15
+
16
+ .editor-nested-listitem {
17
+ list-style-type: none;
18
+ }
19
+
20
+ .editor-list-ol {
21
+ padding-left: 20px;
22
+ }
23
+ }
24
+
25
+ .gc-contenteditable-placeholder {
26
+ font-size: 15px;
27
+ color: #999;
28
+ overflow: hidden;
29
+ position: absolute;
30
+ text-overflow: ellipsis;
31
+ top: 8px;
32
+ left: 46px;
33
+ right: 28px;
34
+ user-select: none;
35
+ white-space: nowrap;
36
+ display: inline-block;
37
+ pointer-events: none;
38
+ }
39
+ @media (max-width: 1025px) {
40
+ .gc-contenteditable-placeholder {
41
+ left: 8px;
42
+ right: 8px;
43
+ }
44
+ }
@@ -0,0 +1,39 @@
1
+ import type { JSX } from "react";
2
+
3
+ import "./ContentEditable.css";
4
+
5
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
6
+ import * as React from "react";
7
+
8
+ type Props = {
9
+ className?: string;
10
+ placeholderClassName?: string;
11
+ placeholder: string;
12
+ id: string;
13
+ ariaLabel?: string;
14
+ ariaDescribedBy?: string;
15
+ };
16
+
17
+ export default function LexicalContentEditable({
18
+ className,
19
+ placeholder,
20
+ placeholderClassName,
21
+ id,
22
+ ariaLabel,
23
+ ariaDescribedBy,
24
+ }: Props): JSX.Element {
25
+ return (
26
+ <ContentEditable
27
+ className={className ?? "gc-contenteditable"}
28
+ aria-placeholder={placeholder}
29
+ id={id}
30
+ aria-label={ariaLabel}
31
+ aria-describedby={ariaDescribedBy}
32
+ placeholder={
33
+ <div className={placeholderClassName ?? "gc-contenteditable-placeholder"}>
34
+ {placeholder}
35
+ </div>
36
+ }
37
+ />
38
+ );
39
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import { $isAtNodeEnd } from "@lexical/selection";
9
+ import { ElementNode, RangeSelection, TextNode } from "lexical";
10
+
11
+ export function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
12
+ const anchor = selection.anchor;
13
+ const focus = selection.focus;
14
+ const anchorNode = selection.anchor.getNode();
15
+ const focusNode = selection.focus.getNode();
16
+ if (anchorNode === focusNode) {
17
+ return anchorNode;
18
+ }
19
+ const isBackward = selection.isBackward();
20
+ if (isBackward) {
21
+ return $isAtNodeEnd(focus) ? anchorNode : focusNode;
22
+ } else {
23
+ return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
24
+ }
25
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ const VERTICAL_GAP = 10;
9
+ const HORIZONTAL_OFFSET = 5;
10
+
11
+ export function setFloatingElemPositionForLinkEditor(
12
+ targetRect: DOMRect | null,
13
+ floatingElem: HTMLElement,
14
+ anchorElem: HTMLElement,
15
+ verticalGap: number = VERTICAL_GAP,
16
+ horizontalOffset: number = HORIZONTAL_OFFSET
17
+ ): void {
18
+ const scrollerElem = anchorElem.parentElement;
19
+
20
+ if (targetRect === null || !scrollerElem) {
21
+ floatingElem.style.opacity = "0";
22
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
23
+ return;
24
+ }
25
+
26
+ const floatingElemRect = floatingElem.getBoundingClientRect();
27
+ const anchorElementRect = anchorElem.getBoundingClientRect();
28
+ const editorScrollerRect = scrollerElem.getBoundingClientRect();
29
+
30
+ let top = targetRect.top - verticalGap;
31
+ let left = targetRect.left - horizontalOffset;
32
+
33
+ if (top < editorScrollerRect.top) {
34
+ top += floatingElemRect.height + targetRect.height + verticalGap * 2;
35
+ }
36
+
37
+ if (left + floatingElemRect.width > editorScrollerRect.right) {
38
+ left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
39
+ }
40
+
41
+ top -= anchorElementRect.top;
42
+ left -= anchorElementRect.left;
43
+
44
+ floatingElem.style.opacity = "1";
45
+ floatingElem.style.transform = `translate(${left}px, ${top}px)`;
46
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export function sanitizeUrl(url: string): string {
10
+ /** A pattern that matches safe URLs. */
11
+ const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
12
+
13
+ /** A pattern that matches safe data URLs. */
14
+ const DATA_URL_PATTERN =
15
+ /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
16
+
17
+ url = String(url).trim();
18
+
19
+ if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url;
20
+
21
+ return "https://";
22
+ }
23
+
24
+ // Source: https://stackoverflow.com/a/8234912/2013580
25
+ const urlRegExp = new RegExp(
26
+ /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/
27
+ );
28
+ export function validateUrl(url: string): boolean {
29
+ // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.
30
+ // Maybe show a dialog where they user can type the URL before inserting it.
31
+ return url === "https://" || urlRegExp.test(url);
32
+ }