@lapikit/repl 0.0.1 → 0.0.3
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/README.md +92 -0
- package/dist/Button.svelte +41 -0
- package/dist/Button.svelte.d.ts +9 -0
- package/dist/Files.svelte +64 -0
- package/dist/Files.svelte.d.ts +4 -0
- package/dist/Repl.svelte +128 -238
- package/dist/Repl.svelte.d.ts +2 -5
- package/dist/Toolbar.svelte +120 -0
- package/dist/Toolbar.svelte.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +31 -0
- package/package.json +2 -2
- package/dist/assets/icons/check.svg +0 -1
- package/dist/assets/icons/code.svg +0 -1
- package/dist/assets/icons/codesandbox.svg +0 -1
- package/dist/assets/icons/copy.svg +0 -1
- /package/dist/{assets/languages → languages}/css.svg +0 -0
- /package/dist/{assets/languages → languages}/html.svg +0 -0
- /package/dist/{assets/languages → languages}/javascript.svg +0 -0
- /package/dist/{assets/languages/code.svg → languages/shell.svg} +0 -0
- /package/dist/{assets/languages → languages}/svelte.svg +0 -0
- /package/dist/{assets/languages → languages}/typescript.svg +0 -0
package/README.md
CHANGED
|
@@ -1 +1,93 @@
|
|
|
1
1
|
# @Lapikit/repl
|
|
2
|
+
|
|
3
|
+
@lapikit/repl is a Svelte component for Lapikit. It's a add-on package for Lapikit library.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -D @lapikit/repl
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
// svelte.config.js
|
|
15
|
+
|
|
16
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
17
|
+
|
|
18
|
+
const config = {
|
|
19
|
+
preprocess: [vitePreprocess(), lapikitPreprocess({ plugins: ['repl'] })],
|
|
20
|
+
|
|
21
|
+
...
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default config;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```svelte
|
|
30
|
+
<kit:repl content="console.log('hello')" />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
```svelte
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import sampleCounter from './samples/counter.svelte?raw';
|
|
37
|
+
import Counter from './samples/counter.svelte';
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<kit:repl presentation content={sampleCounter}>
|
|
41
|
+
<Counter />
|
|
42
|
+
</kit:repl>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
```svelte
|
|
47
|
+
<script lang="ts">
|
|
48
|
+
import sampleJson from './samples/json.json?raw';
|
|
49
|
+
import sampleCounter from './samples/counter.svelte?raw';
|
|
50
|
+
import sampleCss from './samples/css.css?raw';
|
|
51
|
+
import Counter from './samples/counter.svelte';
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<kit:repl
|
|
55
|
+
presentation
|
|
56
|
+
content={{
|
|
57
|
+
'Counter.svelte': { code: sampleCounter, lang: 'svelte' },
|
|
58
|
+
'file.json': { code: sampleJson, lang: 'json' },
|
|
59
|
+
'Styles.css': { code: sampleCss, lang: 'css' },
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Counter />
|
|
63
|
+
</kit:repl>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
```svelte
|
|
68
|
+
<script lang="ts">
|
|
69
|
+
import sampleJson from './samples/json.json?raw';
|
|
70
|
+
import sampleCounter from './samples/counter.svelte?raw';
|
|
71
|
+
import sampleCss from './samples/css.css?raw';
|
|
72
|
+
import Counter from './samples/counter.svelte';
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<kit:repl
|
|
76
|
+
content={{
|
|
77
|
+
'Counter.svelte': { code: sampleCounter, lang: 'svelte' },
|
|
78
|
+
'file.json': { code: sampleJson, lang: 'json' },
|
|
79
|
+
'Styles.css': { code: sampleCss, lang: 'css' },
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Counter />
|
|
83
|
+
</kit:repl>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Props
|
|
88
|
+
|
|
89
|
+
| Prop | Type | Default | Description |
|
|
90
|
+
| ----------- | --------------------------- | --------- | ------------------------------------------------------------ |
|
|
91
|
+
| content | string \| Record<string, { code: string; lang?: string }> | '' | The code content to be displayed in the REPL. It can be a single string or an object representing multiple files. |
|
|
92
|
+
| presentation | boolean | false | If true, the REPL will be in presentation mode, showing only the output without the code editor. |
|
|
93
|
+
| tiltle | string | '' | The title displayed on the REPL toolbar. |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let { children, type = 'button', disabled = false, ...rest }: PropsTypeButton = $props();
|
|
5
|
+
|
|
6
|
+
type PropsTypeButton = {
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
type?: 'button' | 'submit' | 'reset';
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
[rest: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<button {type} {disabled} {...rest}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
button {
|
|
20
|
+
background: none;
|
|
21
|
+
border: none;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-self: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
font-size: 0.875rem;
|
|
27
|
+
border-radius: 0.375rem;
|
|
28
|
+
transition: background-color 0.2s ease;
|
|
29
|
+
padding: 8px;
|
|
30
|
+
color: var(--kit-repl-secondary);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
button:hover {
|
|
34
|
+
color: var(--kit-repl-primary);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
button :global(svg) {
|
|
38
|
+
height: 20px;
|
|
39
|
+
width: 20px;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
declare const Button: import("svelte").Component<{
|
|
3
|
+
[rest: string]: unknown;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
type?: "button" | "submit" | "reset";
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}, {}, "">;
|
|
8
|
+
type Button = ReturnType<typeof Button>;
|
|
9
|
+
export default Button;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FilesProps } from './types.js';
|
|
3
|
+
import { dictionaryIcons } from './utils.js';
|
|
4
|
+
|
|
5
|
+
let { files, activeIndex = $bindable(), modeState, viewState }: FilesProps = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
{#if modeState !== 'playground' && viewState === 'code' && files && files.length > 1}
|
|
9
|
+
<div role="tablist" aria-label="Files">
|
|
10
|
+
{#each files as file, index (index)}
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
role="tab"
|
|
14
|
+
aria-selected={activeIndex === index}
|
|
15
|
+
aria-label="Select {file.name}"
|
|
16
|
+
class:active={activeIndex === index}
|
|
17
|
+
onclick={() => (activeIndex = index)}
|
|
18
|
+
>
|
|
19
|
+
{#if file.lang && dictionaryIcons[file.lang]}
|
|
20
|
+
<img src={dictionaryIcons[file.lang]} alt="{file.lang} icon" />
|
|
21
|
+
{/if}
|
|
22
|
+
<span>{file.name}</span>
|
|
23
|
+
</button>
|
|
24
|
+
{/each}
|
|
25
|
+
</div>
|
|
26
|
+
{/if}
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
div {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: calc(var(--kit-repl-spacing) * 2);
|
|
32
|
+
padding-left: calc(5 * var(--kit-repl-spacing));
|
|
33
|
+
padding-right: calc(5 * var(--kit-repl-spacing));
|
|
34
|
+
padding-block: calc(var(--kit-repl-spacing) * 2);
|
|
35
|
+
overflow-x: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
button {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: calc(var(--kit-repl-spacing) * 2);
|
|
42
|
+
padding: calc(var(--kit-repl-spacing) * 2) calc(var(--kit-repl-spacing) * 3);
|
|
43
|
+
font-size: 0.875rem;
|
|
44
|
+
transition: all 0.2s ease;
|
|
45
|
+
border: 0;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
background-color: transparent;
|
|
48
|
+
border-bottom: 2px solid transparent;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
button img {
|
|
53
|
+
width: 16px;
|
|
54
|
+
height: 16px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
button:hover {
|
|
58
|
+
border-color: #cfcfcf;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
button.active {
|
|
62
|
+
border-color: #000000;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
package/dist/Repl.svelte
CHANGED
|
@@ -1,39 +1,35 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { copyToClipboard } from './utils.js';
|
|
3
|
+
import { getHighlighterSingleton } from './shiki.js';
|
|
4
|
+
import type { FileItem, ReplProps } from './types.js';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
// components
|
|
7
|
+
import Toolbar from './Toolbar.svelte';
|
|
8
|
+
import Files from './Files.svelte';
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
import JavaScriptIcon from './assets/languages/javascript.svg';
|
|
8
|
-
import TypeScriptIcon from './assets/languages/typescript.svg';
|
|
9
|
-
import SvelteIcon from './assets/languages/svelte.svg';
|
|
10
|
-
import CssIcon from './assets/languages/css.svg';
|
|
11
|
-
import HtmlIcon from './assets/languages/html.svg';
|
|
12
|
-
import { getHighlighterSingleton } from './shiki.js';
|
|
10
|
+
let { title, content, children, presentation }: ReplProps = $props();
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
content: string;
|
|
17
|
-
lang?: string;
|
|
18
|
-
}
|
|
12
|
+
// refs
|
|
13
|
+
let ref: null | HTMLElement = $state(null);
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
let
|
|
15
|
+
// states
|
|
16
|
+
let language = $state('sh');
|
|
17
|
+
|
|
18
|
+
let modeState: 'code' | 'playground' | 'mixed' = $state('code');
|
|
22
19
|
let copyState = $state(false);
|
|
23
|
-
let
|
|
24
|
-
let
|
|
20
|
+
let viewState: 'code' | 'preview' = $state('code');
|
|
21
|
+
let themeState: 'light' | 'dark' = $state('light');
|
|
25
22
|
|
|
26
|
-
let
|
|
27
|
-
let
|
|
23
|
+
let codeHTML = $state('');
|
|
24
|
+
let activeFileIndex = $state(0);
|
|
28
25
|
|
|
29
|
-
// multiple content types
|
|
30
26
|
let files = $derived.by<FileItem[]>(() => {
|
|
31
27
|
if (typeof content === 'object' && content !== null && 'code' in content) {
|
|
32
28
|
return [
|
|
33
29
|
{
|
|
34
30
|
name: title || 'code',
|
|
35
31
|
content: content.code,
|
|
36
|
-
lang: content.lang || '
|
|
32
|
+
lang: content.lang || 'sh'
|
|
37
33
|
}
|
|
38
34
|
];
|
|
39
35
|
}
|
|
@@ -41,8 +37,14 @@
|
|
|
41
37
|
if (typeof content === 'object' && content !== null && !Array.isArray(content)) {
|
|
42
38
|
return Object.entries(content).map(([name, fileContent]) => ({
|
|
43
39
|
name,
|
|
44
|
-
content:
|
|
45
|
-
|
|
40
|
+
content:
|
|
41
|
+
typeof fileContent === 'string'
|
|
42
|
+
? fileContent
|
|
43
|
+
: (fileContent as Record<string, unknown>).code || '',
|
|
44
|
+
lang:
|
|
45
|
+
typeof fileContent === 'object'
|
|
46
|
+
? ((fileContent as Record<string, unknown>).lang as string)
|
|
47
|
+
: 'sh'
|
|
46
48
|
}));
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -50,27 +52,29 @@
|
|
|
50
52
|
return content.map((item) => ({
|
|
51
53
|
name: item.name,
|
|
52
54
|
content: item.content || item.code || '',
|
|
53
|
-
lang: item.lang || '
|
|
55
|
+
lang: item.lang || 'sh'
|
|
54
56
|
}));
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
return [{ name: 'code', content: content || '', lang: '
|
|
59
|
+
return [{ name: 'code', content: content || '', lang: 'sh' }];
|
|
58
60
|
});
|
|
59
|
-
|
|
60
|
-
let activeFileIndex = $state(0);
|
|
61
61
|
let activeFile = $derived(files[activeFileIndex]);
|
|
62
|
-
let hasMultipleFiles = $derived(files.length > 1);
|
|
63
|
-
|
|
64
|
-
let iconMap = {
|
|
65
|
-
code: CodeIcon,
|
|
66
|
-
javascript: JavaScriptIcon,
|
|
67
|
-
typescript: TypeScriptIcon,
|
|
68
|
-
svelte: SvelteIcon,
|
|
69
|
-
css: CssIcon,
|
|
70
|
-
html: HtmlIcon
|
|
71
|
-
} as const;
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
$effect.pre(() => {
|
|
64
|
+
if (children && content && !presentation) {
|
|
65
|
+
modeState = 'mixed';
|
|
66
|
+
viewState = 'preview';
|
|
67
|
+
} else if (presentation) {
|
|
68
|
+
modeState = 'mixed';
|
|
69
|
+
viewState = 'code';
|
|
70
|
+
} else if (children && !content) {
|
|
71
|
+
modeState = 'playground';
|
|
72
|
+
viewState = 'preview';
|
|
73
|
+
} else {
|
|
74
|
+
modeState = 'code';
|
|
75
|
+
viewState = 'code';
|
|
76
|
+
}
|
|
77
|
+
});
|
|
74
78
|
|
|
75
79
|
$effect(() => {
|
|
76
80
|
if (copyState) {
|
|
@@ -87,16 +91,16 @@
|
|
|
87
91
|
|
|
88
92
|
$effect(() => {
|
|
89
93
|
const file = activeFile;
|
|
90
|
-
const theme =
|
|
94
|
+
const theme = themeState;
|
|
91
95
|
|
|
92
96
|
if (file?.content) {
|
|
93
97
|
(async () => {
|
|
94
|
-
console.log('Rendering file:', file);
|
|
95
98
|
const highlighter = await getHighlighterSingleton();
|
|
96
99
|
|
|
100
|
+
language = file.lang || 'sh';
|
|
97
101
|
const html = highlighter.codeToHtml(file.content, {
|
|
98
102
|
theme: theme === 'light' ? 'github-light' : 'github-dark',
|
|
99
|
-
lang: file.lang ||
|
|
103
|
+
lang: file.lang || language
|
|
100
104
|
});
|
|
101
105
|
|
|
102
106
|
codeHTML = html;
|
|
@@ -105,235 +109,121 @@
|
|
|
105
109
|
});
|
|
106
110
|
</script>
|
|
107
111
|
|
|
108
|
-
<div class="repl
|
|
109
|
-
|
|
110
|
-
<div class="repl-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<div class="repl-toolbar--actions">
|
|
116
|
-
{#if viewMode !== 'editor'}
|
|
117
|
-
<button
|
|
118
|
-
class="repl-btn--icon"
|
|
119
|
-
onclick={() => (themeMode = themeMode === 'light' ? 'dark' : 'light')}
|
|
120
|
-
title="Toggle Theme"
|
|
121
|
-
>
|
|
122
|
-
{#if themeMode === 'light'}
|
|
123
|
-
<Moon class="toolbar-icon" />
|
|
124
|
-
{:else}
|
|
125
|
-
<Sun class="toolbar-icon" />
|
|
126
|
-
{/if}
|
|
127
|
-
</button>
|
|
128
|
-
{/if}
|
|
129
|
-
<button
|
|
130
|
-
class="repl-btn--icon"
|
|
131
|
-
title={viewMode === 'editor' ? 'Code' : 'Playground'}
|
|
132
|
-
onclick={() => (viewMode = viewMode === 'editor' ? 'playground' : 'editor')}
|
|
133
|
-
>
|
|
134
|
-
{#if viewMode === 'editor'}
|
|
135
|
-
<Code class="toolbar-icon" />
|
|
136
|
-
{:else}
|
|
137
|
-
<Codesandbox class="toolbar-icon" />
|
|
138
|
-
{/if}
|
|
139
|
-
</button>
|
|
140
|
-
<button
|
|
141
|
-
class="repl-btn--icon"
|
|
142
|
-
onclick={() => (copyState = true)}
|
|
143
|
-
title={copyState ? 'Copied!' : 'Copy'}
|
|
112
|
+
<div class="kit-repl">
|
|
113
|
+
{#if presentation}
|
|
114
|
+
<div class="kit-repl-content" class:kit-repl-content--playground={presentation}>
|
|
115
|
+
<div
|
|
116
|
+
class="wrapper-playground"
|
|
117
|
+
class:dark={themeState === 'dark'}
|
|
118
|
+
class:light={themeState === 'light'}
|
|
144
119
|
>
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
{:else}
|
|
148
|
-
<Copy class="toolbar-icon" />
|
|
149
|
-
{/if}
|
|
150
|
-
</button>
|
|
120
|
+
{@render children?.()}
|
|
121
|
+
</div>
|
|
151
122
|
</div>
|
|
152
|
-
|
|
123
|
+
{/if}
|
|
153
124
|
|
|
154
|
-
<
|
|
125
|
+
<div class="kit-repl-container">
|
|
126
|
+
<Toolbar
|
|
127
|
+
{title}
|
|
128
|
+
{language}
|
|
129
|
+
{presentation}
|
|
130
|
+
bind:copyState
|
|
131
|
+
bind:viewState
|
|
132
|
+
bind:themeState
|
|
133
|
+
bind:modeState
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
{#if modeState !== 'code'}
|
|
137
|
+
<hr />
|
|
138
|
+
{/if}
|
|
155
139
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
140
|
+
<Files {files} bind:activeIndex={activeFileIndex} {modeState} {viewState} />
|
|
141
|
+
|
|
142
|
+
<div
|
|
143
|
+
class="kit-repl-content"
|
|
144
|
+
class:kit-repl-content--code={viewState === 'code' && !presentation}
|
|
145
|
+
>
|
|
146
|
+
{#if viewState === 'code'}
|
|
147
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
148
|
+
<div class="kit-repl-wrapper-highlight" bind:this={ref}>{@html codeHTML}</div>
|
|
149
|
+
{:else}
|
|
150
|
+
<div
|
|
151
|
+
class="kit-repl-wrapper-playground"
|
|
152
|
+
class:dark={themeState === 'dark'}
|
|
153
|
+
class:light={themeState === 'light'}
|
|
164
154
|
>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class="file-icon"
|
|
169
|
-
/>
|
|
170
|
-
<span>{file.name}</span>
|
|
171
|
-
</button>
|
|
172
|
-
{/each}
|
|
155
|
+
{@render children?.()}
|
|
156
|
+
</div>
|
|
157
|
+
{/if}
|
|
173
158
|
</div>
|
|
174
|
-
<hr />
|
|
175
|
-
{/if}
|
|
176
|
-
|
|
177
|
-
<div class="repl-content">
|
|
178
|
-
{#if viewMode === 'editor'}
|
|
179
|
-
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
180
|
-
<div class="wrapper-highlight" bind:this={ref}>{@html codeHTML}</div>
|
|
181
|
-
{:else}
|
|
182
|
-
<div>{@render children?.()}</div>
|
|
183
|
-
{/if}
|
|
184
159
|
</div>
|
|
185
160
|
</div>
|
|
186
161
|
|
|
187
162
|
<style>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
--repl-
|
|
191
|
-
--repl-
|
|
192
|
-
--repl-shiki-tab-size: 2;
|
|
163
|
+
.kit-repl {
|
|
164
|
+
/* ui */
|
|
165
|
+
--kit-repl-spacing: 0.25rem;
|
|
166
|
+
--kit-repl-radius: 1rem;
|
|
193
167
|
|
|
194
|
-
|
|
195
|
-
--repl-
|
|
168
|
+
/* shiki override */
|
|
169
|
+
--kit-repl-shiki-size: 0.875rem;
|
|
170
|
+
--kit-repl-shiki-tab-size: 2;
|
|
196
171
|
|
|
197
|
-
|
|
198
|
-
--repl-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
border: 2px solid var(--repl-border-color);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
div.repl-container :global(pre) {
|
|
206
|
-
background-color: var(--repl-background) !important;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
div.repl-toolbar {
|
|
210
|
-
display: flex;
|
|
211
|
-
align-items: center;
|
|
212
|
-
justify-content: space-between;
|
|
213
|
-
gap: calc(var(--repl-spacing) * 3);
|
|
214
|
-
padding-left: calc(5 * var(--repl-spacing));
|
|
215
|
-
padding-right: calc(var(--repl-spacing) * 2);
|
|
216
|
-
padding-block: calc(var(--repl-spacing) * 1.5);
|
|
217
|
-
border-top-left-radius: var(--repl-radius);
|
|
218
|
-
border-top-right-radius: var(--repl-radius);
|
|
219
|
-
min-height: 36px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
div.repl-toolbar--actions {
|
|
223
|
-
display: flex;
|
|
224
|
-
align-items: center;
|
|
225
|
-
gap: calc(var(--repl-spacing) * 2);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.sub-toolbar button {
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
div button.repl-btn--icon {
|
|
232
|
-
background: none;
|
|
233
|
-
border: none;
|
|
234
|
-
cursor: pointer;
|
|
235
|
-
display: flex;
|
|
236
|
-
align-self: center;
|
|
237
|
-
justify-content: center;
|
|
238
|
-
font-size: 0.875rem;
|
|
239
|
-
border-radius: 0.375rem;
|
|
240
|
-
transition: background-color 0.2s ease;
|
|
241
|
-
padding: 8px;
|
|
242
|
-
color: var(--repl-secondary);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
div button.repl-btn--icon:hover {
|
|
246
|
-
color: var(--repl-primary);
|
|
172
|
+
/* colors */
|
|
173
|
+
--kit-repl-background: #f9f9f9;
|
|
174
|
+
--kit-repl-border-color: #ebebeb;
|
|
175
|
+
--kit-repl-primary: #0d0d34;
|
|
176
|
+
--kit-repl-secondary: #8f8f8f;
|
|
247
177
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
178
|
+
.kit-repl-container {
|
|
179
|
+
background-color: var(--kit-repl-background);
|
|
180
|
+
border-radius: var(--kit-repl-radius);
|
|
181
|
+
border: 2px solid var(--kit-repl-border-color);
|
|
252
182
|
}
|
|
253
183
|
|
|
254
|
-
.repl-
|
|
255
|
-
|
|
256
|
-
align-items: center;
|
|
257
|
-
gap: calc(var(--repl-spacing) * 2);
|
|
258
|
-
font-weight: 600;
|
|
259
|
-
color: var(--repl-secondary);
|
|
260
|
-
max-width: 80%;
|
|
261
|
-
min-width: 0;
|
|
184
|
+
.kit-repl-container :global(pre) {
|
|
185
|
+
background-color: var(--kit-repl-background) !important;
|
|
262
186
|
}
|
|
263
187
|
|
|
264
|
-
.repl-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
188
|
+
.kit-repl-content {
|
|
189
|
+
display: flow-root;
|
|
190
|
+
margin-top: calc(var(--kit-repl-spacing) * 0);
|
|
191
|
+
padding-right: calc(10 * var(--kit-repl-spacing));
|
|
192
|
+
padding-left: calc(5 * var(--kit-repl-spacing));
|
|
193
|
+
padding-bottom: calc(4 * var(--kit-repl-spacing));
|
|
194
|
+
padding-top: calc(3 * var(--kit-repl-spacing));
|
|
195
|
+
position: relative;
|
|
268
196
|
}
|
|
269
197
|
|
|
270
|
-
.repl-
|
|
271
|
-
|
|
272
|
-
height: 20px;
|
|
273
|
-
object-fit: contain;
|
|
274
|
-
flex-shrink: 0;
|
|
198
|
+
.kit-repl-content--code {
|
|
199
|
+
padding-top: 0;
|
|
275
200
|
}
|
|
276
201
|
|
|
277
|
-
.repl-content {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
padding-right: calc(10 * var(--repl-spacing));
|
|
281
|
-
padding-left: calc(5 * var(--repl-spacing));
|
|
282
|
-
padding-bottom: calc(4 * var(--repl-spacing));
|
|
283
|
-
padding-top: calc(3 * var(--repl-spacing));
|
|
284
|
-
position: relative;
|
|
202
|
+
.kit-repl-content--playground {
|
|
203
|
+
padding-top: calc(4 * var(--kit-repl-spacing));
|
|
204
|
+
padding-bottom: calc(10 * var(--kit-repl-spacing));
|
|
285
205
|
}
|
|
286
206
|
|
|
287
207
|
hr {
|
|
288
|
-
max-width: calc(100% -
|
|
289
|
-
margin-inline-start: calc(
|
|
208
|
+
max-width: calc(100% - 2.5rem);
|
|
209
|
+
margin-inline-start: calc(2.5rem / 2);
|
|
290
210
|
display: block;
|
|
291
|
-
border: thin solid var(--repl-border-color);
|
|
211
|
+
border: thin solid var(--kit-repl-border-color);
|
|
292
212
|
margin-top: 0;
|
|
293
213
|
margin-bottom: 0;
|
|
294
214
|
}
|
|
295
215
|
|
|
296
|
-
div.repl-container .wrapper-highlight :global(pre code) {
|
|
297
|
-
font-size: var(--repl-shiki-size);
|
|
298
|
-
-moz-tab-size: var(--repl-shiki-tab-size);
|
|
299
|
-
tab-size: var(--repl-shiki-tab-size);
|
|
216
|
+
div.kit-repl-container .kit-repl-wrapper-highlight :global(pre code) {
|
|
217
|
+
font-size: var(--kit-repl-shiki-size);
|
|
218
|
+
-moz-tab-size: var(--kit-repl-shiki-tab-size);
|
|
219
|
+
tab-size: var(--kit-repl-shiki-tab-size);
|
|
300
220
|
white-space: pre-wrap;
|
|
301
221
|
word-break: break-word;
|
|
302
222
|
}
|
|
303
223
|
|
|
304
|
-
.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
padding
|
|
308
|
-
padding-right: calc(5 * var(--repl-spacing));
|
|
309
|
-
padding-block: calc(var(--repl-spacing) * 2);
|
|
310
|
-
overflow-x: auto;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
.file-tab {
|
|
314
|
-
display: flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
gap: calc(var(--repl-spacing) * 2);
|
|
317
|
-
padding: calc(var(--repl-spacing) * 2) calc(var(--repl-spacing) * 3);
|
|
318
|
-
border-radius: 0.375rem;
|
|
319
|
-
font-size: 0.875rem;
|
|
320
|
-
transition: all 0.2s ease;
|
|
321
|
-
border: 1px solid transparent;
|
|
322
|
-
white-space: nowrap;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.file-tab:hover {
|
|
326
|
-
background-color: #f5f5f5;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.file-tab.active {
|
|
330
|
-
background-color: #f0f0f0;
|
|
331
|
-
border-color: #d0d0d0;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.file-icon {
|
|
335
|
-
width: 16px;
|
|
336
|
-
height: 16px;
|
|
337
|
-
object-fit: contain;
|
|
224
|
+
div.kit-repl-container .kit-repl-wrapper-playground {
|
|
225
|
+
background-color: var(--kit-repl-background);
|
|
226
|
+
border-radius: var(--kit-repl-radius);
|
|
227
|
+
padding: calc(4 * var(--kit-repl-spacing));
|
|
338
228
|
}
|
|
339
229
|
</style>
|
package/dist/Repl.svelte.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
content: any;
|
|
4
|
-
children: any;
|
|
5
|
-
}, {}, "">;
|
|
1
|
+
import type { ReplProps } from './types.js';
|
|
2
|
+
declare const Repl: import("svelte").Component<ReplProps, {}, "">;
|
|
6
3
|
type Repl = ReturnType<typeof Repl>;
|
|
7
4
|
export default Repl;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ToolbarProps } from './types.js';
|
|
3
|
+
import Button from './Button.svelte';
|
|
4
|
+
import { dictionary } from './utils.js';
|
|
5
|
+
import { Copy, Check, Code, Codesandbox, Moon, Sun } from '@lucide/svelte';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
title,
|
|
9
|
+
language,
|
|
10
|
+
presentation,
|
|
11
|
+
copyState = $bindable(),
|
|
12
|
+
viewState = $bindable(),
|
|
13
|
+
themeState = $bindable(),
|
|
14
|
+
modeState = $bindable()
|
|
15
|
+
}: ToolbarProps = $props();
|
|
16
|
+
|
|
17
|
+
let languageKey = $derived(
|
|
18
|
+
Object.entries(dictionary).find(([, values]) => values.includes(language))?.[0] || language
|
|
19
|
+
);
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div class="kit-repl--toolbar">
|
|
23
|
+
<div
|
|
24
|
+
class="kit-repl--toolbar-title"
|
|
25
|
+
class:kit-repl--toolbar-title--language={!title && language}
|
|
26
|
+
class:kit-repl--toolbar-title--title={title}
|
|
27
|
+
>
|
|
28
|
+
{#if title}
|
|
29
|
+
<span>{title}</span>
|
|
30
|
+
{:else if language}
|
|
31
|
+
<span>{languageKey}</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="kit-repl--toolbar-actions">
|
|
36
|
+
{#if (modeState !== 'code' && viewState === 'preview') || presentation}
|
|
37
|
+
<Button
|
|
38
|
+
aria-label={themeState === 'light' ? 'Switch to dark theme' : 'Switch to light theme'}
|
|
39
|
+
aria-pressed={themeState === 'dark'}
|
|
40
|
+
onclick={() => (themeState = themeState === 'light' ? 'dark' : 'light')}
|
|
41
|
+
>
|
|
42
|
+
{#if themeState === 'light'}
|
|
43
|
+
<Moon />
|
|
44
|
+
{:else}
|
|
45
|
+
<Sun />
|
|
46
|
+
{/if}
|
|
47
|
+
</Button>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
{#if modeState === 'mixed' && !presentation}
|
|
51
|
+
<Button
|
|
52
|
+
aria-label={viewState === 'code' ? 'Show preview' : 'Show code'}
|
|
53
|
+
aria-pressed={viewState === 'preview'}
|
|
54
|
+
onclick={() => (viewState = viewState === 'code' ? 'preview' : 'code')}
|
|
55
|
+
>
|
|
56
|
+
{#if viewState === 'code'}
|
|
57
|
+
<Code />
|
|
58
|
+
{:else}
|
|
59
|
+
<Codesandbox />
|
|
60
|
+
{/if}
|
|
61
|
+
</Button>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
64
|
+
{#if modeState !== 'playground'}
|
|
65
|
+
<Button
|
|
66
|
+
aria-label={copyState ? 'Code copied' : 'Copy code'}
|
|
67
|
+
onclick={() => (copyState = true)}
|
|
68
|
+
>
|
|
69
|
+
{#if copyState}
|
|
70
|
+
<Check />
|
|
71
|
+
{:else}
|
|
72
|
+
<Copy />
|
|
73
|
+
{/if}
|
|
74
|
+
</Button>
|
|
75
|
+
{/if}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
.kit-repl--toolbar {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
gap: calc(var(--kit-repl-spacing) * 3);
|
|
85
|
+
padding-left: calc(5 * var(--kit-repl-spacing));
|
|
86
|
+
padding-right: calc(var(--kit-repl-spacing) * 2);
|
|
87
|
+
padding-block: calc(var(--kit-repl-spacing) * 1.5);
|
|
88
|
+
border-top-left-radius: var(--kit-repl-radius);
|
|
89
|
+
border-top-right-radius: var(--kit-repl-radius);
|
|
90
|
+
min-height: 36px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.kit-repl--toolbar .kit-repl--toolbar-title {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: calc(var(--kit-repl-spacing) * 2);
|
|
97
|
+
max-width: 80%;
|
|
98
|
+
min-width: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.kit-repl--toolbar-title--language {
|
|
102
|
+
font-size: 0.875rem;
|
|
103
|
+
line-height: 16px;
|
|
104
|
+
font-weight: 400;
|
|
105
|
+
color: #5d5d5d;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.kit-repl--toolbar-title--title {
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
font-size: 1rem;
|
|
111
|
+
line-height: 20px;
|
|
112
|
+
color: #8f8f8f;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.kit-repl--toolbar .kit-repl--toolbar-actions {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: calc(var(--kit-repl-spacing) * 2);
|
|
119
|
+
}
|
|
120
|
+
</style>
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as KitRepl } from './Repl.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// components
|
|
2
|
-
export { default as
|
|
2
|
+
export { default as KitRepl } from './Repl.svelte';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export interface ReplProps {
|
|
3
|
+
title?: string;
|
|
4
|
+
content?: string | Record<string, string | Record<string, unknown>>;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
presentation?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolbarProps {
|
|
9
|
+
title?: string;
|
|
10
|
+
language: string;
|
|
11
|
+
copyState?: boolean;
|
|
12
|
+
presentation?: boolean;
|
|
13
|
+
viewState?: 'code' | 'preview';
|
|
14
|
+
themeState?: 'light' | 'dark';
|
|
15
|
+
modeState?: 'code' | 'playground' | 'mixed';
|
|
16
|
+
}
|
|
17
|
+
export interface FilesProps {
|
|
18
|
+
files?: FileItem[];
|
|
19
|
+
activeIndex: number;
|
|
20
|
+
viewState: 'code' | 'preview';
|
|
21
|
+
modeState: 'code' | 'playground' | 'mixed';
|
|
22
|
+
}
|
|
23
|
+
export interface FileItem {
|
|
24
|
+
name: string;
|
|
25
|
+
content: string;
|
|
26
|
+
lang?: string;
|
|
27
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import javascript from './languages/javascript.svg';
|
|
2
|
+
import typescript from './languages/typescript.svg';
|
|
3
|
+
import svelte from './languages/svelte.svg';
|
|
4
|
+
import css from './languages/css.svg';
|
|
5
|
+
import html from './languages/html.svg';
|
|
6
|
+
import shell from './languages/shell.svg';
|
|
1
7
|
export const copyToClipboard = (value) => {
|
|
2
8
|
if (navigator.clipboard && window.isSecureContext) {
|
|
3
9
|
navigator.clipboard
|
|
@@ -28,3 +34,28 @@ export const copyToClipboard = (value) => {
|
|
|
28
34
|
document.body.removeChild(zoneTexte);
|
|
29
35
|
}
|
|
30
36
|
};
|
|
37
|
+
export const dictionary = {
|
|
38
|
+
javascript: ['js', 'javascript'],
|
|
39
|
+
typescript: ['ts', 'typescript'],
|
|
40
|
+
svelte: ['svelte', 'svelte.ts', 'svelte.js'],
|
|
41
|
+
css: ['css', 'scss', 'less'],
|
|
42
|
+
html: ['html', 'htm'],
|
|
43
|
+
json: ['json'],
|
|
44
|
+
shell: ['bash', 'sh', 'shell']
|
|
45
|
+
};
|
|
46
|
+
export const dictionaryIcons = {
|
|
47
|
+
js: javascript,
|
|
48
|
+
ts: typescript,
|
|
49
|
+
'svelte.ts': svelte,
|
|
50
|
+
'svelte.js': svelte,
|
|
51
|
+
svelte: svelte,
|
|
52
|
+
shell: shell,
|
|
53
|
+
bash: shell,
|
|
54
|
+
sh: shell,
|
|
55
|
+
css: css,
|
|
56
|
+
scss: css,
|
|
57
|
+
less: css,
|
|
58
|
+
html: html,
|
|
59
|
+
htm: html,
|
|
60
|
+
json: shell
|
|
61
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lapikit/repl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/Nycolaide/lapikit.git"
|
|
14
|
+
"url": "git+https://github.com/Nycolaide/lapikit.git"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "vite dev",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-icon lucide-code"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-codesandbox-icon lucide-codesandbox"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="7.5 4.21 12 6.81 16.5 4.21"/><polyline points="7.5 19.79 7.5 14.6 3 12"/><polyline points="21 12 16.5 14.6 16.5 19.79"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" x2="12" y1="22.08" y2="12"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|