@estiva-app/ui 0.72.0-widgets2.2 → 0.72.0
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/dist/CommentHighlights.d.ts +100 -0
- package/dist/CommentHighlights.d.ts.map +1 -0
- package/dist/ComposeBox.d.ts +55 -0
- package/dist/ComposeBox.d.ts.map +1 -0
- package/dist/ComposerTriggers.d.ts.map +1 -1
- package/dist/SelectionToolbar.d.ts +17 -1
- package/dist/SelectionToolbar.d.ts.map +1 -1
- package/dist/editor.d.ts +3 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +618 -217
- package/dist/editor.js.map +4 -4
- package/dist/index.js.map +1 -1
- package/dist/looks.d.ts +6 -0
- package/dist/looks.d.ts.map +1 -1
- package/package.json +1 -5
- package/registry.json +309 -3
- package/src/CommentHighlights.mdx +61 -0
- package/src/CommentHighlights.stories.tsx +72 -0
- package/src/CommentHighlights.test.ts +100 -0
- package/src/CommentHighlights.ts +413 -0
- package/src/ComposeBox.mdx +58 -0
- package/src/ComposeBox.stories.tsx +81 -0
- package/src/ComposeBox.tsx +183 -0
- package/src/ComposerTriggers.tsx +9 -3
- package/src/SelectionToolbar.actions.test.tsx +44 -0
- package/src/SelectionToolbar.mdx +1 -0
- package/src/SelectionToolbar.tsx +36 -2
- package/src/editor.ts +15 -1
- package/src/looks.ts +8 -0
- package/src/registry/app.test.ts +7 -1
- package/src/registry/registry.test.ts +6 -6
- package/stories/Choosing.mdx +2 -0
- package/dist/widgets-2/fixtures.d.ts +0 -102
- package/dist/widgets-2/fixtures.d.ts.map +0 -1
- package/dist/widgets-2/places.d.ts +0 -105
- package/dist/widgets-2/places.d.ts.map +0 -1
- package/dist/widgets-2/proposed.d.ts +0 -130
- package/dist/widgets-2/proposed.d.ts.map +0 -1
- package/dist/widgets-2.d.ts +0 -12
- package/dist/widgets-2.d.ts.map +0 -1
- package/dist/widgets-2.js +0 -585
- package/dist/widgets-2.js.map +0 -7
- package/src/widgets-2/fixtures.ts +0 -161
- package/src/widgets-2/places.tsx +0 -316
- package/src/widgets-2/proposed.tsx +0 -596
- package/src/widgets-2.ts +0 -11
- package/stories/widgets-2/ReadMe.mdx +0 -60
- package/stories/widgets-2/Widgets.stories.tsx +0 -504
- package/stories/widgets-2/today.tsx +0 -352
package/dist/looks.d.ts
CHANGED
|
@@ -51,4 +51,10 @@ export declare const MENU_TRIGGER_CLASSES = "cursor-pointer rounded-md transitio
|
|
|
51
51
|
* prefers-reduced-motion. CollapsibleSection's rows, and the rows under a NavItem.
|
|
52
52
|
*/
|
|
53
53
|
export declare const COLLAPSIBLE_PANEL_CLASSES = "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0";
|
|
54
|
+
/**
|
|
55
|
+
* The box a message or a comment is written in: Peek's writing box (its
|
|
56
|
+
* `looks.ts`, Katerina 24 September), moved with ComposeBox. Inset, a hairline,
|
|
57
|
+
* 12px in, 16px between the text and the row under it; the focus ring in Signal.
|
|
58
|
+
*/
|
|
59
|
+
export declare const WRITING_BOX_CLASSES = "relative bg-bg-inset border border-border-default rounded-lg p-3 flex flex-col gap-4 signal:transition-shadow signal:focus-within:border-border-focus signal:focus-within:shadow-focus-ring";
|
|
54
60
|
//# sourceMappingURL=looks.d.ts.map
|
package/dist/looks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,wDAAwD,CAAA;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,qHAAqH;AACrH,eAAO,MAAM,oBAAoB,wEAAwE,CAAA;AAEzG,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA"}
|
|
1
|
+
{"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,wDAAwD,CAAA;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,qHAAqH;AACrH,eAAO,MAAM,oBAAoB,wEAAwE,CAAA;AAEzG,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA;AAExN;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,gMAC+J,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/ui",
|
|
3
|
-
"version": "0.72.0
|
|
3
|
+
"version": "0.72.0",
|
|
4
4
|
"description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,10 +29,6 @@
|
|
|
29
29
|
"types": "./dist/editor.d.ts",
|
|
30
30
|
"default": "./dist/editor.js"
|
|
31
31
|
},
|
|
32
|
-
"./widgets-2": {
|
|
33
|
-
"types": "./dist/widgets-2.d.ts",
|
|
34
|
-
"default": "./dist/widgets-2.js"
|
|
35
|
-
},
|
|
36
32
|
"./gates": {
|
|
37
33
|
"types": "./dist/gates/index.d.ts",
|
|
38
34
|
"default": "./dist/gates/index.js"
|
package/registry.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"kind": "package",
|
|
5
5
|
"repo": "estiva-ui",
|
|
6
6
|
"package": "@estiva-app/ui",
|
|
7
|
-
"packageVersion": "0.72.0
|
|
8
|
-
"exports":
|
|
9
|
-
"typeExports":
|
|
7
|
+
"packageVersion": "0.72.0",
|
|
8
|
+
"exports": 177,
|
|
9
|
+
"typeExports": 162,
|
|
10
10
|
"files": null
|
|
11
11
|
},
|
|
12
12
|
"storybook": {
|
|
@@ -15,6 +15,25 @@
|
|
|
15
15
|
"devUrl": "http://localhost:6008"
|
|
16
16
|
},
|
|
17
17
|
"entries": [
|
|
18
|
+
{
|
|
19
|
+
"name": "anchorOfSelection",
|
|
20
|
+
"repo": "estiva-ui",
|
|
21
|
+
"kind": "helper",
|
|
22
|
+
"importPath": "@estiva-app/ui/editor",
|
|
23
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
24
|
+
"purpose": "The anchor a comment on the selection carries, as §13.6 writes it: the selected blocks, and the selected words in one block or the words where it starts and ends across several.",
|
|
25
|
+
"purposeFrom": "comment",
|
|
26
|
+
"props": [],
|
|
27
|
+
"variants": [],
|
|
28
|
+
"ownsBehaviours": [],
|
|
29
|
+
"status": "stable",
|
|
30
|
+
"migrationStage": null,
|
|
31
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
32
|
+
"storyId": "primitives-commenthighlights--default",
|
|
33
|
+
"docPage": null,
|
|
34
|
+
"app": null,
|
|
35
|
+
"looks": []
|
|
36
|
+
},
|
|
18
37
|
{
|
|
19
38
|
"name": "appendTableColumn",
|
|
20
39
|
"repo": "estiva-ui",
|
|
@@ -1832,6 +1851,186 @@
|
|
|
1832
1851
|
"app": null,
|
|
1833
1852
|
"looks": []
|
|
1834
1853
|
},
|
|
1854
|
+
{
|
|
1855
|
+
"name": "commentElement",
|
|
1856
|
+
"repo": "estiva-ui",
|
|
1857
|
+
"kind": "helper",
|
|
1858
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1859
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
1860
|
+
"purpose": "What a comment's card stands beside: its drawn highlight, or — its words gone — its first block.",
|
|
1861
|
+
"purposeFrom": "comment",
|
|
1862
|
+
"props": [],
|
|
1863
|
+
"variants": [],
|
|
1864
|
+
"ownsBehaviours": [],
|
|
1865
|
+
"status": "stable",
|
|
1866
|
+
"migrationStage": null,
|
|
1867
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
1868
|
+
"storyId": "primitives-commenthighlights--default",
|
|
1869
|
+
"docPage": null,
|
|
1870
|
+
"app": null,
|
|
1871
|
+
"looks": []
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
"name": "CommentHighlights",
|
|
1875
|
+
"repo": "estiva-ui",
|
|
1876
|
+
"kind": "component",
|
|
1877
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1878
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
1879
|
+
"purpose": "Comments drawn where they are in an editor of a block document: on a few words, on a passage across blocks, or on whole blocks, in a light fill, the open one underlined.",
|
|
1880
|
+
"purposeFrom": "page",
|
|
1881
|
+
"props": [],
|
|
1882
|
+
"variants": [],
|
|
1883
|
+
"ownsBehaviours": [],
|
|
1884
|
+
"status": "stable",
|
|
1885
|
+
"migrationStage": null,
|
|
1886
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
1887
|
+
"storyId": "primitives-commenthighlights--default",
|
|
1888
|
+
"docPage": "src/CommentHighlights.mdx",
|
|
1889
|
+
"app": null,
|
|
1890
|
+
"looks": [
|
|
1891
|
+
"cursor-pointer rounded-sm bg-warning-muted underline decoration-warning-default decoration-2 underline-offset-4"
|
|
1892
|
+
]
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
"name": "commentPlace",
|
|
1896
|
+
"repo": "estiva-ui",
|
|
1897
|
+
"kind": "helper",
|
|
1898
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1899
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
1900
|
+
"purpose": "Where a comment is now: on its words or blocks, beside its blocks with its words gone, or detached.",
|
|
1901
|
+
"purposeFrom": "comment",
|
|
1902
|
+
"props": [],
|
|
1903
|
+
"variants": [],
|
|
1904
|
+
"ownsBehaviours": [],
|
|
1905
|
+
"status": "stable",
|
|
1906
|
+
"migrationStage": null,
|
|
1907
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
1908
|
+
"storyId": "primitives-commenthighlights--default",
|
|
1909
|
+
"docPage": null,
|
|
1910
|
+
"app": null,
|
|
1911
|
+
"looks": []
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
"name": "COMPOSE_EDITOR_CLASSES",
|
|
1915
|
+
"repo": "estiva-ui",
|
|
1916
|
+
"kind": "helper",
|
|
1917
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1918
|
+
"sourceFile": "src/ComposeBox.tsx",
|
|
1919
|
+
"purpose": "The editor's own classes inside the box: Peek's, rich text with no outline.",
|
|
1920
|
+
"purposeFrom": "comment",
|
|
1921
|
+
"props": [],
|
|
1922
|
+
"variants": [],
|
|
1923
|
+
"ownsBehaviours": [],
|
|
1924
|
+
"status": "stable",
|
|
1925
|
+
"migrationStage": null,
|
|
1926
|
+
"docsId": "primitives-composebox--docs",
|
|
1927
|
+
"storyId": "primitives-composebox--default",
|
|
1928
|
+
"docPage": null,
|
|
1929
|
+
"app": null,
|
|
1930
|
+
"looks": []
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
"name": "ComposeBox",
|
|
1934
|
+
"repo": "estiva-ui",
|
|
1935
|
+
"kind": "component",
|
|
1936
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1937
|
+
"sourceFile": "src/ComposeBox.tsx",
|
|
1938
|
+
"purpose": "The box a message, a reply or a comment is written in: the text, the selection strip over it, what is attached, the tools and Send.",
|
|
1939
|
+
"purposeFrom": "page",
|
|
1940
|
+
"props": [
|
|
1941
|
+
{
|
|
1942
|
+
"name": "editor",
|
|
1943
|
+
"takes": "Editor | null",
|
|
1944
|
+
"required": true,
|
|
1945
|
+
"note": "The app's editor, with `ComposeKeys` in its extensions and `COMPOSE_EDITOR_CLASSES` on it."
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
"name": "onSend",
|
|
1949
|
+
"takes": "a handler",
|
|
1950
|
+
"required": true,
|
|
1951
|
+
"note": "Send what is written. The box empties afterwards — unless this returns `false`: nothing was sent, and the words stay."
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
"name": "placeholder",
|
|
1955
|
+
"takes": "anything",
|
|
1956
|
+
"required": true,
|
|
1957
|
+
"note": "Said in the empty box: words, and a `Kbd` where a key helps (\"type / for commands\")."
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
"name": "tools",
|
|
1961
|
+
"takes": "anything",
|
|
1962
|
+
"required": false,
|
|
1963
|
+
"note": "The tools under the text, left: `ToolbarButton`s (attach, highlight)."
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
"name": "attachments",
|
|
1967
|
+
"takes": "anything",
|
|
1968
|
+
"required": false,
|
|
1969
|
+
"note": "What is attached, under the text: `AttachmentCard pending`s."
|
|
1970
|
+
},
|
|
1971
|
+
{
|
|
1972
|
+
"name": "ready",
|
|
1973
|
+
"takes": "true/false",
|
|
1974
|
+
"required": false,
|
|
1975
|
+
"note": "Something to send even with no words — a file ready."
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
"name": "holding",
|
|
1979
|
+
"takes": "true/false",
|
|
1980
|
+
"required": false,
|
|
1981
|
+
"note": "Sending waits — a file still uploading."
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
"name": "marked",
|
|
1985
|
+
"takes": "true/false",
|
|
1986
|
+
"required": false,
|
|
1987
|
+
"note": "The bar down the text's left: an urgent mention or a highlight, as Peek draws it."
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
"name": "selection",
|
|
1991
|
+
"takes": "Pick<SelectionToolbarProps, 'marks' | 'link' | 'actions'>",
|
|
1992
|
+
"required": false,
|
|
1993
|
+
"note": "The selection strip's buttons: its marks, Link, and the app's own (`actions`)."
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
"name": "className",
|
|
1997
|
+
"takes": "text",
|
|
1998
|
+
"required": false,
|
|
1999
|
+
"note": null
|
|
2000
|
+
}
|
|
2001
|
+
],
|
|
2002
|
+
"variants": [],
|
|
2003
|
+
"ownsBehaviours": [],
|
|
2004
|
+
"status": "stable",
|
|
2005
|
+
"migrationStage": null,
|
|
2006
|
+
"docsId": "primitives-composebox--docs",
|
|
2007
|
+
"storyId": "primitives-composebox--default",
|
|
2008
|
+
"docPage": "src/ComposeBox.mdx",
|
|
2009
|
+
"app": null,
|
|
2010
|
+
"looks": [
|
|
2011
|
+
"bg-bg-inset border border-border-default rounded-lg signal:transition-shadow signal:focus-within:border-border-focus signal:focus-within:shadow-focus-ring focus-within:border-border-strong transition-colors",
|
|
2012
|
+
"select-none font-mono text-menu font-normal tracking-wider text-text-muted"
|
|
2013
|
+
]
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
"name": "ComposeKeys",
|
|
2017
|
+
"repo": "estiva-ui",
|
|
2018
|
+
"kind": "component",
|
|
2019
|
+
"importPath": "@estiva-app/ui/editor",
|
|
2020
|
+
"sourceFile": "src/ComposeBox.tsx",
|
|
2021
|
+
"purpose": "Enter sends; Shift+Enter is a new line — in a list a new item (or out of the list, on an empty one), in a quote a new line (or out of it, on an empty one).",
|
|
2022
|
+
"purposeFrom": "comment",
|
|
2023
|
+
"props": [],
|
|
2024
|
+
"variants": [],
|
|
2025
|
+
"ownsBehaviours": [],
|
|
2026
|
+
"status": "stable",
|
|
2027
|
+
"migrationStage": null,
|
|
2028
|
+
"docsId": "primitives-composebox--docs",
|
|
2029
|
+
"storyId": "primitives-composebox--default",
|
|
2030
|
+
"docPage": null,
|
|
2031
|
+
"app": null,
|
|
2032
|
+
"looks": []
|
|
2033
|
+
},
|
|
1835
2034
|
{
|
|
1836
2035
|
"name": "ConfirmDialog",
|
|
1837
2036
|
"repo": "estiva-ui",
|
|
@@ -3465,6 +3664,25 @@
|
|
|
3465
3664
|
"border-border-tooltip-key bg-bg-tooltip-key text-text-tooltip-key signal:bg-bg-tooltip-key ship:bg-bg-tooltip-key"
|
|
3466
3665
|
]
|
|
3467
3666
|
},
|
|
3667
|
+
{
|
|
3668
|
+
"name": "keepSelection",
|
|
3669
|
+
"repo": "estiva-ui",
|
|
3670
|
+
"kind": "helper",
|
|
3671
|
+
"importPath": "@estiva-app/ui/editor",
|
|
3672
|
+
"sourceFile": "src/SelectionToolbar.tsx",
|
|
3673
|
+
"purpose": "Draw the selection as it is now, until `releaseSelection`.",
|
|
3674
|
+
"purposeFrom": "comment",
|
|
3675
|
+
"props": [],
|
|
3676
|
+
"variants": [],
|
|
3677
|
+
"ownsBehaviours": [],
|
|
3678
|
+
"status": "stable",
|
|
3679
|
+
"migrationStage": null,
|
|
3680
|
+
"docsId": "primitives-selectiontoolbar--docs",
|
|
3681
|
+
"storyId": "primitives-selectiontoolbar--default",
|
|
3682
|
+
"docPage": null,
|
|
3683
|
+
"app": null,
|
|
3684
|
+
"looks": []
|
|
3685
|
+
},
|
|
3468
3686
|
{
|
|
3469
3687
|
"name": "KeptSelection",
|
|
3470
3688
|
"repo": "estiva-ui",
|
|
@@ -5359,6 +5577,50 @@
|
|
|
5359
5577
|
"text-menu uppercase tracking-widest text-text-secondary"
|
|
5360
5578
|
]
|
|
5361
5579
|
},
|
|
5580
|
+
{
|
|
5581
|
+
"name": "quoteText",
|
|
5582
|
+
"repo": "estiva-ui",
|
|
5583
|
+
"kind": "helper",
|
|
5584
|
+
"importPath": "@estiva-app/ui/editor",
|
|
5585
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
5586
|
+
"purpose": "A quote in one line, as a card says it: its start and end words around an ellipsis.",
|
|
5587
|
+
"purposeFrom": "comment",
|
|
5588
|
+
"props": [
|
|
5589
|
+
{
|
|
5590
|
+
"name": "start",
|
|
5591
|
+
"takes": "text",
|
|
5592
|
+
"required": true,
|
|
5593
|
+
"note": "The selection's words in one block, or its first words when it runs across blocks."
|
|
5594
|
+
},
|
|
5595
|
+
{
|
|
5596
|
+
"name": "end",
|
|
5597
|
+
"takes": "text",
|
|
5598
|
+
"required": false,
|
|
5599
|
+
"note": "Its last words, in the run's last block."
|
|
5600
|
+
},
|
|
5601
|
+
{
|
|
5602
|
+
"name": "prefix",
|
|
5603
|
+
"takes": "text",
|
|
5604
|
+
"required": false,
|
|
5605
|
+
"note": "A few characters before `start`, only when the words repeat in their block."
|
|
5606
|
+
},
|
|
5607
|
+
{
|
|
5608
|
+
"name": "suffix",
|
|
5609
|
+
"takes": "text",
|
|
5610
|
+
"required": false,
|
|
5611
|
+
"note": "A few characters after the end, only when the words repeat in their block."
|
|
5612
|
+
}
|
|
5613
|
+
],
|
|
5614
|
+
"variants": [],
|
|
5615
|
+
"ownsBehaviours": [],
|
|
5616
|
+
"status": "stable",
|
|
5617
|
+
"migrationStage": null,
|
|
5618
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
5619
|
+
"storyId": "primitives-commenthighlights--default",
|
|
5620
|
+
"docPage": null,
|
|
5621
|
+
"app": null,
|
|
5622
|
+
"looks": []
|
|
5623
|
+
},
|
|
5362
5624
|
{
|
|
5363
5625
|
"name": "Rail",
|
|
5364
5626
|
"repo": "estiva-ui",
|
|
@@ -5804,6 +6066,25 @@
|
|
|
5804
6066
|
"app": null,
|
|
5805
6067
|
"looks": []
|
|
5806
6068
|
},
|
|
6069
|
+
{
|
|
6070
|
+
"name": "releaseSelection",
|
|
6071
|
+
"repo": "estiva-ui",
|
|
6072
|
+
"kind": "helper",
|
|
6073
|
+
"importPath": "@estiva-app/ui/editor",
|
|
6074
|
+
"sourceFile": "src/SelectionToolbar.tsx",
|
|
6075
|
+
"purpose": "Stop drawing it. Nothing kept, nothing dispatched.",
|
|
6076
|
+
"purposeFrom": "comment",
|
|
6077
|
+
"props": [],
|
|
6078
|
+
"variants": [],
|
|
6079
|
+
"ownsBehaviours": [],
|
|
6080
|
+
"status": "stable",
|
|
6081
|
+
"migrationStage": null,
|
|
6082
|
+
"docsId": "primitives-selectiontoolbar--docs",
|
|
6083
|
+
"storyId": "primitives-selectiontoolbar--default",
|
|
6084
|
+
"docPage": null,
|
|
6085
|
+
"app": null,
|
|
6086
|
+
"looks": []
|
|
6087
|
+
},
|
|
5807
6088
|
{
|
|
5808
6089
|
"name": "RichText",
|
|
5809
6090
|
"repo": "estiva-ui",
|
|
@@ -6403,6 +6684,12 @@
|
|
|
6403
6684
|
"takes": "anything",
|
|
6404
6685
|
"required": false,
|
|
6405
6686
|
"note": "More of the strip, after the marks: the app's own `ToolbarButton`s for what is selected — a chip's card, say."
|
|
6687
|
+
},
|
|
6688
|
+
{
|
|
6689
|
+
"name": "actions",
|
|
6690
|
+
"takes": "readonly SelectionAction[]",
|
|
6691
|
+
"required": false,
|
|
6692
|
+
"note": "The app's own buttons after the marks, past a separator: \"Comment\"."
|
|
6406
6693
|
}
|
|
6407
6694
|
],
|
|
6408
6695
|
"variants": [],
|
|
@@ -6436,6 +6723,25 @@
|
|
|
6436
6723
|
"app": null,
|
|
6437
6724
|
"looks": []
|
|
6438
6725
|
},
|
|
6726
|
+
{
|
|
6727
|
+
"name": "setCommentAnchors",
|
|
6728
|
+
"repo": "estiva-ui",
|
|
6729
|
+
"kind": "helper",
|
|
6730
|
+
"importPath": "@estiva-app/ui/editor",
|
|
6731
|
+
"sourceFile": "src/CommentHighlights.ts",
|
|
6732
|
+
"purpose": "Draw these comments, `active` the one being read.",
|
|
6733
|
+
"purposeFrom": "comment",
|
|
6734
|
+
"props": [],
|
|
6735
|
+
"variants": [],
|
|
6736
|
+
"ownsBehaviours": [],
|
|
6737
|
+
"status": "stable",
|
|
6738
|
+
"migrationStage": null,
|
|
6739
|
+
"docsId": "primitives-commenthighlights--docs",
|
|
6740
|
+
"storyId": "primitives-commenthighlights--default",
|
|
6741
|
+
"docPage": null,
|
|
6742
|
+
"app": null,
|
|
6743
|
+
"looks": []
|
|
6744
|
+
},
|
|
6439
6745
|
{
|
|
6440
6746
|
"name": "setReferenceCard",
|
|
6441
6747
|
"repo": "estiva-ui",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Meta, Canvas } from '@storybook/addon-docs/blocks'
|
|
2
|
+
import * as CommentHighlightsStories from './CommentHighlights.stories'
|
|
3
|
+
|
|
4
|
+
<Meta of={CommentHighlightsStories} />
|
|
5
|
+
|
|
6
|
+
# CommentHighlights
|
|
7
|
+
|
|
8
|
+
Comments drawn where they are in an editor of a block document: on a few words, on a passage across blocks, or on whole blocks, in a light fill, the open one underlined.
|
|
9
|
+
|
|
10
|
+
<Canvas of={CommentHighlightsStories.Default} />
|
|
11
|
+
|
|
12
|
+
## When
|
|
13
|
+
|
|
14
|
+
- **A comment on words or blocks of a block document.** SPEC §13.6 anchors a comment to a run of blocks; the words where its selection starts and ends travel with it as a quote and are found again in the run's first and last blocks when the document is drawn.
|
|
15
|
+
- `setCommentAnchors(editor, anchors, active)` draws them; `active` is the one being read.
|
|
16
|
+
- The editor's `commentSelect` event says which highlight was clicked. Where two overlap, the narrower one is.
|
|
17
|
+
- `commentPlace` says where a comment is now: `resolved` (drawn, on its words or on its whole blocks), `reworded` (its blocks are there, its words are not: nothing is drawn) or `detached` (its blocks are gone). A reworded comment's words are shown as what its writer quoted, never as what the page says; neither state is ever shown as a comment on the whole document.
|
|
18
|
+
- `commentElement` is what its card stands beside: the highlight, or the first block of a reworded comment.
|
|
19
|
+
- `anchorOfSelection` is what a comment on the selection carries: the selected blocks, and their words unless whole blocks are selected. `quoteText` puts a quote in one line.
|
|
20
|
+
|
|
21
|
+
<Canvas of={CommentHighlightsStories.NoneOpen} />
|
|
22
|
+
|
|
23
|
+
## When not
|
|
24
|
+
|
|
25
|
+
- Showing a selection while a field has the focus → **SelectionToolbar**'s kept selection.
|
|
26
|
+
|
|
27
|
+
No alternative: a comment on the whole document has no place to draw. Say where it belongs in words.
|
|
28
|
+
|
|
29
|
+
## How
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useEffect } from 'react'
|
|
33
|
+
import { useEditor } from '@tiptap/react'
|
|
34
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
35
|
+
import { BlockId, CommentHighlights, setCommentAnchors } from '@estiva-app/ui/editor'
|
|
36
|
+
|
|
37
|
+
const editor = useEditor({ extensions: [StarterKit, BlockId, CommentHighlights] })
|
|
38
|
+
|
|
39
|
+
// threads: [{ id, blocks: ['b2', 'b3'], quote: { start: 'first words', end: 'last words' } }, …]
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!editor) return
|
|
42
|
+
setCommentAnchors(editor, threads, openId)
|
|
43
|
+
const select = ({ id }: { id: string }) => open(id)
|
|
44
|
+
editor.on('commentSelect', select)
|
|
45
|
+
return () => { editor.off('commentSelect', select) }
|
|
46
|
+
}, [editor, threads, openId])
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Nothing is written into the document.** The highlight is a decoration: a save never carries it.
|
|
50
|
+
- `BlockId` must be in the editor: the anchors find their blocks by their ids.
|
|
51
|
+
- Words are matched exactly, never trimmed or guessed. Words that repeat in their block are told apart by the few characters around them; without those, the first is drawn.
|
|
52
|
+
- `commentPlace` reads the document as it is: call it again after an edit.
|
|
53
|
+
|
|
54
|
+
## What it owns
|
|
55
|
+
|
|
56
|
+
| It does | So you never write |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Finds the run and its words again, after every edit | position arithmetic |
|
|
59
|
+
| Draws words inline and whole blocks whole, the open one underlined | a mark in the document |
|
|
60
|
+
| Tells drawn, reworded and detached apart | a "not found" guess |
|
|
61
|
+
| Says which highlight was clicked | a click handler on the text |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { EditorContent, useEditor } from '@tiptap/react'
|
|
4
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
5
|
+
import { BlockId } from './BlockNodes'
|
|
6
|
+
import { CommentHighlights, commentPlace, setCommentAnchors, type CommentAnchor } from './CommentHighlights'
|
|
7
|
+
import { richTextClassName } from './RichText'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Comments drawn where they are. Click a highlight: it becomes the open one.
|
|
11
|
+
* Type over highlighted words and the line under the text says where each
|
|
12
|
+
* comment is now.
|
|
13
|
+
*/
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'Primitives/CommentHighlights',
|
|
16
|
+
parameters: { layout: 'padded' },
|
|
17
|
+
} satisfies Meta
|
|
18
|
+
|
|
19
|
+
export default meta
|
|
20
|
+
type Story = StoryObj
|
|
21
|
+
|
|
22
|
+
const DOCUMENT = {
|
|
23
|
+
type: 'doc',
|
|
24
|
+
content: [
|
|
25
|
+
{ type: 'heading', attrs: { level: 1, blockId: 'b1' }, content: [{ type: 'text', text: 'Heading' }] },
|
|
26
|
+
{ type: 'paragraph', attrs: { blockId: 'b2' }, content: [{ type: 'text', text: 'A highlight is drawn on the words a comment quotes, found again in its block.' }] },
|
|
27
|
+
{ type: 'paragraph', attrs: { blockId: 'b3' }, content: [{ type: 'text', text: 'A selection across blocks keeps the words where it starts and where it ends.' }] },
|
|
28
|
+
{ type: 'paragraph', attrs: { blockId: 'b4' }, content: [{ type: 'text', text: 'A comment on whole blocks keeps no words, and is drawn on every block of its run.' }] },
|
|
29
|
+
{ type: 'paragraph', attrs: { blockId: 'b5' }, content: [{ type: 'text', text: 'Words that changed are not drawn.' }] },
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ANCHORS: CommentAnchor[] = [
|
|
34
|
+
{ id: 'one', blocks: ['b2'], quote: { start: 'the words a comment quotes' } },
|
|
35
|
+
{ id: 'two', blocks: ['b2', 'b3'], quote: { start: 'found again in its block.', end: 'A selection across blocks' } },
|
|
36
|
+
{ id: 'three', blocks: ['b4'] },
|
|
37
|
+
{ id: 'four', blocks: ['b5'], quote: { start: 'Items that changed' } },
|
|
38
|
+
{ id: 'five', blocks: ['gone'], quote: { start: 'Label' } },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
function Highlighted({ initial }: { initial: string | null }) {
|
|
42
|
+
const [active, setActive] = useState<string | null>(initial)
|
|
43
|
+
const [, setVersion] = useState(0)
|
|
44
|
+
const editor = useEditor({
|
|
45
|
+
extensions: [StarterKit, BlockId, CommentHighlights],
|
|
46
|
+
content: DOCUMENT,
|
|
47
|
+
editorProps: { attributes: { class: richTextClassName('default', 'outline-none'), 'aria-label': 'Text' } },
|
|
48
|
+
onUpdate: () => setVersion((v) => v + 1),
|
|
49
|
+
})
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!editor) return
|
|
52
|
+
setCommentAnchors(editor, ANCHORS, active)
|
|
53
|
+
const select = ({ id }: { id: string }) => setActive(id)
|
|
54
|
+
editor.on('commentSelect', select)
|
|
55
|
+
return () => {
|
|
56
|
+
editor.off('commentSelect', select)
|
|
57
|
+
}
|
|
58
|
+
}, [editor, active])
|
|
59
|
+
const states = editor ? ANCHORS.map((a) => `${a.id}: ${commentPlace(editor, a).state}`).join(' · ') : ''
|
|
60
|
+
return (
|
|
61
|
+
<div className="flex max-w-[560px] flex-col gap-3">
|
|
62
|
+
<EditorContent editor={editor} />
|
|
63
|
+
<p className="text-caption text-text-muted">{`Open: ${active ?? 'none'} · ${states}`}</p>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Words, a run across two blocks and a whole block, the open one underlined. Click another to open it. */
|
|
69
|
+
export const Default: Story = { render: () => <Highlighted initial="two" /> }
|
|
70
|
+
|
|
71
|
+
/** Nothing open: every comment in the same light fill. */
|
|
72
|
+
export const NoneOpen: Story = { render: () => <Highlighted initial={null} /> }
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* Where a comment on a selection is anchored (§13.6), and where an anchor is
|
|
4
|
+
* found again: what `anchorOfSelection` hands the app to write on the
|
|
5
|
+
* comment, and what `commentPlace` says when the page has changed since.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
8
|
+
import { Editor } from '@tiptap/core'
|
|
9
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
10
|
+
import { BlockId } from './BlockNodes'
|
|
11
|
+
import { CommentHighlights, anchorOfSelection, commentPlace } from './CommentHighlights'
|
|
12
|
+
|
|
13
|
+
let editor: Editor | undefined
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
editor?.destroy()
|
|
17
|
+
editor = undefined
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const paragraph = (blockId: string, text: string) => ({ type: 'paragraph', attrs: { blockId }, content: [{ type: 'text', text }] })
|
|
21
|
+
|
|
22
|
+
/** Two paragraphs: "alpha beta gamma" (text at 1–17) and "delta epsilon" (text at 19–32). */
|
|
23
|
+
function open(content: object[] = [paragraph('p1', 'alpha beta gamma'), paragraph('p2', 'delta epsilon')]) {
|
|
24
|
+
editor = new Editor({ extensions: [StarterKit, BlockId, CommentHighlights], content: { type: 'doc', content } })
|
|
25
|
+
return editor
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('anchorOfSelection', () => {
|
|
29
|
+
it('quotes the first word of a paragraph, not the whole paragraph', () => {
|
|
30
|
+
const e = open()
|
|
31
|
+
e.commands.setTextSelection({ from: 1, to: 6 })
|
|
32
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['p1'], quote: { start: 'alpha' } })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('quotes the last word of a paragraph, not the whole paragraph', () => {
|
|
36
|
+
const e = open()
|
|
37
|
+
e.commands.setTextSelection({ from: 12, to: 17 })
|
|
38
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['p1'], quote: { start: 'gamma' } })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('anchors a selection of a whole paragraph to the block, with no words', () => {
|
|
42
|
+
const e = open()
|
|
43
|
+
e.commands.setTextSelection({ from: 1, to: 17 })
|
|
44
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['p1'] })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('quotes where a selection across blocks starts and where it ends', () => {
|
|
48
|
+
const e = open()
|
|
49
|
+
e.commands.setTextSelection({ from: 7, to: 24 })
|
|
50
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['p1', 'p2'], quote: { start: 'beta gamma', end: 'delta' } })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('anchors nothing when only the edge between two blocks is selected', () => {
|
|
54
|
+
const e = open()
|
|
55
|
+
e.commands.setTextSelection({ from: 17, to: 19 })
|
|
56
|
+
expect(anchorOfSelection(e)).toBeNull()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('anchors two lines of a quote to the quote', () => {
|
|
60
|
+
const e = open([
|
|
61
|
+
{ type: 'blockquote', attrs: { blockId: 'q1' }, content: [{ type: 'paragraph', content: [{ type: 'text', text: 'one line' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'two line' }] }] },
|
|
62
|
+
])
|
|
63
|
+
// The quote opens at 0, its first line's text at 2–10, the second's at 12–20.
|
|
64
|
+
e.commands.setTextSelection({ from: 6, to: 15 })
|
|
65
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['q1'], quote: { start: 'linetwo' } })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('keeps the words around a quote only when the quoted words repeat', () => {
|
|
69
|
+
const e = open([paragraph('p1', 'see it, then see it again')])
|
|
70
|
+
e.commands.setTextSelection({ from: 14, to: 20 })
|
|
71
|
+
expect(anchorOfSelection(e)).toEqual({ blocks: ['p1'], quote: { start: 'see it', prefix: 'see it, then ', suffix: ' again' } })
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('commentPlace', () => {
|
|
76
|
+
it('finds quoted words on the words', () => {
|
|
77
|
+
const e = open()
|
|
78
|
+
expect(commentPlace(e, { id: 'c', blocks: ['p1'], quote: { start: 'beta' } })).toEqual({ state: 'resolved', from: 7, to: 11, words: true })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('draws a comment with no words on its whole blocks', () => {
|
|
82
|
+
const e = open()
|
|
83
|
+
expect(commentPlace(e, { id: 'c', blocks: ['p1', 'p2'] })).toEqual({ state: 'resolved', from: 0, to: 33, words: false })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('says reworded when the blocks are there and the words are not', () => {
|
|
87
|
+
const e = open()
|
|
88
|
+
expect(commentPlace(e, { id: 'c', blocks: ['p1'], quote: { start: 'omega' } })).toEqual({ state: 'reworded', from: 0, to: 18 })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('says detached when none of the blocks is left', () => {
|
|
92
|
+
const e = open()
|
|
93
|
+
expect(commentPlace(e, { id: 'c', blocks: ['gone'], quote: { start: 'alpha' } })).toEqual({ state: 'detached' })
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('picks the repeated words whose context matches', () => {
|
|
97
|
+
const e = open([paragraph('p1', 'see it, then see it again')])
|
|
98
|
+
expect(commentPlace(e, { id: 'c', blocks: ['p1'], quote: { start: 'see it', suffix: ' again' } })).toEqual({ state: 'resolved', from: 14, to: 20, words: true })
|
|
99
|
+
})
|
|
100
|
+
})
|