@maple-kit/ui 0.1.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +39 -0
- package/dist/composer/actions.d.ts +15 -0
- package/dist/composer/actions.js +59 -0
- package/dist/composer/attachments.d.ts +33 -0
- package/dist/composer/attachments.js +166 -0
- package/dist/composer/badge.d.ts +13 -0
- package/dist/composer/badge.js +25 -0
- package/dist/composer/body.d.ts +13 -0
- package/dist/composer/body.js +119 -0
- package/dist/composer/composer.d.ts +23 -0
- package/dist/composer/composer.js +122 -0
- package/dist/composer/css.js +559 -0
- package/dist/composer/detail.d.ts +32 -0
- package/dist/composer/detail.js +102 -0
- package/dist/composer/emoji.d.ts +54 -0
- package/dist/composer/emoji.js +131 -0
- package/dist/composer/index.d.ts +15 -0
- package/dist/composer/index.js +15 -0
- package/dist/composer/inset.js +20 -0
- package/dist/composer/leave.d.ts +19 -0
- package/dist/composer/leave.js +34 -0
- package/dist/composer/peek.d.ts +21 -0
- package/dist/composer/peek.js +29 -0
- package/dist/composer/phrase.d.ts +27 -0
- package/dist/composer/phrase.js +30 -0
- package/dist/composer/picker-emoji.d.ts +34 -0
- package/dist/composer/picker-emoji.js +48 -0
- package/dist/composer/prompt.js +31 -0
- package/dist/composer/scope.d.ts +11 -0
- package/dist/composer/scope.js +16 -0
- package/dist/composer/target.d.ts +13 -0
- package/dist/composer/target.js +45 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.js +16 -0
- package/dist/data.d.ts +52 -0
- package/dist/data.js +30 -0
- package/dist/icons/branch.d.ts +4 -0
- package/dist/icons/branch.js +8 -0
- package/dist/icons/cog.d.ts +4 -0
- package/dist/icons/cog.js +8 -0
- package/dist/icons/commit.d.ts +4 -0
- package/dist/icons/commit.js +5 -0
- package/dist/icons/crossfade.d.ts +15 -0
- package/dist/icons/crossfade.js +27 -0
- package/dist/icons/element.d.ts +4 -0
- package/dist/icons/element.js +5 -0
- package/dist/icons/icon.d.ts +24 -0
- package/dist/icons/icon.js +27 -0
- package/dist/icons/index.d.ts +13 -0
- package/dist/icons/index.js +13 -0
- package/dist/icons/noplace.d.ts +4 -0
- package/dist/icons/noplace.js +5 -0
- package/dist/icons/region.d.ts +4 -0
- package/dist/icons/region.js +8 -0
- package/dist/icons/smiley.d.ts +4 -0
- package/dist/icons/smiley.js +8 -0
- package/dist/icons/sparkle.d.ts +4 -0
- package/dist/icons/sparkle.js +8 -0
- package/dist/icons/target.d.ts +4 -0
- package/dist/icons/target.js +5 -0
- package/dist/icons/text.d.ts +4 -0
- package/dist/icons/text.js +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/island/account.d.ts +10 -0
- package/dist/island/account.js +52 -0
- package/dist/island/comments.d.ts +24 -0
- package/dist/island/comments.js +40 -0
- package/dist/island/content.d.ts +10 -0
- package/dist/island/content.js +26 -0
- package/dist/island/context.d.ts +58 -0
- package/dist/island/context.js +23 -0
- package/dist/island/css.d.ts +13 -0
- package/dist/island/css.js +1020 -0
- package/dist/island/drag.d.ts +10 -0
- package/dist/island/drag.js +77 -0
- package/dist/island/filters.d.ts +10 -0
- package/dist/island/filters.js +63 -0
- package/dist/island/header.d.ts +32 -0
- package/dist/island/header.js +47 -0
- package/dist/island/index.d.ts +18 -0
- package/dist/island/index.js +18 -0
- package/dist/island/island.d.ts +12 -0
- package/dist/island/island.js +111 -0
- package/dist/island/item.d.ts +12 -0
- package/dist/island/item.js +158 -0
- package/dist/island/language.d.ts +55 -0
- package/dist/island/language.js +125 -0
- package/dist/island/leaf.js +17 -0
- package/dist/island/list.d.ts +12 -0
- package/dist/island/list.js +31 -0
- package/dist/island/new-comment.d.ts +18 -0
- package/dist/island/new-comment.js +56 -0
- package/dist/island/settings.d.ts +10 -0
- package/dist/island/settings.js +134 -0
- package/dist/island/stagger.d.ts +12 -0
- package/dist/island/stagger.js +4 -0
- package/dist/island/time.d.ts +17 -0
- package/dist/island/time.js +29 -0
- package/dist/island/trigger.d.ts +10 -0
- package/dist/island/trigger.js +39 -0
- package/dist/language.d.ts +45 -0
- package/dist/language.js +66 -0
- package/dist/maple.d.ts +25 -0
- package/dist/maple.js +65 -0
- package/dist/marks/avatar.d.ts +16 -0
- package/dist/marks/avatar.js +36 -0
- package/dist/marks/css.d.ts +12 -0
- package/dist/marks/css.js +371 -0
- package/dist/marks/frame.d.ts +25 -0
- package/dist/marks/frame.js +53 -0
- package/dist/marks/geometry.d.ts +55 -0
- package/dist/marks/geometry.js +61 -0
- package/dist/marks/index.d.ts +14 -0
- package/dist/marks/index.js +14 -0
- package/dist/marks/label.d.ts +23 -0
- package/dist/marks/label.js +31 -0
- package/dist/marks/layer.d.ts +16 -0
- package/dist/marks/layer.js +194 -0
- package/dist/marks/leaf.d.ts +18 -0
- package/dist/marks/leaf.js +7 -0
- package/dist/marks/mark.d.ts +28 -0
- package/dist/marks/mark.js +43 -0
- package/dist/marks/nudge.d.ts +50 -0
- package/dist/marks/nudge.js +76 -0
- package/dist/marks/paint.d.ts +11 -0
- package/dist/marks/paint.js +19 -0
- package/dist/marks/placement.d.ts +24 -0
- package/dist/marks/placement.js +28 -0
- package/dist/marks/ring.d.ts +28 -0
- package/dist/marks/ring.js +85 -0
- package/dist/marks/shape.d.ts +21 -0
- package/dist/marks/shape.js +51 -0
- package/dist/notice/css.d.ts +11 -0
- package/dist/notice/css.js +74 -0
- package/dist/notice/index.d.ts +4 -0
- package/dist/notice/index.js +4 -0
- package/dist/notice/language.d.ts +15 -0
- package/dist/notice/language.js +14 -0
- package/dist/notice/notice.d.ts +14 -0
- package/dist/notice/notice.js +49 -0
- package/dist/part.d.ts +9 -0
- package/dist/part.js +11 -0
- package/dist/picker/css.d.ts +13 -0
- package/dist/picker/css.js +141 -0
- package/dist/picker/index.d.ts +6 -0
- package/dist/picker/index.js +6 -0
- package/dist/picker/language.d.ts +14 -0
- package/dist/picker/language.js +17 -0
- package/dist/picker/picker.d.ts +18 -0
- package/dist/picker/picker.js +164 -0
- package/dist/picker/session.d.ts +16 -0
- package/dist/picker/session.js +35 -0
- package/dist/picker/target.d.ts +23 -0
- package/dist/picker/target.js +37 -0
- package/dist/root.d.ts +42 -0
- package/dist/root.js +75 -0
- package/dist/shots.js +32 -0
- package/dist/slot.d.ts +28 -0
- package/dist/slot.js +52 -0
- package/dist/slots.d.ts +24 -0
- package/dist/slots.js +75 -0
- package/dist/stylesheet.d.ts +15 -0
- package/dist/stylesheet.js +188 -0
- package/dist/theme.d.ts +20 -0
- package/dist/theme.js +13 -0
- package/dist/tip.d.ts +40 -0
- package/dist/tip.js +82 -0
- package/dist/tokens.d.ts +28 -0
- package/dist/tokens.js +180 -0
- package/package.json +56 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//#region src/island/language.ts
|
|
2
|
+
const FILTER_LABELS = {
|
|
3
|
+
all: "All",
|
|
4
|
+
open: "Open",
|
|
5
|
+
needs_reverify: "Re-verify",
|
|
6
|
+
resolved: "Resolved",
|
|
7
|
+
unpinned: "Unpinned"
|
|
8
|
+
};
|
|
9
|
+
const FILTER_ORDER = [
|
|
10
|
+
"all",
|
|
11
|
+
"open",
|
|
12
|
+
"needs_reverify",
|
|
13
|
+
"resolved"
|
|
14
|
+
];
|
|
15
|
+
const TALLY_ORDER = [
|
|
16
|
+
"open",
|
|
17
|
+
"needs_reverify",
|
|
18
|
+
"resolved",
|
|
19
|
+
"unpinned"
|
|
20
|
+
];
|
|
21
|
+
const FILTERS_LABEL = "Show";
|
|
22
|
+
function tallyTitle(filter, count) {
|
|
23
|
+
const many = count === 1 ? "comment" : "comments";
|
|
24
|
+
return `${String(count)} ${FILTER_LABELS[filter].toLowerCase()} ${many} — click to show only these`;
|
|
25
|
+
}
|
|
26
|
+
const PICK_ORDER = [
|
|
27
|
+
"element",
|
|
28
|
+
"text",
|
|
29
|
+
"region"
|
|
30
|
+
];
|
|
31
|
+
const PICK_LABELS = {
|
|
32
|
+
element: "Element",
|
|
33
|
+
region: "Region",
|
|
34
|
+
text: "Text"
|
|
35
|
+
};
|
|
36
|
+
const THEME_LABELS = {
|
|
37
|
+
auto: "Auto",
|
|
38
|
+
light: "Light",
|
|
39
|
+
dark: "Dark"
|
|
40
|
+
};
|
|
41
|
+
const THEME_TITLES = {
|
|
42
|
+
auto: "The opposite of this page, so the overlay reads as a guest on it.",
|
|
43
|
+
light: "Always light, whatever this page is in.",
|
|
44
|
+
dark: "Always dark, whatever this page is in."
|
|
45
|
+
};
|
|
46
|
+
const CORNER_LABELS = {
|
|
47
|
+
"top-left": "Top left",
|
|
48
|
+
"top-right": "Top right",
|
|
49
|
+
"bottom-left": "Bottom left",
|
|
50
|
+
"bottom-right": "Bottom right"
|
|
51
|
+
};
|
|
52
|
+
const CORNER_ORDER = [
|
|
53
|
+
"top-left",
|
|
54
|
+
"top-right",
|
|
55
|
+
"bottom-left",
|
|
56
|
+
"bottom-right"
|
|
57
|
+
];
|
|
58
|
+
const SETTINGS_COPY = {
|
|
59
|
+
theme: {
|
|
60
|
+
name: "Theme",
|
|
61
|
+
hint: "What the overlay itself is drawn in. A comment always records the page's own."
|
|
62
|
+
},
|
|
63
|
+
position: {
|
|
64
|
+
name: "Corner",
|
|
65
|
+
hint: "Where the island sits. Dragging it by its pill does the same thing."
|
|
66
|
+
},
|
|
67
|
+
hideResolved: {
|
|
68
|
+
name: "Hide resolved",
|
|
69
|
+
hint: "Done comments stay off the page and out of the list until you pick the Resolved filter."
|
|
70
|
+
},
|
|
71
|
+
developer: {
|
|
72
|
+
name: "Developer mode",
|
|
73
|
+
hint: "Shows how each comment is re-found after a deploy, its source line and its CSS path."
|
|
74
|
+
},
|
|
75
|
+
untagged: {
|
|
76
|
+
name: "This build is not tagged",
|
|
77
|
+
hint: "Comments still work, but none of them can name a component or the file it is written in. Wrap the Next config in withMaple from @maple-kit/core/next, or the Vite config in the maple plugin with tagger on."
|
|
78
|
+
},
|
|
79
|
+
hidden: {
|
|
80
|
+
name: "Hide the island",
|
|
81
|
+
hint: "It goes until you reload. A comment arriving, or a link to one, brings it straight back."
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const ACCOUNT_COPY = {
|
|
85
|
+
name: "GitHub",
|
|
86
|
+
unlinked: "Link your account and comments are posted on the pull request as you.",
|
|
87
|
+
linking: "Enter this code on GitHub. This stays open while you do.",
|
|
88
|
+
open: "Open github.com",
|
|
89
|
+
link: "Link",
|
|
90
|
+
retry: "Try again",
|
|
91
|
+
unlink: "Unlink",
|
|
92
|
+
linkedAs: (login) => login === void 0 ? "Linked." : `Linked as ${login}.`,
|
|
93
|
+
unlinkHint: "Forgets the token here. GitHub keeps the authorisation until you revoke it."
|
|
94
|
+
};
|
|
95
|
+
const ISLAND_COPY = {
|
|
96
|
+
title: "Comments",
|
|
97
|
+
settings: "Settings",
|
|
98
|
+
close: "Close the inventory",
|
|
99
|
+
closeGlyph: "✕",
|
|
100
|
+
newComment: "New:",
|
|
101
|
+
empty: "Nothing here under this filter.",
|
|
102
|
+
loading: "Reading the comments on this branch…",
|
|
103
|
+
unread: "The comments could not be read, so this list is not the whole story.",
|
|
104
|
+
showAll: "Show all",
|
|
105
|
+
showLess: "Show less",
|
|
106
|
+
hide: "Hide",
|
|
107
|
+
attachment: "shot",
|
|
108
|
+
attachmentSentence: "A screenshot went with this comment. The panel shows it."
|
|
109
|
+
};
|
|
110
|
+
function openLabel(count) {
|
|
111
|
+
return `${String(count)} open`;
|
|
112
|
+
}
|
|
113
|
+
function triggerLabel(count) {
|
|
114
|
+
return `Open Maple: ${openLabel(count)}`;
|
|
115
|
+
}
|
|
116
|
+
function pickTitle(kind) {
|
|
117
|
+
return `Comment on ${kind} — press t while picking to cycle`;
|
|
118
|
+
}
|
|
119
|
+
function kindPhrase(kind, human) {
|
|
120
|
+
if (human === void 0) return "somewhere on this page";
|
|
121
|
+
if (kind === "text") return `a passage in ${human}`;
|
|
122
|
+
return kind === "region" ? `an area of ${human}` : human;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { ACCOUNT_COPY, CORNER_LABELS, CORNER_ORDER, FILTERS_LABEL, FILTER_LABELS, FILTER_ORDER, ISLAND_COPY, PICK_LABELS, PICK_ORDER, SETTINGS_COPY, TALLY_ORDER, THEME_LABELS, THEME_TITLES, kindPhrase, openLabel, pickTitle, tallyTitle, triggerLabel };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LEAF_ROTATION, LEAF_SOLID, LEAF_VIEW_BOX } from "../marks/leaf.js";
|
|
2
|
+
import { createElement, forwardRef } from "react";
|
|
3
|
+
//#region src/island/leaf.ts
|
|
4
|
+
const Leaf = forwardRef(function Leaf(props, ref) {
|
|
5
|
+
const { size = 15, ...rest } = props;
|
|
6
|
+
return createElement("svg", {
|
|
7
|
+
"aria-hidden": true,
|
|
8
|
+
focusable: false,
|
|
9
|
+
...rest,
|
|
10
|
+
ref,
|
|
11
|
+
width: size,
|
|
12
|
+
height: size,
|
|
13
|
+
viewBox: LEAF_VIEW_BOX
|
|
14
|
+
}, createElement("g", { transform: LEAF_ROTATION }, createElement("path", { d: LEAF_SOLID })));
|
|
15
|
+
});
|
|
16
|
+
//#endregion
|
|
17
|
+
export { Leaf };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PartProps } from "../part.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { Comment } from "@maple-kit/core";
|
|
4
|
+
//#region src/island/list.d.ts
|
|
5
|
+
/** The list, and how one comment is rendered into a row. */
|
|
6
|
+
export interface ListProps extends PartProps {
|
|
7
|
+
/** Called for each comment the filter shows, in render order. */
|
|
8
|
+
readonly children?: (comment: Comment) => ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/** Every comment under the current filter, or the one line that says there are none. */
|
|
11
|
+
export declare const List: import("react").ForwardRefExoticComponent<ListProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
12
|
+
//#endregion
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cx, renderPart } from "../part.js";
|
|
2
|
+
import { FILTER_LABELS, ISLAND_COPY } from "./language.js";
|
|
3
|
+
import { byReason } from "./comments.js";
|
|
4
|
+
import { listId, reasonOf, useIsland } from "./context.js";
|
|
5
|
+
import { Fragment, createElement, forwardRef } from "react";
|
|
6
|
+
import { useComments, useMaple } from "@maple-kit/react";
|
|
7
|
+
//#region src/island/list.ts
|
|
8
|
+
const PART = "<Maple.List>";
|
|
9
|
+
const List = forwardRef(function List(props, ref) {
|
|
10
|
+
const { asChild, children, className, ...rest } = props;
|
|
11
|
+
const island = useIsland(PART);
|
|
12
|
+
const { error, filter, phase } = useMaple();
|
|
13
|
+
const comments = useComments();
|
|
14
|
+
const failed = error?.during === "load";
|
|
15
|
+
const rows = filter === "unpinned" ? byReason(comments, (comment) => reasonOf(island.resolutions, comment.id)) : comments;
|
|
16
|
+
const body = rows.length === 0 ? renderPart("p", false, { className: "mk-empty" }, nothing(phase, failed)) : rows.map((comment) => createElement(Fragment, { key: comment.id }, children?.(comment)));
|
|
17
|
+
return renderPart("div", asChild, {
|
|
18
|
+
role: "tabpanel",
|
|
19
|
+
...rest,
|
|
20
|
+
id: listId(island.contentId),
|
|
21
|
+
"aria-label": FILTER_LABELS[filter],
|
|
22
|
+
className: cx("mk-list", className),
|
|
23
|
+
ref
|
|
24
|
+
}, body);
|
|
25
|
+
});
|
|
26
|
+
function nothing(phase, failed) {
|
|
27
|
+
if (failed) return ISLAND_COPY.unread;
|
|
28
|
+
return phase === "loading" ? ISLAND_COPY.loading : ISLAND_COPY.empty;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { List };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PartProps } from "../part.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { PickKind } from "@maple-kit/core/client";
|
|
4
|
+
//#region src/island/new-comment.d.ts
|
|
5
|
+
/** The bar. Its children are the pick buttons. */
|
|
6
|
+
export interface NewCommentProps extends PartProps {
|
|
7
|
+
readonly children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/** One pick. There is no fourth kind and no variant of these three. */
|
|
10
|
+
export interface PickButtonProps extends PartProps {
|
|
11
|
+
readonly kind: PickKind;
|
|
12
|
+
readonly children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/** `NEW COMMENT`, and the three ways to start one. */
|
|
15
|
+
export declare const NewComment: import("react").ForwardRefExoticComponent<NewCommentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
16
|
+
/** Arming one collapses the island; clicking the armed one disarms it. */
|
|
17
|
+
export declare const PickButton: import("react").ForwardRefExoticComponent<PickButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
18
|
+
//#endregion
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { dataAttributes } from "../data.js";
|
|
2
|
+
import { cx, renderPart } from "../part.js";
|
|
3
|
+
import { ElementIcon } from "../icons/element.js";
|
|
4
|
+
import { RegionIcon } from "../icons/region.js";
|
|
5
|
+
import { TextIcon } from "../icons/text.js";
|
|
6
|
+
import { ISLAND_COPY, PICK_LABELS, pickTitle } from "./language.js";
|
|
7
|
+
import { useIsland } from "./context.js";
|
|
8
|
+
import { createElement, forwardRef } from "react";
|
|
9
|
+
import { useMapleClient, usePicker } from "@maple-kit/react";
|
|
10
|
+
//#region src/island/new-comment.ts
|
|
11
|
+
const PART = "<Maple.PickButton>";
|
|
12
|
+
const NewComment = forwardRef(function NewComment(props, ref) {
|
|
13
|
+
const { asChild, children, className, ...rest } = props;
|
|
14
|
+
return renderPart("div", asChild, {
|
|
15
|
+
...rest,
|
|
16
|
+
className: cx("mk-new", className),
|
|
17
|
+
ref
|
|
18
|
+
}, [renderPart("span", false, {
|
|
19
|
+
key: "label",
|
|
20
|
+
className: "mk-new-label"
|
|
21
|
+
}, ISLAND_COPY.newComment), renderPart("div", false, {
|
|
22
|
+
key: "picks",
|
|
23
|
+
className: "mk-picks"
|
|
24
|
+
}, children)]);
|
|
25
|
+
});
|
|
26
|
+
const PickButton = forwardRef(function PickButton(props, ref) {
|
|
27
|
+
const { asChild, children, className, kind, ...rest } = props;
|
|
28
|
+
const island = useIsland(PART);
|
|
29
|
+
const client = useMapleClient();
|
|
30
|
+
const pick = usePicker();
|
|
31
|
+
const armed = pick.armed && pick.kind === kind;
|
|
32
|
+
const onClick = () => {
|
|
33
|
+
if (armed) {
|
|
34
|
+
client.disarm();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
client.arm(kind);
|
|
38
|
+
island.setOpen(false);
|
|
39
|
+
};
|
|
40
|
+
return renderPart("button", asChild, {
|
|
41
|
+
type: "button",
|
|
42
|
+
...dataAttributes({ armed }),
|
|
43
|
+
...rest,
|
|
44
|
+
"aria-pressed": armed,
|
|
45
|
+
className: cx("mk-pick mk-hit", className),
|
|
46
|
+
title: pickTitle(kind),
|
|
47
|
+
onClick,
|
|
48
|
+
ref
|
|
49
|
+
}, children ?? [createElement(icon(kind), { key: "icon" }), PICK_LABELS[kind]]);
|
|
50
|
+
});
|
|
51
|
+
function icon(kind) {
|
|
52
|
+
if (kind === "text") return TextIcon;
|
|
53
|
+
return kind === "region" ? RegionIcon : ElementIcon;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { NewComment, PickButton };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PartProps } from "../part.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/island/settings.d.ts
|
|
4
|
+
/** The control. Its children replace the icon inside it. */
|
|
5
|
+
export interface SettingsProps extends PartProps {
|
|
6
|
+
readonly children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/** The sliders control, and the panel it opens under the header. */
|
|
9
|
+
export declare const Settings: import("react").ForwardRefExoticComponent<SettingsProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { cx, renderPart } from "../part.js";
|
|
2
|
+
import { CORNER_LABELS, CORNER_ORDER, ISLAND_COPY, SETTINGS_COPY, THEME_LABELS, THEME_TITLES } from "./language.js";
|
|
3
|
+
import { Account } from "./account.js";
|
|
4
|
+
import { useIsland } from "./context.js";
|
|
5
|
+
import { CogIcon } from "../icons/cog.js";
|
|
6
|
+
import { createElement, forwardRef, useId } from "react";
|
|
7
|
+
import { THEME_PREFERENCES } from "@maple-kit/core/client";
|
|
8
|
+
import { useMaple, useMapleClient } from "@maple-kit/react";
|
|
9
|
+
//#region src/island/settings.ts
|
|
10
|
+
const PART = "<Maple.Settings>";
|
|
11
|
+
const Settings = forwardRef(function Settings(props, ref) {
|
|
12
|
+
const { asChild, children, className, ...rest } = props;
|
|
13
|
+
const { settingsOpen: open, setSettingsOpen: setOpen } = useIsland(PART);
|
|
14
|
+
const panelId = useId();
|
|
15
|
+
const button = renderPart("button", asChild, {
|
|
16
|
+
type: "button",
|
|
17
|
+
...rest,
|
|
18
|
+
"aria-controls": panelId,
|
|
19
|
+
"aria-expanded": open,
|
|
20
|
+
"aria-label": ISLAND_COPY.settings,
|
|
21
|
+
className: cx("mk-iconbtn mk-hit", className),
|
|
22
|
+
onClick: () => setOpen(!open),
|
|
23
|
+
ref
|
|
24
|
+
}, children ?? createElement(CogIcon));
|
|
25
|
+
return createElement("span", { className: "mk-settings-anchor" }, button, open ? createElement(Panel, {
|
|
26
|
+
id: panelId,
|
|
27
|
+
onClose: () => setOpen(false)
|
|
28
|
+
}) : null);
|
|
29
|
+
});
|
|
30
|
+
function Panel(props) {
|
|
31
|
+
const client = useMapleClient();
|
|
32
|
+
const { position, showResolved, tagged, themePreference } = useMaple();
|
|
33
|
+
const island = useIsland(PART);
|
|
34
|
+
const onKeyDown = (event) => {
|
|
35
|
+
if (event.key === "Escape") props.onClose();
|
|
36
|
+
};
|
|
37
|
+
const onBlur = (event) => {
|
|
38
|
+
if (!event.currentTarget.contains(event.relatedTarget)) props.onClose();
|
|
39
|
+
};
|
|
40
|
+
return createElement("div", {
|
|
41
|
+
id: props.id,
|
|
42
|
+
className: "mk-settings",
|
|
43
|
+
role: "group",
|
|
44
|
+
onBlur,
|
|
45
|
+
onKeyDown
|
|
46
|
+
}, createElement(Account, null), tagged ? null : createElement(Untagged, null), createElement(Theme, {
|
|
47
|
+
value: themePreference,
|
|
48
|
+
onPick: (theme) => client.setTheme(theme)
|
|
49
|
+
}), createElement(Corners, {
|
|
50
|
+
value: position,
|
|
51
|
+
onPick: (corner) => client.setPosition(corner)
|
|
52
|
+
}), createElement(Setting, {
|
|
53
|
+
checked: !showResolved,
|
|
54
|
+
copy: SETTINGS_COPY.hideResolved,
|
|
55
|
+
onChange: (on) => client.setShowResolved(!on)
|
|
56
|
+
}), createElement(Setting, {
|
|
57
|
+
checked: island.developer,
|
|
58
|
+
copy: SETTINGS_COPY.developer,
|
|
59
|
+
onChange: island.setDeveloper
|
|
60
|
+
}), createElement(Dismiss, { onHide: () => client.setHidden(true) }));
|
|
61
|
+
}
|
|
62
|
+
function Untagged() {
|
|
63
|
+
const copy = SETTINGS_COPY.untagged;
|
|
64
|
+
return createElement("div", {
|
|
65
|
+
className: "mk-setting",
|
|
66
|
+
"data-mk-maple": "true"
|
|
67
|
+
}, createElement("span", null, createElement("span", { className: "mk-setting-name" }, copy.name), createElement("span", { className: "mk-setting-hint" }, copy.hint)));
|
|
68
|
+
}
|
|
69
|
+
function Theme(props) {
|
|
70
|
+
const labelId = useId();
|
|
71
|
+
const copy = SETTINGS_COPY.theme;
|
|
72
|
+
return createElement("div", { className: "mk-setting" }, createElement("span", null, createElement("span", {
|
|
73
|
+
className: "mk-setting-name",
|
|
74
|
+
id: labelId
|
|
75
|
+
}, copy.name), createElement("span", { className: "mk-setting-hint" }, copy.hint)), createElement("span", {
|
|
76
|
+
className: "mk-seg",
|
|
77
|
+
role: "radiogroup",
|
|
78
|
+
"aria-labelledby": labelId
|
|
79
|
+
}, ...THEME_PREFERENCES.map((theme) => createElement("button", {
|
|
80
|
+
key: theme,
|
|
81
|
+
type: "button",
|
|
82
|
+
role: "radio",
|
|
83
|
+
"aria-checked": theme === props.value,
|
|
84
|
+
className: "mk-seg-one",
|
|
85
|
+
title: THEME_TITLES[theme],
|
|
86
|
+
onClick: () => props.onPick(theme)
|
|
87
|
+
}, THEME_LABELS[theme]))));
|
|
88
|
+
}
|
|
89
|
+
function Corners(props) {
|
|
90
|
+
const labelId = useId();
|
|
91
|
+
const copy = SETTINGS_COPY.position;
|
|
92
|
+
return createElement("div", { className: "mk-setting" }, createElement("span", null, createElement("span", {
|
|
93
|
+
className: "mk-setting-name",
|
|
94
|
+
id: labelId
|
|
95
|
+
}, copy.name), createElement("span", { className: "mk-setting-hint" }, copy.hint)), createElement("span", {
|
|
96
|
+
className: "mk-corners",
|
|
97
|
+
role: "radiogroup",
|
|
98
|
+
"aria-labelledby": labelId
|
|
99
|
+
}, ...CORNER_ORDER.map((corner) => createElement("button", {
|
|
100
|
+
key: corner,
|
|
101
|
+
type: "button",
|
|
102
|
+
role: "radio",
|
|
103
|
+
"aria-checked": corner === props.value,
|
|
104
|
+
"aria-label": CORNER_LABELS[corner],
|
|
105
|
+
title: CORNER_LABELS[corner],
|
|
106
|
+
className: "mk-corner",
|
|
107
|
+
"data-mk-corner": corner,
|
|
108
|
+
onClick: () => props.onPick(corner)
|
|
109
|
+
}))));
|
|
110
|
+
}
|
|
111
|
+
function Dismiss(props) {
|
|
112
|
+
const copy = SETTINGS_COPY.hidden;
|
|
113
|
+
return createElement("div", { className: "mk-setting" }, createElement("span", null, createElement("span", { className: "mk-setting-name" }, copy.name), createElement("span", { className: "mk-setting-hint" }, copy.hint)), createElement("button", {
|
|
114
|
+
type: "button",
|
|
115
|
+
className: "mk-more",
|
|
116
|
+
onClick: props.onHide
|
|
117
|
+
}, ISLAND_COPY.hide));
|
|
118
|
+
}
|
|
119
|
+
function Setting(props) {
|
|
120
|
+
const labelId = useId();
|
|
121
|
+
return createElement("div", { className: "mk-setting" }, createElement("span", null, createElement("span", {
|
|
122
|
+
className: "mk-setting-name",
|
|
123
|
+
id: labelId
|
|
124
|
+
}, props.copy.name), createElement("span", { className: "mk-setting-hint" }, props.copy.hint)), createElement("button", {
|
|
125
|
+
type: "button",
|
|
126
|
+
role: "switch",
|
|
127
|
+
"aria-checked": props.checked,
|
|
128
|
+
"aria-labelledby": labelId,
|
|
129
|
+
className: "mk-switch",
|
|
130
|
+
onClick: () => props.onChange(!props.checked)
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { Settings };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/island/stagger.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* How many rows stagger before the rest arrive together.
|
|
4
|
+
*
|
|
5
|
+
* The step and the cap are `--mk-stagger-step` and `--mk-stagger-cap`; this is
|
|
6
|
+
* the cap divided by the step, so a list of thirty comments does not take a
|
|
7
|
+
* second to appear. It is a count rather than a duration, which is why it is
|
|
8
|
+
* here and not in the token table.
|
|
9
|
+
*/
|
|
10
|
+
/** Six rows at a 40ms step, which is the 240ms cap. */
|
|
11
|
+
export declare const STAGGER_ROWS = 6;
|
|
12
|
+
//#endregion
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/island/time.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* How long ago a comment was written, in the shortest true form.
|
|
4
|
+
*
|
|
5
|
+
* `Intl.RelativeTimeFormat` is in every browser the overlay supports and costs
|
|
6
|
+
* the bundle nothing, so this is a unit choice and a rounding rule rather than
|
|
7
|
+
* a date library.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The same instant in the reader's own zone, for the tooltip behind the
|
|
11
|
+
* relative one. "3h ago" is what a row is scanned for; which afternoon it was
|
|
12
|
+
* is what someone asks a minute later, and the row should not have to say it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function absoluteTime(iso: string): string;
|
|
15
|
+
/** "3h ago", or "now" for anything inside the last minute. */
|
|
16
|
+
export declare function relativeTime(iso: string, now: number): string;
|
|
17
|
+
//#endregion
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/island/time.ts
|
|
2
|
+
const UNITS = [
|
|
3
|
+
["year", 31536e3],
|
|
4
|
+
["month", 2592e3],
|
|
5
|
+
["week", 604800],
|
|
6
|
+
["day", 86400],
|
|
7
|
+
["hour", 3600],
|
|
8
|
+
["minute", 60]
|
|
9
|
+
];
|
|
10
|
+
const FORMAT = new Intl.RelativeTimeFormat(void 0, { style: "narrow" });
|
|
11
|
+
const EXACT = new Intl.DateTimeFormat(void 0, {
|
|
12
|
+
dateStyle: "medium",
|
|
13
|
+
timeStyle: "short"
|
|
14
|
+
});
|
|
15
|
+
function absoluteTime(iso) {
|
|
16
|
+
const at = Date.parse(iso);
|
|
17
|
+
return Number.isFinite(at) ? EXACT.format(at) : "";
|
|
18
|
+
}
|
|
19
|
+
function relativeTime(iso, now) {
|
|
20
|
+
const seconds = (Date.parse(iso) - now) / 1e3;
|
|
21
|
+
if (!Number.isFinite(seconds)) return "";
|
|
22
|
+
for (const [unit, size] of UNITS) {
|
|
23
|
+
const value = seconds / size;
|
|
24
|
+
if (Math.abs(value) >= 1) return FORMAT.format(Math.round(value), unit);
|
|
25
|
+
}
|
|
26
|
+
return "now";
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { absoluteTime, relativeTime };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PartProps } from "../part.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/island/trigger.d.ts
|
|
4
|
+
/** The pill. Its own children replace the leaf and the count. */
|
|
5
|
+
export interface IslandTriggerProps extends PartProps {
|
|
6
|
+
readonly children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/** `8 open`, and the hit area a thumb needs around it. */
|
|
9
|
+
export declare const IslandTrigger: import("react").ForwardRefExoticComponent<IslandTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { dataAttributes } from "../data.js";
|
|
2
|
+
import { cx, renderPart } from "../part.js";
|
|
3
|
+
import { openLabel, triggerLabel } from "./language.js";
|
|
4
|
+
import { useIsland } from "./context.js";
|
|
5
|
+
import { Leaf } from "./leaf.js";
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import { useMaple } from "@maple-kit/react";
|
|
8
|
+
//#region src/island/trigger.ts
|
|
9
|
+
const PART = "<Maple.IslandTrigger>";
|
|
10
|
+
const IslandTrigger = forwardRef(function IslandTrigger(props, ref) {
|
|
11
|
+
const { asChild, children, className, ...rest } = props;
|
|
12
|
+
const island = useIsland(PART);
|
|
13
|
+
const { openCount, pick } = useMaple();
|
|
14
|
+
return renderPart("button", asChild, {
|
|
15
|
+
type: "button",
|
|
16
|
+
...dataAttributes({ armed: pick.armed }),
|
|
17
|
+
...rest,
|
|
18
|
+
"aria-controls": island.contentId,
|
|
19
|
+
"aria-expanded": island.phase !== "closed",
|
|
20
|
+
"aria-label": triggerLabel(openCount),
|
|
21
|
+
className: cx("mk-pill mk-hit", className),
|
|
22
|
+
onClick: () => {
|
|
23
|
+
if (!island.drag.moved()) island.setOpen(island.phase === "closed");
|
|
24
|
+
},
|
|
25
|
+
onPointerDown: island.drag.onPointerDown,
|
|
26
|
+
onPointerMove: island.drag.onPointerMove,
|
|
27
|
+
onPointerUp: island.drag.onPointerUp,
|
|
28
|
+
ref
|
|
29
|
+
}, children ?? [renderPart(Leaf, false, {
|
|
30
|
+
key: "leaf",
|
|
31
|
+
size: 15,
|
|
32
|
+
className: "mk-logo-leaf"
|
|
33
|
+
}), renderPart("span", false, {
|
|
34
|
+
key: "count",
|
|
35
|
+
className: "mk-num"
|
|
36
|
+
}, openLabel(openCount))]);
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { IslandTrigger };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OrphanReason, Rung } from "@maple-kit/core/anchor";
|
|
2
|
+
import { CommentStatus, IdentityProvenance } from "@maple-kit/core";
|
|
3
|
+
//#region src/language.d.ts
|
|
4
|
+
/** The four statuses, in the words the island, the mark and the composer use. */
|
|
5
|
+
export declare const STATUS_LABELS: Readonly<Record<CommentStatus, string>>;
|
|
6
|
+
/**
|
|
7
|
+
* What each status means. The chip carries two words and the panel carries
|
|
8
|
+
* this: a reviewer who has not read the docs is looking at a word, not at a
|
|
9
|
+
* state machine.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STATUS_SENTENCES: Readonly<Record<CommentStatus, string>>;
|
|
12
|
+
/**
|
|
13
|
+
* How much an author's name is worth, said as the dot that draws it. The fill
|
|
14
|
+
* is the whole of this signal and no word beside it repeats it, so the sentence
|
|
15
|
+
* names the shape a reader is looking at.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PROVENANCE_SENTENCES: Readonly<Record<IdentityProvenance, string>>;
|
|
18
|
+
/**
|
|
19
|
+
* How a rung reads inside a sentence. It is never shown as a field name: the
|
|
20
|
+
* number is the value and this says what the number is about.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RUNG_LABELS: Readonly<Record<Rung, string>>;
|
|
23
|
+
/** What a percentage reads as, for anyone not looking at it. */
|
|
24
|
+
export declare function rungLabel(percent: number): string;
|
|
25
|
+
/** Two words each. The sentence underneath belongs in the tooltip, not the row. */
|
|
26
|
+
export declare const ORPHAN_LABELS: Readonly<Record<OrphanReason, string>>;
|
|
27
|
+
/** What the chip's tooltip says after its two words. */
|
|
28
|
+
export declare const ORPHAN_SENTENCES: Readonly<Record<OrphanReason, string>>;
|
|
29
|
+
/** The order the unpinned tab groups its rows in. */
|
|
30
|
+
export declare const ORPHAN_ORDER: readonly OrphanReason[];
|
|
31
|
+
/** The chip's full tooltip: the two words, then the sentence. */
|
|
32
|
+
export declare function orphanTitle(reason: OrphanReason, tried?: readonly Rung[]): string;
|
|
33
|
+
/** The panel's labels, and the sentence under each of the ones worth one. */
|
|
34
|
+
export declare const DETAIL_COPY: {
|
|
35
|
+
readonly author: "Written by";
|
|
36
|
+
readonly status: "Status";
|
|
37
|
+
readonly rung: "Found again by";
|
|
38
|
+
readonly rungNote: "after a redeploy Maple re-finds it that way, and a lower rung is worth less";
|
|
39
|
+
readonly lost: "Nothing to pin to";
|
|
40
|
+
readonly area: "Area";
|
|
41
|
+
readonly areaNote: "of the box it was drawn in, which is what moves it when the layout does";
|
|
42
|
+
readonly source: "Source";
|
|
43
|
+
readonly selector: "CSS path";
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
package/dist/language.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/language.ts
|
|
2
|
+
const STATUS_LABELS = {
|
|
3
|
+
open: "Open",
|
|
4
|
+
resolved: "Resolved",
|
|
5
|
+
needs_reverify: "Needs re-verify",
|
|
6
|
+
orphaned: "Unpinned"
|
|
7
|
+
};
|
|
8
|
+
const STATUS_SENTENCES = {
|
|
9
|
+
open: "nobody has claimed to have addressed this yet",
|
|
10
|
+
resolved: "an agent claimed a commit for it; re-check it and say whether it is done",
|
|
11
|
+
needs_reverify: "it was resolved, and the page has changed under it since",
|
|
12
|
+
orphaned: "the page has nowhere left to put it; the comment is kept, only its place is lost"
|
|
13
|
+
};
|
|
14
|
+
const PROVENANCE_SENTENCES = {
|
|
15
|
+
server: "a filled dot: the application's own session verified who this is",
|
|
16
|
+
client: "a half-filled dot: the application said who this is, and nothing checked it",
|
|
17
|
+
guest: "a hollow dot: nobody verified this, they typed a name into Maple"
|
|
18
|
+
};
|
|
19
|
+
const RUNG_LABELS = {
|
|
20
|
+
key: "the app's own key",
|
|
21
|
+
source: "the source line",
|
|
22
|
+
component: "the component name",
|
|
23
|
+
quote: "the quoted text",
|
|
24
|
+
selector: "a CSS path"
|
|
25
|
+
};
|
|
26
|
+
function rungLabel(percent) {
|
|
27
|
+
return `${String(percent)}%`;
|
|
28
|
+
}
|
|
29
|
+
const ORPHAN_LABELS = {
|
|
30
|
+
empty: "No anchor",
|
|
31
|
+
missing: "Nothing matches",
|
|
32
|
+
ambiguous: "Several matches",
|
|
33
|
+
changed: "Text changed"
|
|
34
|
+
};
|
|
35
|
+
const ORPHAN_SENTENCES = {
|
|
36
|
+
empty: "Written before the build tagged anything, so there was never anything to search for.",
|
|
37
|
+
missing: "Every rung was tried. The element is gone, or this is a different route.",
|
|
38
|
+
ambiguous: "More than one element answers to what was recorded, and nothing tells them apart.",
|
|
39
|
+
changed: "The passage is still on the page, but edited past the point where the match can be trusted."
|
|
40
|
+
};
|
|
41
|
+
const ORPHAN_ORDER = [
|
|
42
|
+
"missing",
|
|
43
|
+
"changed",
|
|
44
|
+
"ambiguous",
|
|
45
|
+
"empty"
|
|
46
|
+
];
|
|
47
|
+
function orphanTitle(reason, tried = []) {
|
|
48
|
+
const ladder = tried.length === 0 ? "" : ` Tried: ${tried.map(rungWord).join(" → ")}.`;
|
|
49
|
+
return `${ORPHAN_LABELS[reason]}. ${ORPHAN_SENTENCES[reason]}${ladder}`;
|
|
50
|
+
}
|
|
51
|
+
function rungWord(rung) {
|
|
52
|
+
return RUNG_LABELS[rung];
|
|
53
|
+
}
|
|
54
|
+
const DETAIL_COPY = {
|
|
55
|
+
author: "Written by",
|
|
56
|
+
status: "Status",
|
|
57
|
+
rung: "Found again by",
|
|
58
|
+
rungNote: "after a redeploy Maple re-finds it that way, and a lower rung is worth less",
|
|
59
|
+
lost: "Nothing to pin to",
|
|
60
|
+
area: "Area",
|
|
61
|
+
areaNote: "of the box it was drawn in, which is what moves it when the layout does",
|
|
62
|
+
source: "Source",
|
|
63
|
+
selector: "CSS path"
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { DETAIL_COPY, ORPHAN_LABELS, ORPHAN_ORDER, ORPHAN_SENTENCES, PROVENANCE_SENTENCES, RUNG_LABELS, STATUS_LABELS, STATUS_SENTENCES, orphanTitle, rungLabel };
|
package/dist/maple.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MapleAttachmentsProps } from "./composer/attachments.js";
|
|
2
|
+
import { LeaveAsk } from "./composer/leave.js";
|
|
3
|
+
import "./composer/index.js";
|
|
4
|
+
import { MapleRootProps } from "./root.js";
|
|
5
|
+
//#region src/maple.d.ts
|
|
6
|
+
/** Everything the root takes, plus the few choices the composition itself has. */
|
|
7
|
+
export interface MapleProps extends MapleRootProps {
|
|
8
|
+
/** Start with the inventory open. Collapsed to its pill unless set. */
|
|
9
|
+
readonly defaultOpen?: boolean;
|
|
10
|
+
/** The bar naming the gesture while a pick is armed. On unless set false. */
|
|
11
|
+
readonly hint?: boolean;
|
|
12
|
+
/** Asked before a link takes the page away from an unsent comment. */
|
|
13
|
+
readonly leave?: LeaveAsk;
|
|
14
|
+
/**
|
|
15
|
+
* Where images go and are read back from. Seams, not options: core has no
|
|
16
|
+
* blob route, and without `resolve` a kept shot is a sentence about one.
|
|
17
|
+
*/
|
|
18
|
+
readonly attachments?: Pick<MapleAttachmentsProps, "resolve" | "upload">;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The whole reviewer interface: the marks on the page, the picker, the
|
|
22
|
+
* inventory and the composer, over one shadow root and one controller.
|
|
23
|
+
*/
|
|
24
|
+
export declare const Maple: import("react").ForwardRefExoticComponent<MapleProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
25
|
+
//#endregion
|