wx-svelte-comments 2.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.
- package/license.txt +21 -0
- package/package.json +54 -0
- package/readme.md +51 -0
- package/src/components/Comments.svelte +21 -0
- package/src/components/Layout.svelte +212 -0
- package/src/components/Messages/Bubbles.svelte +135 -0
- package/src/components/Messages/Common.svelte +27 -0
- package/src/components/Messages/Flow.svelte +79 -0
- package/src/components/Messages/MarkdownPlain.svelte +7 -0
- package/src/components/Messages/TextPlain.svelte +5 -0
- package/src/components/Messages/UserIcon.svelte +77 -0
- package/src/components/Messages/index.js +14 -0
- package/src/components/Messages.svelte +51 -0
- package/src/components/TextArea.svelte +81 -0
- package/src/helpers/colors.js +58 -0
- package/src/index.js +5 -0
- package/src/libs/lima.es.js +337 -0
- package/src/themes/Willow.svelte +58 -0
- package/src/themes/WillowDark.svelte +56 -0
- package/whatsnew.md +15 -0
package/license.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 XB Software Sp. z o.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wx-svelte-comments",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Simple Svelte component for adding a comments section on a page",
|
|
5
|
+
"productTag": "comments",
|
|
6
|
+
"productTrial": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"build:dist": "vite build --mode dist",
|
|
11
|
+
"build:tests": "vite build --mode test",
|
|
12
|
+
"lint": "yarn eslint ./demos ./src",
|
|
13
|
+
"start": "vite --open",
|
|
14
|
+
"start:tests": "vite --open=/tests/ --host 0.0.0.0 --port 5100 --mode test",
|
|
15
|
+
"test": "true",
|
|
16
|
+
"test:cypress": "cypress run -P ./ --config \"baseUrl=http://localhost:5100/tests\""
|
|
17
|
+
},
|
|
18
|
+
"svelte": "src/index.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"svelte": "./src/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/svar-widgets/comments.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://forum.svar.dev"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://svar.dev/svelte/comments/",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"wx-comments-locales": "2.0.1",
|
|
36
|
+
"wx-lib-data-provider": "1.6.0",
|
|
37
|
+
"wx-lib-dom": "0.7.1",
|
|
38
|
+
"wx-lib-state": "1.9.0",
|
|
39
|
+
"wx-svelte-core": "2.0.1",
|
|
40
|
+
"wx-svelte-menu": "2.0.1"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"src",
|
|
44
|
+
"readme.md",
|
|
45
|
+
"whatsnew.md",
|
|
46
|
+
"license.txt"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"svelte",
|
|
50
|
+
"comments",
|
|
51
|
+
"ui component",
|
|
52
|
+
"comments section"
|
|
53
|
+
]
|
|
54
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# SVAR Svelte Comments
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/wx-svelte-comments)
|
|
6
|
+
[](https://github.com/svar-widgets/comments/blob/main/license.txt)
|
|
7
|
+
[](https://www.npmjs.com/package/wx-svelte-comments)
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div align="center">
|
|
12
|
+
|
|
13
|
+
[Documentation](https://docs.svar.dev/svelte/core/comments/) • [Demos](https://docs.svar.dev/svelte/core/samples-comments/#/base/willow)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
A Svelte UI component for creating a comments section. Supports adding, editing, and deleting comments with ease.
|
|
18
|
+
|
|
19
|
+
### How to Use
|
|
20
|
+
|
|
21
|
+
To use the widget, simply import the package and include the component in your Svelte file:
|
|
22
|
+
|
|
23
|
+
```svelte
|
|
24
|
+
<script>
|
|
25
|
+
import { Comments } from "wx-svelte-comments";
|
|
26
|
+
|
|
27
|
+
const value = [];
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<Comments {value} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### How to Modify
|
|
34
|
+
|
|
35
|
+
Typically, you don't need to modify the code. However, if you wish to do so, follow these steps:
|
|
36
|
+
|
|
37
|
+
1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work
|
|
38
|
+
2. Start the project in development mode with `yarn start`
|
|
39
|
+
|
|
40
|
+
### Run Tests
|
|
41
|
+
|
|
42
|
+
To run the test:
|
|
43
|
+
|
|
44
|
+
1. Start the test examples with:
|
|
45
|
+
```sh
|
|
46
|
+
yarn start:tests
|
|
47
|
+
```
|
|
48
|
+
2. In a separate console, run the end-to-end tests with:
|
|
49
|
+
```sh
|
|
50
|
+
yarn test:cypress
|
|
51
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Layout from "./Layout.svelte";
|
|
3
|
+
import { Locale } from "wx-svelte-core";
|
|
4
|
+
import { en } from "wx-comments-locales";
|
|
5
|
+
|
|
6
|
+
const { ondata, onchange, value, ...props } = $props();
|
|
7
|
+
const finalData = $derived(ondata && value ? ondata(value) : value);
|
|
8
|
+
|
|
9
|
+
const handleOnchange = e => {
|
|
10
|
+
e.originalValue = value;
|
|
11
|
+
return onchange && onchange(e);
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Locale words={en} optional={true}>
|
|
16
|
+
{#await finalData}
|
|
17
|
+
<Layout data={[]} {...props} onchange={handleOnchange} />
|
|
18
|
+
{:then data}
|
|
19
|
+
<Layout {data} {...props} onchange={handleOnchange} />
|
|
20
|
+
{/await}
|
|
21
|
+
</Locale>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext, setContext } from "svelte";
|
|
3
|
+
import Messages from "./Messages.svelte";
|
|
4
|
+
import TextArea from "./TextArea.svelte";
|
|
5
|
+
import { ActionMenu } from "wx-svelte-menu";
|
|
6
|
+
import { uid } from "wx-lib-state";
|
|
7
|
+
import { dateToString } from "wx-lib-dom";
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
onaction,
|
|
11
|
+
onchange,
|
|
12
|
+
readonly = false,
|
|
13
|
+
render = "flow",
|
|
14
|
+
format = "text",
|
|
15
|
+
users: rawUsers,
|
|
16
|
+
data: rawData,
|
|
17
|
+
activeUser,
|
|
18
|
+
focus = false,
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
let edit = $state(null);
|
|
22
|
+
const lang = getContext("wx-i18n");
|
|
23
|
+
const { calendar, comments, formats } = lang.getRaw();
|
|
24
|
+
const _ = lang.getGroup("comments");
|
|
25
|
+
|
|
26
|
+
const dateFormatter = dateToString(
|
|
27
|
+
comments.dateFormat || formats.dateFormat,
|
|
28
|
+
calendar
|
|
29
|
+
);
|
|
30
|
+
setContext("wx-comments-format", {
|
|
31
|
+
dateStr: date => dateFormatter(date),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const unknownUser = { id: 0, name: _("Unknown"), color: "hsl(0, 0%, 85%)" };
|
|
35
|
+
|
|
36
|
+
const users = $derived.by(() => {
|
|
37
|
+
if (!rawUsers) return [];
|
|
38
|
+
return rawUsers.map(u => {
|
|
39
|
+
if (!u.color)
|
|
40
|
+
return { ...u, color: "hsl(" + getColor(u.id + u.name) + ", 100%, 85%)" };
|
|
41
|
+
return u;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const author = $derived.by(() => {
|
|
46
|
+
if (typeof activeUser === "object") return activeUser;
|
|
47
|
+
const a = users.find(u => u.id === activeUser) || unknownUser;
|
|
48
|
+
if (a) return a;
|
|
49
|
+
return { id: activeUser || -1, name: _("Me"), color: "hsl(225, 76%, 67%)" };
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const data = $derived.by(() => {
|
|
53
|
+
if (!rawData) return [];
|
|
54
|
+
|
|
55
|
+
// data with user objects
|
|
56
|
+
return rawData.map(d => {
|
|
57
|
+
if (typeof d.author === "object") return d;
|
|
58
|
+
|
|
59
|
+
const user = users ? users.find(u => u.id === d.user) : null;
|
|
60
|
+
return {
|
|
61
|
+
...d,
|
|
62
|
+
author: user || unknownUser,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function getColor(name) {
|
|
68
|
+
let hash = 0;
|
|
69
|
+
for (let i = 0; i < name.length; i++) {
|
|
70
|
+
// eslint-disable-next-line no-bitwise
|
|
71
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
72
|
+
}
|
|
73
|
+
return (hash % 60) * 6;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function add(content) {
|
|
77
|
+
const comment = {
|
|
78
|
+
id: uid(),
|
|
79
|
+
content,
|
|
80
|
+
author,
|
|
81
|
+
user: author.id,
|
|
82
|
+
date: new Date(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
rawData = [...data, comment];
|
|
86
|
+
if (onchange){
|
|
87
|
+
const res = onchange({ value:rawData, comment, action: "add" });
|
|
88
|
+
if (res && typeof res === "object"){
|
|
89
|
+
if (res.then){
|
|
90
|
+
res.then((data) => {
|
|
91
|
+
updateAfter(comment.id, data);
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
updateAfter(comment.id, res);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function updateAfter(id, data){
|
|
101
|
+
const index = rawData.findIndex(d => d.id === id);
|
|
102
|
+
|
|
103
|
+
const copy = [ ...rawData ];
|
|
104
|
+
copy[index] = { ...rawData[index], ...data };
|
|
105
|
+
rawData = copy;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function remove(id) {
|
|
109
|
+
if (edit === id) edit = null;
|
|
110
|
+
|
|
111
|
+
rawData = rawData.filter(d => d.id !== id);
|
|
112
|
+
onchange && onchange({ value: rawData, id, action: "delete" });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function update(id, content) {
|
|
116
|
+
let comment;
|
|
117
|
+
rawData = rawData.map(d => {
|
|
118
|
+
if (d.id === id) {
|
|
119
|
+
comment = { ...d, content };
|
|
120
|
+
return comment;
|
|
121
|
+
} else return d;
|
|
122
|
+
});
|
|
123
|
+
edit = null;
|
|
124
|
+
|
|
125
|
+
onchange && onchange({ value: rawData, id, action: "update", comment });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function cancelUpdate() {
|
|
129
|
+
edit = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function handleActionMenu(ev) {
|
|
133
|
+
const { context, action } = ev;
|
|
134
|
+
|
|
135
|
+
if (!action) return;
|
|
136
|
+
|
|
137
|
+
onaction && onaction({ action: "menu-clicked" });
|
|
138
|
+
|
|
139
|
+
switch (action.id) {
|
|
140
|
+
case "edit-comment":
|
|
141
|
+
edit = context;
|
|
142
|
+
break;
|
|
143
|
+
case "delete-comment":
|
|
144
|
+
remove(context);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const menuItems = [
|
|
150
|
+
{
|
|
151
|
+
id: "edit-comment",
|
|
152
|
+
text: _("Edit"),
|
|
153
|
+
icon: "wxi-edit-outline",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "delete-comment",
|
|
157
|
+
text: _("Delete"),
|
|
158
|
+
icon: "wxi-delete-outline",
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
let menu = $state(null);
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
166
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
167
|
+
<div class="wx-comments-list">
|
|
168
|
+
<ActionMenu
|
|
169
|
+
at={"bottom"}
|
|
170
|
+
bind:this={menu}
|
|
171
|
+
options={menuItems}
|
|
172
|
+
resolver={item => item}
|
|
173
|
+
dataKey="commentMenuId"
|
|
174
|
+
onclick={handleActionMenu}
|
|
175
|
+
/>
|
|
176
|
+
<div class="wx-list" onclick={menu.show}>
|
|
177
|
+
<Messages
|
|
178
|
+
{author}
|
|
179
|
+
{render}
|
|
180
|
+
{data}
|
|
181
|
+
{format}
|
|
182
|
+
{edit}
|
|
183
|
+
onpost={ev => update(edit, ev.value)}
|
|
184
|
+
oncancel={cancelUpdate}
|
|
185
|
+
></Messages>
|
|
186
|
+
</div>
|
|
187
|
+
{#if !readonly && !edit}
|
|
188
|
+
<TextArea
|
|
189
|
+
{author}
|
|
190
|
+
flow={render === "flow"}
|
|
191
|
+
{focus}
|
|
192
|
+
onpost={ev => add(ev.value)}
|
|
193
|
+
buttonLabel={_("Add")}
|
|
194
|
+
/>
|
|
195
|
+
{/if}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<style>
|
|
199
|
+
.wx-comments-list {
|
|
200
|
+
height: 100%;
|
|
201
|
+
width: 100%;
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
}
|
|
205
|
+
.wx-list {
|
|
206
|
+
display: flex;
|
|
207
|
+
flex-direction: column;
|
|
208
|
+
margin-bottom: 4px;
|
|
209
|
+
overflow-y: auto;
|
|
210
|
+
flex: 1;
|
|
211
|
+
}
|
|
212
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from "svelte";
|
|
3
|
+
import UserIcon from "./UserIcon.svelte";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
owned,
|
|
7
|
+
author,
|
|
8
|
+
date,
|
|
9
|
+
edit,
|
|
10
|
+
children,
|
|
11
|
+
} = $props();
|
|
12
|
+
|
|
13
|
+
const dateFormatter = getContext("wx-comments-format").dateStr;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="wx-bubble" class:wx-owned={owned}>
|
|
17
|
+
<div class="wx-bubble-wrapper">
|
|
18
|
+
<div class="wx-avatar">
|
|
19
|
+
<UserIcon data={author} />
|
|
20
|
+
</div>
|
|
21
|
+
<div class="wx-main-bubble">
|
|
22
|
+
<span class="wx-author-name">{author.name}</span>
|
|
23
|
+
{#if owned}
|
|
24
|
+
<div class="wx-agent-message">
|
|
25
|
+
<div class="wx-message">
|
|
26
|
+
{@render children()}
|
|
27
|
+
{#if edit !== owned}<div class="wx-comment-date">
|
|
28
|
+
{dateFormatter(date)}
|
|
29
|
+
</div>{/if}
|
|
30
|
+
</div>
|
|
31
|
+
<div class="wx-menu-icon" data-comment-menu-id={owned}>
|
|
32
|
+
<i class="wx-icon wxi-dots-v"></i>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
{:else}
|
|
36
|
+
<div class="wx-message">
|
|
37
|
+
{@render children()}
|
|
38
|
+
<div class="wx-comment-date">{dateFormatter(date)}</div>
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
.wx-bubble {
|
|
47
|
+
width: 100%;
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: end;
|
|
50
|
+
}
|
|
51
|
+
.wx-bubble.wx-owned {
|
|
52
|
+
justify-content: start;
|
|
53
|
+
}
|
|
54
|
+
.wx-bubble-wrapper {
|
|
55
|
+
width: 65%;
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: end;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
}
|
|
60
|
+
.wx-owned .wx-bubble-wrapper {
|
|
61
|
+
width: calc(65% + 28px);
|
|
62
|
+
justify-content: start;
|
|
63
|
+
}
|
|
64
|
+
.wx-author-name {
|
|
65
|
+
margin-left: auto;
|
|
66
|
+
}
|
|
67
|
+
.wx-owned .wx-author-name {
|
|
68
|
+
margin-left: 0;
|
|
69
|
+
}
|
|
70
|
+
.wx-message {
|
|
71
|
+
background-color: var(--wx-comments-msg-background);
|
|
72
|
+
padding: 8px 12px;
|
|
73
|
+
white-space: pre-wrap;
|
|
74
|
+
line-height: 24px;
|
|
75
|
+
border-radius: 6px 0 6px 6px;
|
|
76
|
+
width: 100%;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
}
|
|
80
|
+
.wx-owned .wx-message {
|
|
81
|
+
background-color: var(--wx-comments-msg-background-agent);
|
|
82
|
+
border-radius: 0 6px 6px 6px;
|
|
83
|
+
width: 100%;
|
|
84
|
+
}
|
|
85
|
+
.wx-author-name {
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
line-height: 24px;
|
|
89
|
+
}
|
|
90
|
+
.wx-agent-message {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: flex-end;
|
|
93
|
+
gap: 4px;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
}
|
|
96
|
+
.wx-menu-icon {
|
|
97
|
+
opacity: 0;
|
|
98
|
+
height: 24px;
|
|
99
|
+
color: #9fa1ae;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
height: 100%;
|
|
102
|
+
}
|
|
103
|
+
.wx-menu-icon i {
|
|
104
|
+
font-size: 24px;
|
|
105
|
+
line-height: 24px;
|
|
106
|
+
}
|
|
107
|
+
.wx-menu-icon:hover {
|
|
108
|
+
color: var(--wx-color-primary);
|
|
109
|
+
}
|
|
110
|
+
.wx-agent-message:hover .wx-menu-icon {
|
|
111
|
+
opacity: 1;
|
|
112
|
+
transition: all 0.2s linear;
|
|
113
|
+
}
|
|
114
|
+
.wx-main-bubble {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: 4px;
|
|
118
|
+
width: 100%;
|
|
119
|
+
}
|
|
120
|
+
.wx-avatar {
|
|
121
|
+
height: 32px;
|
|
122
|
+
width: 32px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.wx-bubble:not(.wx-owned) .wx-bubble-wrapper {
|
|
126
|
+
flex-direction: row-reverse;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.wx-comment-date {
|
|
130
|
+
font-size: var(--wx-font-size-sm);
|
|
131
|
+
color: var(--wx-color-font-alt);
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: row-reverse;
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import TextArea from "../TextArea.svelte";
|
|
3
|
+
import { messages, formats } from "./index.js";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
text,
|
|
7
|
+
date,
|
|
8
|
+
owned,
|
|
9
|
+
render,
|
|
10
|
+
format = "text",
|
|
11
|
+
author,
|
|
12
|
+
edit,
|
|
13
|
+
onpost,
|
|
14
|
+
oncancel
|
|
15
|
+
} = $props();
|
|
16
|
+
|
|
17
|
+
const BoxRender = typeof render === "string" ? messages[render] : render;
|
|
18
|
+
const TextRender = typeof format === "string" ? formats[format] : format;
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<BoxRender {owned} {edit} {author} {date}>
|
|
22
|
+
{#if edit && edit === owned}
|
|
23
|
+
<TextArea focus={true} {onpost} {oncancel} value={text} />
|
|
24
|
+
{:else}
|
|
25
|
+
<TextRender {text} {edit} />
|
|
26
|
+
{/if}
|
|
27
|
+
</BoxRender>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from "svelte";
|
|
3
|
+
import UserIcon from "./UserIcon.svelte";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
owned,
|
|
7
|
+
author,
|
|
8
|
+
date,
|
|
9
|
+
edit,
|
|
10
|
+
children,
|
|
11
|
+
} = $props();
|
|
12
|
+
|
|
13
|
+
const dateFormatter = getContext("wx-comments-format").dateStr;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="wx-flow" class:owned>
|
|
17
|
+
<div class="wx-flow-toolbar">
|
|
18
|
+
<UserIcon data={author} />
|
|
19
|
+
<span class="wx-author-name">{author.name}</span>
|
|
20
|
+
{#if owned && owned !== edit}
|
|
21
|
+
<div class="wx-menu-icon" data-comment-menu-id={owned}>
|
|
22
|
+
<i class="wx-icon wxi-dots-v" ></i>
|
|
23
|
+
</div>
|
|
24
|
+
{/if}
|
|
25
|
+
</div>
|
|
26
|
+
<div class="wx-comment-date">{dateFormatter(date)}</div>
|
|
27
|
+
<div class="wx-message">
|
|
28
|
+
{@render children()}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.wx-flow {
|
|
34
|
+
width: 100%;
|
|
35
|
+
padding: 16px 20px;
|
|
36
|
+
border-radius: 6px;
|
|
37
|
+
}
|
|
38
|
+
.wx-flow.owned {
|
|
39
|
+
background-color: var(--wx-comments-msg-background-agent);
|
|
40
|
+
}
|
|
41
|
+
.wx-flow:hover .wx-menu-icon {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
transition: all 0.2s linear;
|
|
44
|
+
}
|
|
45
|
+
.wx-flow-toolbar {
|
|
46
|
+
width: 100%;
|
|
47
|
+
display: flex;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
}
|
|
50
|
+
.wx-message {
|
|
51
|
+
margin-left: 32px;
|
|
52
|
+
line-height: 24px;
|
|
53
|
+
white-space: pre-wrap;
|
|
54
|
+
}
|
|
55
|
+
.wx-author-name {
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
line-height: 24px;
|
|
59
|
+
}
|
|
60
|
+
.wx-menu-icon {
|
|
61
|
+
color: #9fa1ae;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
margin-left: auto;
|
|
64
|
+
opacity: 0;
|
|
65
|
+
height: 24px;
|
|
66
|
+
}
|
|
67
|
+
.wx-menu-icon:hover {
|
|
68
|
+
color: var(--wx-color-primary);
|
|
69
|
+
}
|
|
70
|
+
.wx-menu-icon i {
|
|
71
|
+
font-size: 24px;
|
|
72
|
+
line-height: 24px;
|
|
73
|
+
}
|
|
74
|
+
.wx-comment-date {
|
|
75
|
+
margin-left: 32px;
|
|
76
|
+
font-size: var(--wx-font-size-sm);
|
|
77
|
+
color: var(--wx-color-font-alt);
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContrastingColor } from "../../helpers/colors";
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
data = {
|
|
6
|
+
name: "",
|
|
7
|
+
avatar: "",
|
|
8
|
+
color: "",
|
|
9
|
+
},
|
|
10
|
+
noTransform = false,
|
|
11
|
+
size = "small",
|
|
12
|
+
border = true,
|
|
13
|
+
} = $props();
|
|
14
|
+
|
|
15
|
+
const firstLetters = $derived(data.name
|
|
16
|
+
.split(" ")
|
|
17
|
+
.map(name => name[0])
|
|
18
|
+
.join(""));
|
|
19
|
+
|
|
20
|
+
let style = $derived(data.color ? `background: ${data.color};` : "");
|
|
21
|
+
let css = $derived(data.color ? `wx-comments-avatar-color-${getContrastingColor(data.color)}` : "");
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div class="wx-user wx-{size} {css}" class:wx-border={border} {style}>
|
|
25
|
+
{#if data.avatar}
|
|
26
|
+
<img src={data.avatar} alt={data.name} />
|
|
27
|
+
{:else if noTransform}{data.name}{:else}{firstLetters}{/if}
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
.wx-user {
|
|
32
|
+
--wx-comments-user-icon-size: 36px;
|
|
33
|
+
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
font-weight: 500;
|
|
36
|
+
line-height: 12px;
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
color: var(--wx-color-font);
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
align-items: center;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
background-color: #ca9cec;
|
|
44
|
+
color: var(--wx-color-font);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.wx-user.wx-normal {
|
|
48
|
+
width: var(--wx-comments-user-icon-size);
|
|
49
|
+
height: var(--wx-comments-user-icon-size);
|
|
50
|
+
font-size: var(--wx-font-size);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.wx-user.wx-small {
|
|
54
|
+
width: 24px;
|
|
55
|
+
height: 24px;
|
|
56
|
+
font-size: var(--wx-font-size-sm);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.wx-user img {
|
|
60
|
+
display: block;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: 100%;
|
|
63
|
+
object-fit: cover;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.wx-user:not(:first-child) {
|
|
67
|
+
margin-left: -15px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.wx-user.wx-comments-avatar-color-light {
|
|
71
|
+
color: var(--wx-color-primary-font);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.wx-user.wx-comments-avatar-color-dark {
|
|
75
|
+
color: var(--wx-avatar-color-dark);
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Bubbles from "./Bubbles.svelte";
|
|
2
|
+
import Flow from "./Flow.svelte";
|
|
3
|
+
|
|
4
|
+
import MarkdownPlain from "./MarkdownPlain.svelte";
|
|
5
|
+
import TextPlain from "./TextPlain.svelte";
|
|
6
|
+
|
|
7
|
+
export const messages = {
|
|
8
|
+
bubbles: Bubbles,
|
|
9
|
+
flow: Flow,
|
|
10
|
+
};
|
|
11
|
+
export const formats = {
|
|
12
|
+
markdown: MarkdownPlain,
|
|
13
|
+
text: TextPlain,
|
|
14
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Message from "./Messages/Common.svelte";
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
data,
|
|
6
|
+
render = "blocks",
|
|
7
|
+
format,
|
|
8
|
+
author,
|
|
9
|
+
edit,
|
|
10
|
+
onpost,
|
|
11
|
+
oncancel,
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
const css = $derived(typeof render === "string" ? render : "blocks");
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div class="wx-messages wx-{css}">
|
|
18
|
+
{#each data as message (message.id)}
|
|
19
|
+
<Message
|
|
20
|
+
text={message.content}
|
|
21
|
+
date={message.date}
|
|
22
|
+
author={message.author}
|
|
23
|
+
owned={message.author.id === author.id ? message.id : null}
|
|
24
|
+
{render}
|
|
25
|
+
{edit}
|
|
26
|
+
format={message.format || format}
|
|
27
|
+
{onpost}
|
|
28
|
+
{oncancel}
|
|
29
|
+
/>
|
|
30
|
+
{/each}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.wx-messages {
|
|
35
|
+
padding: 10px 0;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.wx-messages.wx-bubbles {
|
|
41
|
+
gap: 24px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.wx-messages.wx-cards {
|
|
45
|
+
gap: 16px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.wx-messages.wx-blocks {
|
|
49
|
+
gap: 24px;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount, getContext } from "svelte";
|
|
3
|
+
import { TextArea, Button } from "wx-svelte-core";
|
|
4
|
+
import UserIcon from "./Messages/UserIcon.svelte";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
focus = false,
|
|
8
|
+
buttonLabel = "Send",
|
|
9
|
+
flow = false,
|
|
10
|
+
value = $bindable(""),
|
|
11
|
+
author,
|
|
12
|
+
onpost,
|
|
13
|
+
} = $props();
|
|
14
|
+
|
|
15
|
+
let textarea, area;
|
|
16
|
+
|
|
17
|
+
const _ = getContext("wx-i18n").getGroup("comments");
|
|
18
|
+
|
|
19
|
+
onMount(() => {
|
|
20
|
+
area = textarea.querySelector("textarea");
|
|
21
|
+
if (focus) area.focus();
|
|
22
|
+
area.onkeydown = ev => {
|
|
23
|
+
if (ev.key === "Enter" && (ev.ctrlKey || ev.metaKey)) {
|
|
24
|
+
ev.preventDefault();
|
|
25
|
+
onclick();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function onclick() {
|
|
31
|
+
if (!value) return;
|
|
32
|
+
onpost && onpost({ value });
|
|
33
|
+
value = "";
|
|
34
|
+
area.focus();
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div
|
|
39
|
+
class="wx-comments-textarea"
|
|
40
|
+
class:wx-flow={flow}
|
|
41
|
+
data-focus="yes"
|
|
42
|
+
bind:this={textarea}
|
|
43
|
+
>
|
|
44
|
+
<div class="wx-textarea-wrapper">
|
|
45
|
+
{#if author}
|
|
46
|
+
<div class="wx-textarea-avatar">
|
|
47
|
+
<UserIcon data={author} />
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
<TextArea placeholder={_("Add a comment...")} bind:value />
|
|
51
|
+
</div>
|
|
52
|
+
<div class="wx-textarea-bottombar">
|
|
53
|
+
<Button type="primary" {onclick}>{buttonLabel}</Button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.wx-comments-textarea {
|
|
59
|
+
width: 100%;
|
|
60
|
+
background-color: inherit;
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
gap: 12px;
|
|
64
|
+
}
|
|
65
|
+
.wx-textarea-wrapper {
|
|
66
|
+
position: relative;
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
}
|
|
70
|
+
.wx-comments-textarea.wx-flow .wx-textarea-avatar {
|
|
71
|
+
margin-left: 20px;
|
|
72
|
+
}
|
|
73
|
+
.wx-textarea-bottombar {
|
|
74
|
+
text-align: right;
|
|
75
|
+
}
|
|
76
|
+
.wx-textarea-bottombar :global(button) {
|
|
77
|
+
padding: 6px 16px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
line-height: 20px;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function getContrastingColor(color) {
|
|
2
|
+
if (color.startsWith("hsl")) {
|
|
3
|
+
const n = toHSLArray(color);
|
|
4
|
+
return n[2] > 55 ? "dark" : "light";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (color.startsWith("#") || color.startsWith("rgb")) {
|
|
8
|
+
const rgb = hexToRgb(color, true);
|
|
9
|
+
const brightness = Math.round(
|
|
10
|
+
(rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
|
|
11
|
+
);
|
|
12
|
+
return brightness > 180 ? "dark" : "light";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// consider it's an html color, most of them are vivid, so
|
|
16
|
+
return "light";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toHSLArray(hslStr) {
|
|
20
|
+
return hslStr.match(/\d+/g).map(Number);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hexToRgb(hex, asObject = false) {
|
|
24
|
+
if (hex.startsWith("rgb")) {
|
|
25
|
+
return asObject ? rgbStringToObj(hex) : hex;
|
|
26
|
+
}
|
|
27
|
+
let r = 0,
|
|
28
|
+
g = 0,
|
|
29
|
+
b = 0;
|
|
30
|
+
|
|
31
|
+
// 3 digits
|
|
32
|
+
if (hex.length == 4) {
|
|
33
|
+
r = "0x" + hex[1] + hex[1];
|
|
34
|
+
g = "0x" + hex[2] + hex[2];
|
|
35
|
+
b = "0x" + hex[3] + hex[3];
|
|
36
|
+
|
|
37
|
+
// 6 digits
|
|
38
|
+
} else if (hex.length == 7) {
|
|
39
|
+
r = "0x" + hex[1] + hex[2];
|
|
40
|
+
g = "0x" + hex[3] + hex[4];
|
|
41
|
+
b = "0x" + hex[5] + hex[6];
|
|
42
|
+
}
|
|
43
|
+
r = +r;
|
|
44
|
+
g = +g;
|
|
45
|
+
b = +b;
|
|
46
|
+
return asObject ? { r, g, b } : "rgb(" + r + "," + g + "," + b + ")";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function rgbStringToObj(rgb) {
|
|
50
|
+
const sep = rgb.indexOf(",") > -1 ? "," : " ";
|
|
51
|
+
// Turn "rgb(r,g,b)" into [r,g,b]
|
|
52
|
+
const arr = rgb.substring(4).split(")")[0].split(sep);
|
|
53
|
+
|
|
54
|
+
const r = (+arr[0]).toString(16),
|
|
55
|
+
g = (+arr[1]).toString(16),
|
|
56
|
+
b = (+arr[2]).toString(16);
|
|
57
|
+
return { r: +r, g: +g, b: +b };
|
|
58
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
function parse(str, ast, notFinal) {
|
|
2
|
+
let content;
|
|
3
|
+
let ctype;
|
|
4
|
+
let headerlevel, newLineCounter;
|
|
5
|
+
let checkbehind, unsurepoint;
|
|
6
|
+
let linestart;
|
|
7
|
+
let i;
|
|
8
|
+
if (!ast) {
|
|
9
|
+
ast = {
|
|
10
|
+
nodes: [{ d: [], t: "p", f: false, i: 0 }],
|
|
11
|
+
length: 0,
|
|
12
|
+
state: {},
|
|
13
|
+
};
|
|
14
|
+
linestart = 0;
|
|
15
|
+
headerlevel = 0;
|
|
16
|
+
checkbehind = -1;
|
|
17
|
+
unsurepoint = -1;
|
|
18
|
+
content = "";
|
|
19
|
+
ctype = "";
|
|
20
|
+
newLineCounter = 0;
|
|
21
|
+
i = 0;
|
|
22
|
+
} else {
|
|
23
|
+
({
|
|
24
|
+
linestart,
|
|
25
|
+
headerlevel,
|
|
26
|
+
checkbehind,
|
|
27
|
+
unsurepoint,
|
|
28
|
+
content,
|
|
29
|
+
ctype,
|
|
30
|
+
newLineCounter,
|
|
31
|
+
i,
|
|
32
|
+
} = ast.state);
|
|
33
|
+
}
|
|
34
|
+
if (unsurepoint) {
|
|
35
|
+
const b = ast.nodes[ast.nodes.length - 1];
|
|
36
|
+
if (b.d.length) {
|
|
37
|
+
ast.length -= b.d[b.d.length - 1].c.length;
|
|
38
|
+
b.d.pop();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const finishBlock = notFinal2 => {
|
|
42
|
+
if (content !== "") {
|
|
43
|
+
finishText("", notFinal2);
|
|
44
|
+
}
|
|
45
|
+
const lastBlock = ast.nodes[ast.nodes.length - 1];
|
|
46
|
+
lastBlock.f = notFinal2 !== 1;
|
|
47
|
+
lastBlock.i = i;
|
|
48
|
+
if (headerlevel > 0) {
|
|
49
|
+
lastBlock.t = `h${headerlevel}`;
|
|
50
|
+
headerlevel = -1;
|
|
51
|
+
}
|
|
52
|
+
if (notFinal2 === 0) ast.nodes.push({ d: [], t: "p", f: false, i: 0 });
|
|
53
|
+
};
|
|
54
|
+
const finishText = (t, notFinal2 = 2) => {
|
|
55
|
+
const block = ast.nodes[ast.nodes.length - 1];
|
|
56
|
+
const last = block.d[block.d.length - 1];
|
|
57
|
+
const utype = notFinal2 === 2 ? ctype : "";
|
|
58
|
+
if (last && last.t === utype && unsurepoint < 0) {
|
|
59
|
+
const text = last;
|
|
60
|
+
ast.length += content.length;
|
|
61
|
+
text.c = text.c + content;
|
|
62
|
+
text.i = i;
|
|
63
|
+
} else {
|
|
64
|
+
const c =
|
|
65
|
+
utype === "" && unsurepoint >= 0
|
|
66
|
+
? str.substring(unsurepoint)
|
|
67
|
+
: content;
|
|
68
|
+
ast.length += c.length;
|
|
69
|
+
block.d.push({ c, t: utype, f: notFinal2 !== 1, i });
|
|
70
|
+
}
|
|
71
|
+
if (notFinal2 === 2) {
|
|
72
|
+
if (t) {
|
|
73
|
+
unsurepoint = checkbehind;
|
|
74
|
+
} else {
|
|
75
|
+
unsurepoint = -1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (notFinal2 !== 1) {
|
|
79
|
+
content = "";
|
|
80
|
+
ctype = t;
|
|
81
|
+
checkbehind = -1;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
for (; i < str.length; i++) {
|
|
85
|
+
const char = str[i];
|
|
86
|
+
if (char === "\n") {
|
|
87
|
+
newLineCounter++;
|
|
88
|
+
linestart = i + 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (char === " " || char === " ") {
|
|
92
|
+
if (newLineCounter > 0) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (newLineCounter > 0) {
|
|
97
|
+
if (newLineCounter > 1) {
|
|
98
|
+
finishBlock(0);
|
|
99
|
+
} else {
|
|
100
|
+
content += "\n";
|
|
101
|
+
}
|
|
102
|
+
newLineCounter = 0;
|
|
103
|
+
}
|
|
104
|
+
if (char == "*" || char == "`" || char == "#") {
|
|
105
|
+
if (str[i - 1] == "\\") {
|
|
106
|
+
content += char;
|
|
107
|
+
} else if (checkbehind === -1) {
|
|
108
|
+
checkbehind = i;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (checkbehind >= 0) {
|
|
112
|
+
const l = i - checkbehind;
|
|
113
|
+
if (str[i - 1] == "*") {
|
|
114
|
+
if (l === 2) {
|
|
115
|
+
if (ctype === "strong") {
|
|
116
|
+
finishText("");
|
|
117
|
+
} else {
|
|
118
|
+
finishText("strong");
|
|
119
|
+
}
|
|
120
|
+
} else if (l === 1) {
|
|
121
|
+
if (ctype === "em") {
|
|
122
|
+
finishText("");
|
|
123
|
+
} else {
|
|
124
|
+
finishText("em");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
checkbehind = -1;
|
|
128
|
+
} else if (str[i - 1] == "`" && l == 1) {
|
|
129
|
+
if (ctype === "code") {
|
|
130
|
+
finishText("");
|
|
131
|
+
} else {
|
|
132
|
+
finishText("code");
|
|
133
|
+
}
|
|
134
|
+
checkbehind = -1;
|
|
135
|
+
} else if (
|
|
136
|
+
str[i - 1] == "#" &&
|
|
137
|
+
str[i] === " " &&
|
|
138
|
+
checkbehind === linestart &&
|
|
139
|
+
i - checkbehind <= 6
|
|
140
|
+
) {
|
|
141
|
+
headerlevel = i - checkbehind;
|
|
142
|
+
checkbehind = -1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (checkbehind !== -1) {
|
|
147
|
+
content += str.substring(checkbehind, i);
|
|
148
|
+
checkbehind = -1;
|
|
149
|
+
}
|
|
150
|
+
content += char;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
finishBlock(notFinal ? 1 : -1);
|
|
154
|
+
if (notFinal) {
|
|
155
|
+
ast.state = {
|
|
156
|
+
linestart,
|
|
157
|
+
headerlevel,
|
|
158
|
+
checkbehind,
|
|
159
|
+
unsurepoint,
|
|
160
|
+
content,
|
|
161
|
+
ctype,
|
|
162
|
+
newLineCounter,
|
|
163
|
+
i,
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
ast.state = {};
|
|
167
|
+
}
|
|
168
|
+
return ast;
|
|
169
|
+
}
|
|
170
|
+
function render(target, ast) {
|
|
171
|
+
_renderNodes(target, ast.nodes);
|
|
172
|
+
}
|
|
173
|
+
function _renderNodes(target, nodes) {
|
|
174
|
+
if (!target) return;
|
|
175
|
+
for (const node of nodes) {
|
|
176
|
+
if (typeof node.d !== "undefined") {
|
|
177
|
+
const block = node;
|
|
178
|
+
const el = document.createElement(block.t);
|
|
179
|
+
target.appendChild(el);
|
|
180
|
+
_renderNodes(el, block.d);
|
|
181
|
+
} else {
|
|
182
|
+
const text = node;
|
|
183
|
+
if (text.t) {
|
|
184
|
+
const el = document.createElement(node.t);
|
|
185
|
+
el.textContent = text.c;
|
|
186
|
+
target.appendChild(el);
|
|
187
|
+
} else {
|
|
188
|
+
const el = document.createTextNode(text.c);
|
|
189
|
+
target.appendChild(el);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function renderNext(target, ast, prevState, distance, cursor) {
|
|
195
|
+
if (!target) return;
|
|
196
|
+
return (
|
|
197
|
+
_renderNodesNext(target, ast.nodes, prevState, distance, 0, cursor) ===
|
|
198
|
+
0
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function _renderNodesNext(target, nodes, prevState, distance, level, cursor) {
|
|
202
|
+
let start = 0,
|
|
203
|
+
textIndex = 0,
|
|
204
|
+
el = null,
|
|
205
|
+
temp = null;
|
|
206
|
+
const prev = prevState[level];
|
|
207
|
+
if (prev) {
|
|
208
|
+
start = prev.nodeIndex;
|
|
209
|
+
textIndex = prev.textIndex;
|
|
210
|
+
el = prev.target;
|
|
211
|
+
temp = prev.temp;
|
|
212
|
+
}
|
|
213
|
+
for (let i = start; i < nodes.length; i++) {
|
|
214
|
+
const node = nodes[i];
|
|
215
|
+
if (typeof node.d !== "undefined") {
|
|
216
|
+
const block = nodes[i];
|
|
217
|
+
if (!el) {
|
|
218
|
+
el = document.createElement(block.t);
|
|
219
|
+
target.appendChild(el);
|
|
220
|
+
}
|
|
221
|
+
distance = _renderNodesNext(
|
|
222
|
+
el,
|
|
223
|
+
block.d,
|
|
224
|
+
prevState,
|
|
225
|
+
distance,
|
|
226
|
+
level + 1,
|
|
227
|
+
cursor
|
|
228
|
+
);
|
|
229
|
+
if (!distance) {
|
|
230
|
+
prevState[level] = { nodeIndex: i, textIndex: 0, target: el };
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
el = null;
|
|
234
|
+
} else {
|
|
235
|
+
let textContent;
|
|
236
|
+
const text = node;
|
|
237
|
+
if (text.c.length - textIndex > distance) {
|
|
238
|
+
textContent = text.c.substring(textIndex, textIndex + distance);
|
|
239
|
+
textIndex += distance;
|
|
240
|
+
distance = 0;
|
|
241
|
+
} else {
|
|
242
|
+
textContent = text.c.substring(textIndex);
|
|
243
|
+
distance -= text.c.length - textIndex;
|
|
244
|
+
textIndex = 0;
|
|
245
|
+
}
|
|
246
|
+
if (text.t) {
|
|
247
|
+
if (!el) {
|
|
248
|
+
el = document.createElement(text.t);
|
|
249
|
+
target.insertBefore(el, temp);
|
|
250
|
+
el.textContent = textContent;
|
|
251
|
+
} else {
|
|
252
|
+
el.textContent += textContent;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
if (!el) {
|
|
256
|
+
const el2 = document.createTextNode(textContent);
|
|
257
|
+
target.insertBefore(el2, temp);
|
|
258
|
+
} else {
|
|
259
|
+
el.textContent += textContent;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (distance === 0) {
|
|
263
|
+
if (!temp) temp = addCursor(target, cursor);
|
|
264
|
+
prevState[level] = textIndex
|
|
265
|
+
? { temp, nodeIndex: i, textIndex, target: el }
|
|
266
|
+
: { temp, nodeIndex: i + 1, textIndex: 0, target: null };
|
|
267
|
+
return distance;
|
|
268
|
+
}
|
|
269
|
+
el = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
prevState[level] = null;
|
|
273
|
+
if (temp) temp.parentNode.removeChild(temp);
|
|
274
|
+
return distance;
|
|
275
|
+
}
|
|
276
|
+
function addCursor(parent, name) {
|
|
277
|
+
const el = document.createElement("div");
|
|
278
|
+
el.className = name;
|
|
279
|
+
el.textContent = " ";
|
|
280
|
+
parent.appendChild(el);
|
|
281
|
+
return el;
|
|
282
|
+
}
|
|
283
|
+
function asHTML(a) {
|
|
284
|
+
let out = "";
|
|
285
|
+
for (const node of a) {
|
|
286
|
+
if (typeof node.d !== "undefined") {
|
|
287
|
+
out += "<" + node.t + ">";
|
|
288
|
+
out += asHTML(node.d);
|
|
289
|
+
out += "</" + node.t + ">";
|
|
290
|
+
} else {
|
|
291
|
+
if (node.t) out += "<" + node.t + ">";
|
|
292
|
+
out += node.c;
|
|
293
|
+
if (node.t) out += "</" + node.t + ">";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function asMarkdown(a) {
|
|
299
|
+
let out = "";
|
|
300
|
+
let space = "";
|
|
301
|
+
for (const node of a) {
|
|
302
|
+
if (typeof node.d !== "undefined") {
|
|
303
|
+
if (node.t === "h1") {
|
|
304
|
+
out += space + "# ";
|
|
305
|
+
} else if (node.t === "h2") {
|
|
306
|
+
out += space + "## ";
|
|
307
|
+
} else if (node.t === "h3") {
|
|
308
|
+
out += space + "### ";
|
|
309
|
+
} else if (node.t === "h4") {
|
|
310
|
+
out += space + "#### ";
|
|
311
|
+
} else if (node.t === "h5") {
|
|
312
|
+
out += space + "##### ";
|
|
313
|
+
} else if (node.t === "h6") {
|
|
314
|
+
out += space + "###### ";
|
|
315
|
+
} else {
|
|
316
|
+
out += space;
|
|
317
|
+
}
|
|
318
|
+
space = "\n\n";
|
|
319
|
+
out += asMarkdown(node.d);
|
|
320
|
+
} else {
|
|
321
|
+
let wrap = "";
|
|
322
|
+
if (node.t === "strong") {
|
|
323
|
+
wrap = "**";
|
|
324
|
+
} else if (node.t === "em") {
|
|
325
|
+
wrap = "*";
|
|
326
|
+
} else if (node.t === "code") {
|
|
327
|
+
wrap = "`";
|
|
328
|
+
}
|
|
329
|
+
out += wrap + node.c + wrap;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
function html(s) {
|
|
335
|
+
return asHTML(parse(s).nodes);
|
|
336
|
+
}
|
|
337
|
+
export { asHTML, asMarkdown, html, parse, render, renderNext };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Willow } from "wx-svelte-core";
|
|
3
|
+
|
|
4
|
+
const { children, fonts } = $$props;
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
{#if children}
|
|
8
|
+
<Willow {fonts}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</Willow>
|
|
11
|
+
{:else}
|
|
12
|
+
<Willow {fonts} />
|
|
13
|
+
{/if}
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
:global(.wx-willow-theme) {
|
|
17
|
+
--wx-theme-name: willow;
|
|
18
|
+
|
|
19
|
+
--wx-comments-background: #ffffff;
|
|
20
|
+
--wx-comments-line-height: 24px;
|
|
21
|
+
--wx-comments-border: 1px solid #e6e6e6;
|
|
22
|
+
|
|
23
|
+
--wx-comments-msg-background: #d5eaf7;
|
|
24
|
+
--wx-comments-msg-background-agent: #f4f5f9;
|
|
25
|
+
--wx-comments-msg-background-hover: rgba(57, 169, 239, 0.03);
|
|
26
|
+
--wx-comments-avatar-icon-color: #ffffff;
|
|
27
|
+
--wx-comments-msg-blocks-background: #e6e6e6;
|
|
28
|
+
--wx-comments-msg-blocks-background-agent: #ffffff;
|
|
29
|
+
--wx-comments-msg-controls-shadow: 0px 3px 10px 0px
|
|
30
|
+
rgba(44, 47, 60, 0.12),
|
|
31
|
+
0px 1px 2px 0px rgba(44, 47, 60, 0.06);
|
|
32
|
+
|
|
33
|
+
--wx-comments-sidebar-background: #fafafc;
|
|
34
|
+
--wx-comments-sidebar-chat-hover: #eaedf5;
|
|
35
|
+
|
|
36
|
+
--wx-comments-popup-agent-hover: #f7f7f7;
|
|
37
|
+
--wx-comments-popup-border: 1px solid #ffffff;
|
|
38
|
+
--wx-comments-popup-background: #ffffff;
|
|
39
|
+
|
|
40
|
+
--wx-comments-textarea-border: var(--wx-comments-border);
|
|
41
|
+
--wx-comments-textarea-focus-border: 1px solid var(--wx-color-primary);
|
|
42
|
+
--wx-comments-textarea-color: #475466;
|
|
43
|
+
|
|
44
|
+
--wx-comments-button-icon--color-disabled: #9fa1ae;
|
|
45
|
+
|
|
46
|
+
--wx-comments-scroll-background: #e6e6e6;
|
|
47
|
+
--wx-comments-slider-background: #f2f3f7;
|
|
48
|
+
|
|
49
|
+
--ww-chat-modal-background: #ffffff;
|
|
50
|
+
|
|
51
|
+
--wx-comments-header-background: rgba(122, 103, 235, 0.2);
|
|
52
|
+
--wx-comments-icon-color: #9fa1ae;
|
|
53
|
+
|
|
54
|
+
--wx-comments-button-toggle-sidebar-background: #f2f3f7;
|
|
55
|
+
|
|
56
|
+
--wx-avatar-color-dark: #2a2b2d;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { WillowDark } from "wx-svelte-core";
|
|
3
|
+
|
|
4
|
+
const { children, fonts } = $$props;
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
{#if children}
|
|
8
|
+
<WillowDark {fonts}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</WillowDark>
|
|
11
|
+
{:else}
|
|
12
|
+
<WillowDark {fonts} />
|
|
13
|
+
{/if}
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
:global(.wx-willow-dark-theme) {
|
|
17
|
+
--wx-theme-name: willow-dark;
|
|
18
|
+
|
|
19
|
+
--wx-comments-background: #2b343b;
|
|
20
|
+
--wx-comments-line-height: 24px;
|
|
21
|
+
--wx-comments-border: 1px solid #384047;
|
|
22
|
+
|
|
23
|
+
--wx-comments-msg-background: rgba(122, 102, 235, 0.2);
|
|
24
|
+
--wx-comments-msg-background-agent: #384047;
|
|
25
|
+
--wx-comments-msg-background-hover: rgb(122, 103, 235, 0.1);
|
|
26
|
+
--wx-comments-avatar-icon-color: #2b343b;
|
|
27
|
+
--wx-comments-msg-blocks-background: #384047;
|
|
28
|
+
--wx-comments-msg-blocks-background-agent: #20262b;
|
|
29
|
+
--wx-comments-msg-controls-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.16);
|
|
30
|
+
|
|
31
|
+
--wx-comments-sidebar-background: #2a2b2d;
|
|
32
|
+
--wx-comments-sidebar-chat-hover: #384047;
|
|
33
|
+
|
|
34
|
+
--wx-comments-popup-agent-hover: #384047;
|
|
35
|
+
--wx-comments-popup-border: var(--wx-comments-border);
|
|
36
|
+
--wx-comments-popup-background: #2a2b2d;
|
|
37
|
+
|
|
38
|
+
--wx-comments-textarea-border: var(--wx-comments-border);
|
|
39
|
+
--wx-comments-textarea-focus-border: 1px solid var(--wx-color-primary);
|
|
40
|
+
--wx-comments-textarea-color: rgba(255, 255, 255, 0.9);
|
|
41
|
+
|
|
42
|
+
--wx-comments-button-icon--color-disabled: #20262b;
|
|
43
|
+
|
|
44
|
+
--wx-comments-scroll-background: #20262b;
|
|
45
|
+
--wx-comments-slider-background: #384047;
|
|
46
|
+
|
|
47
|
+
--ww-chat-modal-background: #2b343b;
|
|
48
|
+
|
|
49
|
+
--wx-comments-header-background: #2a2b2d;
|
|
50
|
+
--wx-comments-icon-color: #9fa1ae;
|
|
51
|
+
|
|
52
|
+
--wx-comments-button-toggle-sidebar-background: #384047;
|
|
53
|
+
|
|
54
|
+
--wx-avatar-color-dark: #2a2b2d;
|
|
55
|
+
}
|
|
56
|
+
</style>
|