diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml
new file mode 100644
index 0000000..7440503
--- /dev/null
+++ b/.github/workflows/docker-hub.yml
@@ -0,0 +1,47 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Publish Docker image
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ push_to_registry:
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v2
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ with:
+ platforms: 'amd64,arm64'
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
+ with:
+ images: interaapps/pastefy-codebox
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a2922c4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea
+dist
+node_modules
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c7417c7
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,9 @@
+FROM oven/bun
+WORKDIR /usr/src/app
+
+COPY . .
+RUN bun install --production
+RUN bun build.ts
+
+EXPOSE 8000/tcp
+ENTRYPOINT [ "bun", "server.ts" ]
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5827461
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Pastefy Codebox
\ No newline at end of file
diff --git a/build.ts b/build.ts
new file mode 100644
index 0000000..261fb69
--- /dev/null
+++ b/build.ts
@@ -0,0 +1,18 @@
+// @ts-ignore
+import fs from 'fs'
+
+// @ts-ignore
+const result = await Bun.build({
+ entrypoints: ["./src/main.ts"],
+ outdir: "./dist",
+});
+
+fs.cpSync('./public', './dist', {recursive: true});
+
+if (!result.success) {
+ console.error("Build failed");
+ for (const message of result.logs) {
+ // Bun will pretty print the message object
+ console.error(message);
+ }
+}
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..bc88875
Binary files /dev/null and b/bun.lockb differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c75f8bb
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,70 @@
+{
+ "name": "pastefy-codebox",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@types/es6-promise": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-3.3.0.tgz",
+ "integrity": "sha512-ixCIAEkLUKv9movnHKCzx2rzAJgEnSALDXPrOSSwOjWwXFs0ssSZKan+O2e3FExPPCbX+DfA9NcKsbvLuyUlNA==",
+ "requires": {
+ "es6-promise": "*"
+ }
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "cajaxjs": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/cajaxjs/-/cajaxjs-3.0.6.tgz",
+ "integrity": "sha512-t3s0X+GflLJUHLZXuMOI56rxXw0icUGINnjGvXI9eG25cElQQsw04GiXp8JQuw9aVWoCpRiwSoessBC4gjNDqQ==",
+ "requires": {
+ "form-data": "^4.0.0"
+ }
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+ },
+ "es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+ },
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..bf43342
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "pastefy-codebox",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@types/es6-promise": "^3.3.0",
+ "@webcontainer/api": "^1.1.9",
+ "@xterm/xterm": "^5.4.0",
+ "cajaxjs": "3.0.7",
+ "highlight.js": "^11.9.0",
+ "hono": "^4.0.10",
+ "jdomjs": "3.1.11",
+ "petrel": "^1.0.7",
+ "sass": "^1.71.1"
+ }
+}
diff --git a/public/dmmono/300.svg b/public/dmmono/300.svg
new file mode 100644
index 0000000..f106546
--- /dev/null
+++ b/public/dmmono/300.svg
@@ -0,0 +1,312 @@
+
+
+
diff --git a/public/dmmono/300.ttf b/public/dmmono/300.ttf
new file mode 100644
index 0000000..3bc3676
Binary files /dev/null and b/public/dmmono/300.ttf differ
diff --git a/public/dmmono/300.woff b/public/dmmono/300.woff
new file mode 100644
index 0000000..96e53ab
Binary files /dev/null and b/public/dmmono/300.woff differ
diff --git a/public/dmmono/300.woff2 b/public/dmmono/300.woff2
new file mode 100644
index 0000000..42680bf
Binary files /dev/null and b/public/dmmono/300.woff2 differ
diff --git a/public/dmmono/300i.svg b/public/dmmono/300i.svg
new file mode 100644
index 0000000..626198e
--- /dev/null
+++ b/public/dmmono/300i.svg
@@ -0,0 +1,323 @@
+
+
+
diff --git a/public/dmmono/300i.ttf b/public/dmmono/300i.ttf
new file mode 100644
index 0000000..9f589ea
Binary files /dev/null and b/public/dmmono/300i.ttf differ
diff --git a/public/dmmono/300i.woff b/public/dmmono/300i.woff
new file mode 100644
index 0000000..85a4a61
Binary files /dev/null and b/public/dmmono/300i.woff differ
diff --git a/public/dmmono/300i.woff2 b/public/dmmono/300i.woff2
new file mode 100644
index 0000000..a06ca60
Binary files /dev/null and b/public/dmmono/300i.woff2 differ
diff --git a/public/dmmono/400.svg b/public/dmmono/400.svg
new file mode 100644
index 0000000..a445729
--- /dev/null
+++ b/public/dmmono/400.svg
@@ -0,0 +1,312 @@
+
+
+
diff --git a/public/dmmono/400.ttf b/public/dmmono/400.ttf
new file mode 100644
index 0000000..14f6b52
Binary files /dev/null and b/public/dmmono/400.ttf differ
diff --git a/public/dmmono/400.woff b/public/dmmono/400.woff
new file mode 100644
index 0000000..375d705
Binary files /dev/null and b/public/dmmono/400.woff differ
diff --git a/public/dmmono/400.woff2 b/public/dmmono/400.woff2
new file mode 100644
index 0000000..08929a9
Binary files /dev/null and b/public/dmmono/400.woff2 differ
diff --git a/public/dmmono/400i.svg b/public/dmmono/400i.svg
new file mode 100644
index 0000000..24be0c9
--- /dev/null
+++ b/public/dmmono/400i.svg
@@ -0,0 +1,322 @@
+
+
+
diff --git a/public/dmmono/400i.ttf b/public/dmmono/400i.ttf
new file mode 100644
index 0000000..47e87c3
Binary files /dev/null and b/public/dmmono/400i.ttf differ
diff --git a/public/dmmono/400i.woff b/public/dmmono/400i.woff
new file mode 100644
index 0000000..e59f846
Binary files /dev/null and b/public/dmmono/400i.woff differ
diff --git a/public/dmmono/400i.woff2 b/public/dmmono/400i.woff2
new file mode 100644
index 0000000..02b7e6b
Binary files /dev/null and b/public/dmmono/400i.woff2 differ
diff --git a/public/dmmono/500.svg b/public/dmmono/500.svg
new file mode 100644
index 0000000..1e09a9e
--- /dev/null
+++ b/public/dmmono/500.svg
@@ -0,0 +1,312 @@
+
+
+
diff --git a/public/dmmono/500.ttf b/public/dmmono/500.ttf
new file mode 100644
index 0000000..e44595a
Binary files /dev/null and b/public/dmmono/500.ttf differ
diff --git a/public/dmmono/500.woff b/public/dmmono/500.woff
new file mode 100644
index 0000000..493d19c
Binary files /dev/null and b/public/dmmono/500.woff differ
diff --git a/public/dmmono/500.woff2 b/public/dmmono/500.woff2
new file mode 100644
index 0000000..bf38b37
Binary files /dev/null and b/public/dmmono/500.woff2 differ
diff --git a/public/dmmono/500i.svg b/public/dmmono/500i.svg
new file mode 100644
index 0000000..b471764
--- /dev/null
+++ b/public/dmmono/500i.svg
@@ -0,0 +1,323 @@
+
+
+
diff --git a/public/dmmono/500i.ttf b/public/dmmono/500i.ttf
new file mode 100644
index 0000000..b72eac9
Binary files /dev/null and b/public/dmmono/500i.ttf differ
diff --git a/public/dmmono/500i.woff b/public/dmmono/500i.woff
new file mode 100644
index 0000000..a5fe9c3
Binary files /dev/null and b/public/dmmono/500i.woff differ
diff --git a/public/dmmono/500i.woff2 b/public/dmmono/500i.woff2
new file mode 100644
index 0000000..d28aff2
Binary files /dev/null and b/public/dmmono/500i.woff2 differ
diff --git a/public/dmmono/font.css b/public/dmmono/font.css
new file mode 100644
index 0000000..b32ae61
--- /dev/null
+++ b/public/dmmono/font.css
@@ -0,0 +1,61 @@
+/* Generated by fonts.intera.dev */
+@font-face {
+ font-family: 'DM Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 300;
+ src: url(/dmmono/300.woff2) format('woff2'),
+ url(/dmmono/300.woff) format('woff'),
+ url(/dmmono/300.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'DM Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/dmmono/400.woff2) format('woff2'),
+ url(/dmmono/400.woff) format('woff'),
+ url(/dmmono/400.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'DM Mono';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/dmmono/400i.woff2) format('woff2'),
+ url(/dmmono/400i.woff) format('woff'),
+ url(/dmmono/400i.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'DM Mono';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 300;
+ src: url(/dmmono/300i.woff2) format('woff2'),
+ url(/dmmono/300i.woff) format('woff'),
+ url(/dmmono/300i.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'DM Mono';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/dmmono/500.woff2) format('woff2'),
+ url(/dmmono/500.woff) format('woff'),
+ url(/dmmono/500.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'DM Mono';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/dmmono/500i.woff2) format('woff2'),
+ url(/dmmono/500i.woff) format('woff'),
+ url(/dmmono/500i.ttf) format('truetype');
+}
+
diff --git a/public/index.css b/public/index.css
new file mode 100644
index 0000000..febe2f2
--- /dev/null
+++ b/public/index.css
@@ -0,0 +1,65 @@
+@import url("/dmmono/font.css");
+@import url("/plusjakartasans/font.css");
+@import url("/test.css");
+@import url("/petrel/dark.css");
+@import url("/petrel/highlight.css");
+@import url("https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@2.47.0/tabler-icons.min.css");
+
+* {
+ font-family: "DM Mono", sans-serif;
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+
+ background: #212531;
+ color: #FFF;
+}
+
+app-root {
+ height: 100%;
+}
+
+.icon-button {
+ font-size: 16px;
+ border-radius: 20px;
+ padding: 2px;
+ cursor: pointer;
+
+ &:hover {
+ background: #ffffff55;
+ }
+}
+
+.btn {
+ padding: 7px 14px;
+ background: #FFFFFF22;
+ color: #FFF;
+ border: none;
+ border-radius: 8px;
+ margin-left: 6px;
+ cursor: pointer;
+
+ &.primary {
+ background: #3469ff;
+ }
+}
+
+.top-bar {
+ display: grid;
+ grid-template-columns: 300px auto 300px;
+ align-content: center;
+ align-items: center;
+ padding: 0 10px;
+ height: 44px;
+
+ .top-bar-buttons {
+ justify-self: right;
+ display: grid;
+ grid-auto-flow: column;
+ align-items: center;
+ }
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..73b267f
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/logged_in.html b/public/logged_in.html
new file mode 100644
index 0000000..f075c42
--- /dev/null
+++ b/public/logged_in.html
@@ -0,0 +1,18 @@
+Logging in...
+
+
\ No newline at end of file
diff --git a/public/petrel/base.css b/public/petrel/base.css
new file mode 100644
index 0000000..e4697a0
--- /dev/null
+++ b/public/petrel/base.css
@@ -0,0 +1,110 @@
+.petrel-code-editor {
+ overflow-wrap: normal;
+ overflow-x: auto;
+ position: relative;
+ font-family: monospace;
+}
+.petrel-code-editor * {
+ font-family: inherit;
+ line-height: inherit;
+}
+.petrel-code-editor textarea {
+ padding: 0px;
+ margin: 0px;
+ line-height: inherit;
+ background: transparent;
+ min-height: 100%;
+ overflow: hidden;
+ border: none;
+ outline: none;
+ width: 100%;
+ resize: none;
+ font-family: inherit;
+ color: transparent;
+ caret-color: #989898;
+ overflow-wrap: normal;
+ white-space: pre;
+ min-width: 100%;
+ padding-left: 0;
+ position: absolute;
+ top: 0;
+ font-size: inherit;
+ left: 0px;
+}
+
+.petrel-code-editor textarea::-moz-selection, .petrel-code-editor textarea::selection {
+ color: #000FFF00;
+ background: #FFFFFF21;
+ border-radius: 10px;
+}
+
+.petrel-code-editor pre {
+ padding: 0px;
+ margin: 0px;
+ user-select: none;
+ line-height: inherit;
+ position: absolute;
+ top: 0;
+ padding-left: 0;
+ pointer-events: none;
+ font-family: inherit;
+ font-size: inherit;
+}
+
+.petrel-code-editor .petrel-code-editor-line-numbering {
+ position: sticky;
+ min-height: 100%;
+ left: 0;
+
+ line-height: inherit;
+ float: left;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+ padding-right: 5px;
+ text-align: right;
+ z-index: 10;
+ background: #FFF;
+}
+
+.petrel-code-editor .petrel-code-editor-line-numbering span {
+ display: block;
+}
+
+
+.petrel-code-editor .petrel-code-editor-line-numbering span div {
+ height: 22px;
+ width: 100%;
+ left: 0px;
+ position: absolute;
+ background: #FFFFFF11;
+ margin-top: -1px;
+}
+
+.petrel-code-editor .petrel-code-editor-autocompletion {
+ display: block;
+ min-height: 35px;
+ min-width: 240px;
+ max-width: 400px;
+ top: 22px;
+ position: absolute;
+ left: 92px;
+ background: #FFF;
+}
+
+
+.petrel-code-editor .petrel-code-editor-autocompletion a {
+ cursor: pointer;
+ display: block;
+}
+
+.petrel-code-editor .petrel-code-editor-autocompletion a.selected {
+ background: #00000022;
+}
+.petrel-code-editor .petrel-code-editor-autocompletion a span {
+ position: absolute;
+ right: 10px;
+ opacity: 0.6;
+ text-transform: lowercase;
+ margin-left: 15px;
+}
\ No newline at end of file
diff --git a/public/petrel/dark.css b/public/petrel/dark.css
new file mode 100644
index 0000000..af84f7e
--- /dev/null
+++ b/public/petrel/dark.css
@@ -0,0 +1,69 @@
+@import url('./base.css');
+
+.petrel-code-editor {
+ background: #212531;
+ color: #FFFFFF;
+ caret-color: #FFF;
+ font-size: 16px;
+ line-height: 23px;
+ resize: vertical;
+ min-height: 140px;
+ border-radius: 10px;
+}
+
+.petrel-code-editor::-webkit-resizer {
+ background: #212531;
+ border-bottom-right-radius: 10px;
+}
+
+
+.petrel-code-editor textarea, .petrel-code-editor pre {
+ left: 20px;
+ top: 15px;
+}
+
+.petrel-code-editor .petrel-code-editor-line-numbering {
+ background: #212531;
+ padding-right: 9px;
+ border-right: 2px solid #FFFFFF11;
+ min-height: 100%;
+
+ padding-top: 15px;
+ padding-left: 15px;
+}
+
+
+.petrel-code-editor::-webkit-scrollbar {
+ width: 20px;
+ height: 20px;
+ background: #00000022;
+ border-radius: 20px;
+ border: 7px solid transparent;
+ background-clip: padding-box;
+}
+
+.petrel-code-editor::-webkit-scrollbar-thumb {
+ background: rgba(0,0,0,.3);
+ border-radius: 20px;
+ border: 7px solid transparent;
+ background-clip: padding-box;
+}
+
+.petrel-code-editor .petrel-code-editor-autocompletion {
+ background: #191c25AA;
+ border-radius: 10px;
+ backdrop-filter: blur(7px);
+ border: 2px solid #00000055;
+
+ max-height: 150px;
+ overflow: auto;
+}
+.petrel-code-editor .petrel-code-editor-autocompletion a {
+ padding: 4px 6px;
+ margin: 5px;
+ border-radius: 6px;
+ font-size: 16px;
+}
+.petrel-code-editor .petrel-code-editor-autocompletion a:hover {
+ background: #FFFFFF21;
+}
\ No newline at end of file
diff --git a/public/petrel/highlight.css b/public/petrel/highlight.css
new file mode 100644
index 0000000..d39cf5a
--- /dev/null
+++ b/public/petrel/highlight.css
@@ -0,0 +1,92 @@
+.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 0.5em;
+ background: transparent;
+}
+
+.hljs,
+.hljs-tag,
+.hljs-subst {
+ color: #ababab;
+}
+
+.hljs-strong,
+.hljs-emphasis {
+ color: #a8a8a2;
+}
+
+.hljs-bullet,
+.hljs-quote,
+.hljs-number,
+.hljs-regexp,
+.hljs-literal,
+.hljs-link {
+ color: #e1fc69;
+ font-weight: 500;
+}
+
+.hljs-code,
+.hljs-title,
+.hljs-section,
+.hljs-selector-class {
+ color: #26eb37;
+}
+
+ .hljs-strong {
+ font-weight: normal;
+}
+
+.hljs-emphasis {
+ font-style: italic;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-name {
+ color: #5c87ff;
+ font-weight: 500;
+}
+
+.hljs-keyword,
+.hljs-selector-class,
+.hljs-selector-tag {
+ font-weight: normal;
+}
+
+.hljs-symbol,
+.hljs-attribute,
+.hljs-attr {
+ color: #66d9ef;
+}
+
+.hljs-params,
+.hljs-class .hljs-title {
+ /*color: var(--highlight-params);*/
+}
+
+.hljs-type,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-selector-id,
+.hljs-selector-attr,
+.hljs-selector-pseudo,
+.hljs-addition,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-deletion {
+ color: #eb2668;
+ font-weight: 400;
+}
+
+.hljs-meta {
+ color: #eb2668EE;
+}
+
+.hljs-comment {
+ opacity: 0.5;
+}
+
+.hljs-string {
+ color: #69d8a0;
+}
\ No newline at end of file
diff --git a/public/plusjakartasans/PlusJakartaSans-Bold.ttf b/public/plusjakartasans/PlusJakartaSans-Bold.ttf
new file mode 100644
index 0000000..d000c02
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Bold.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Bold.woff b/public/plusjakartasans/PlusJakartaSans-Bold.woff
new file mode 100644
index 0000000..5d93299
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Bold.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Bold.woff2 b/public/plusjakartasans/PlusJakartaSans-Bold.woff2
new file mode 100644
index 0000000..a50bc44
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Bold.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-BoldItalic.ttf b/public/plusjakartasans/PlusJakartaSans-BoldItalic.ttf
new file mode 100644
index 0000000..31b3aeb
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-BoldItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff b/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff
new file mode 100644
index 0000000..e3f29ad
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff2
new file mode 100644
index 0000000..8cea92d
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-BoldItalic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBold.ttf b/public/plusjakartasans/PlusJakartaSans-ExtraBold.ttf
new file mode 100644
index 0000000..0cb6016
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBold.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff b/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff
new file mode 100644
index 0000000..b1e3225
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff2 b/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff2
new file mode 100644
index 0000000..f938403
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBold.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.ttf b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..4e7c5c7
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff
new file mode 100644
index 0000000..7208504
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff2
new file mode 100644
index 0000000..134b7a1
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLight.ttf b/public/plusjakartasans/PlusJakartaSans-ExtraLight.ttf
new file mode 100644
index 0000000..c13a67a
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLight.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff b/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff
new file mode 100644
index 0000000..f6e1b8c
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff2 b/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff2
new file mode 100644
index 0000000..f36474f
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLight.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.ttf b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.ttf
new file mode 100644
index 0000000..d9dda7f
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff
new file mode 100644
index 0000000..ea4c78e
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff2
new file mode 100644
index 0000000..6d65051
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Italic.ttf b/public/plusjakartasans/PlusJakartaSans-Italic.ttf
new file mode 100644
index 0000000..9763a78
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Italic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Italic.woff b/public/plusjakartasans/PlusJakartaSans-Italic.woff
new file mode 100644
index 0000000..068fd9a
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Italic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Italic.woff2 b/public/plusjakartasans/PlusJakartaSans-Italic.woff2
new file mode 100644
index 0000000..8c234f4
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Italic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Light.ttf b/public/plusjakartasans/PlusJakartaSans-Light.ttf
new file mode 100644
index 0000000..8a0e456
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Light.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Light.woff b/public/plusjakartasans/PlusJakartaSans-Light.woff
new file mode 100644
index 0000000..04ad6e1
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Light.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Light.woff2 b/public/plusjakartasans/PlusJakartaSans-Light.woff2
new file mode 100644
index 0000000..da3d815
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Light.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-LightItalic.ttf b/public/plusjakartasans/PlusJakartaSans-LightItalic.ttf
new file mode 100644
index 0000000..b737e3d
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-LightItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-LightItalic.woff b/public/plusjakartasans/PlusJakartaSans-LightItalic.woff
new file mode 100644
index 0000000..0f07808
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-LightItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-LightItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-LightItalic.woff2
new file mode 100644
index 0000000..7a87f10
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-LightItalic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Medium.ttf b/public/plusjakartasans/PlusJakartaSans-Medium.ttf
new file mode 100644
index 0000000..32b1643
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Medium.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Medium.woff b/public/plusjakartasans/PlusJakartaSans-Medium.woff
new file mode 100644
index 0000000..11bcbb0
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Medium.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Medium.woff2 b/public/plusjakartasans/PlusJakartaSans-Medium.woff2
new file mode 100644
index 0000000..88a249f
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Medium.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-MediumItalic.ttf b/public/plusjakartasans/PlusJakartaSans-MediumItalic.ttf
new file mode 100644
index 0000000..84c8e85
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-MediumItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff b/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff
new file mode 100644
index 0000000..050f9c3
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff2
new file mode 100644
index 0000000..ad93bf6
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-MediumItalic.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Regular.ttf b/public/plusjakartasans/PlusJakartaSans-Regular.ttf
new file mode 100644
index 0000000..63b61d9
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Regular.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Regular.woff b/public/plusjakartasans/PlusJakartaSans-Regular.woff
new file mode 100644
index 0000000..f47a63d
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Regular.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-Regular.woff2 b/public/plusjakartasans/PlusJakartaSans-Regular.woff2
new file mode 100644
index 0000000..ce77de7
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-Regular.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBold.ttf b/public/plusjakartasans/PlusJakartaSans-SemiBold.ttf
new file mode 100644
index 0000000..7c184ae
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBold.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBold.woff b/public/plusjakartasans/PlusJakartaSans-SemiBold.woff
new file mode 100644
index 0000000..5637ca5
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBold.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBold.woff2 b/public/plusjakartasans/PlusJakartaSans-SemiBold.woff2
new file mode 100644
index 0000000..8255163
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBold.woff2 differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.ttf b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.ttf
new file mode 100644
index 0000000..7159d31
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.ttf differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff
new file mode 100644
index 0000000..9575008
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff differ
diff --git a/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff2 b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff2
new file mode 100644
index 0000000..c97f5eb
Binary files /dev/null and b/public/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff2 differ
diff --git a/public/plusjakartasans/font.css b/public/plusjakartasans/font.css
new file mode 100644
index 0000000..9355a4e
--- /dev/null
+++ b/public/plusjakartasans/font.css
@@ -0,0 +1,141 @@
+/* Generated by fonts.intera.dev */
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 800;
+ src: url(/plusjakartasans/PlusJakartaSans-ExtraBold.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraBold.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraBold.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 800;
+ src: url(/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraBoldItalic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 700;
+ src: url(/plusjakartasans/PlusJakartaSans-Bold.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-Bold.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-Bold.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 700;
+ src: url(/plusjakartasans/PlusJakartaSans-BoldItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-BoldItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-BoldItalic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 600;
+ src: url(/plusjakartasans/PlusJakartaSans-SemiBold.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-SemiBold.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-SemiBold.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 600;
+ src: url(/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-SemiBoldItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-SemiBoldItalic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/plusjakartasans/PlusJakartaSans-Medium.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-Medium.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-Medium.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/plusjakartasans/PlusJakartaSans-MediumItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-MediumItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-MediumItalic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/plusjakartasans/PlusJakartaSans-Regular.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-Regular.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-Regular.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/plusjakartasans/PlusJakartaSans-Italic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-Italic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-Italic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 300;
+ src: url(/plusjakartasans/PlusJakartaSans-Light.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-Light.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-Light.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 300;
+ src: url(/plusjakartasans/PlusJakartaSans-LightItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-LightItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-LightItalic.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 200;
+ src: url(/plusjakartasans/PlusJakartaSans-ExtraLight.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraLight.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraLight.ttf) format('truetype');
+}
+
+@font-face {
+ font-family: 'Plus Jakarta Sans';
+ font-style: italic;
+ font-display: swap;
+ font-weight: 200;
+ src: url(/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff2) format('woff2'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraLightItalic.woff) format('woff'),
+ url(/plusjakartasans/PlusJakartaSans-ExtraLightItalic.ttf) format('truetype');
+}
+
diff --git a/public/test.css b/public/test.css
new file mode 100644
index 0000000..e8d36bb
--- /dev/null
+++ b/public/test.css
@@ -0,0 +1,182 @@
+
+.xterm {
+ cursor: text;
+ position: relative;
+ user-select: none;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+}
+
+.xterm.focus,
+.xterm:focus {
+ outline: none;
+}
+
+.xterm .xterm-helpers {
+ position: absolute;
+ top: 0;
+ /**
+ * The z-index of the helpers must be higher than the canvases in order for
+ * IMEs to appear on top.
+ */
+ z-index: 5;
+}
+
+.xterm .xterm-helper-textarea {
+ padding: 0;
+ border: 0;
+ margin: 0;
+ /* Move textarea out of the screen to the far left, so that the cursor is not visible */
+ position: absolute;
+ opacity: 0;
+ left: -9999em;
+ top: 0;
+ width: 0;
+ height: 0;
+ z-index: -5;
+ /** Prevent wrapping so the IME appears against the textarea at the correct position */
+ white-space: nowrap;
+ overflow: hidden;
+ resize: none;
+}
+
+.xterm .composition-view {
+ /* TODO: Composition position got messed up somewhere */
+ background: #000;
+ color: #FFF;
+ display: none;
+ position: absolute;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.xterm .composition-view.active {
+ display: block;
+}
+
+.xterm .xterm-viewport {
+ /* On OS X this is required in order for the scroll bar to appear fully opaque */
+ background-color: #000;
+ overflow-y: scroll;
+ cursor: default;
+ position: absolute;
+ right: 0;
+ left: 0;
+ top: 0;
+ bottom: 0;
+}
+
+.xterm .xterm-screen {
+ position: relative;
+}
+
+.xterm .xterm-screen canvas {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.xterm .xterm-scroll-area {
+ visibility: hidden;
+}
+
+.xterm-char-measure-element {
+ display: inline-block;
+ visibility: hidden;
+ position: absolute;
+ top: 0;
+ left: -9999em;
+ line-height: normal;
+}
+
+.xterm.enable-mouse-events {
+ /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
+ cursor: default;
+}
+
+.xterm.xterm-cursor-pointer,
+.xterm .xterm-cursor-pointer {
+ cursor: pointer;
+}
+
+.xterm.column-select.focus {
+ /* Column selection mode */
+ cursor: crosshair;
+}
+
+.xterm .xterm-accessibility:not(.debug),
+.xterm .xterm-message {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+ color: transparent;
+ pointer-events: none;
+}
+
+.xterm .xterm-accessibility-tree:not(.debug) *::selection {
+ color: transparent;
+}
+
+.xterm .xterm-accessibility-tree {
+ user-select: text;
+ white-space: pre;
+}
+
+.xterm .live-region {
+ position: absolute;
+ left: -9999px;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+}
+
+.xterm-dim {
+ /* Dim should not apply to background, so the opacity of the foreground color is applied
+ * explicitly in the generated class and reset to 1 here */
+ opacity: 1 !important;
+}
+
+.xterm-underline-1 { text-decoration: underline; }
+.xterm-underline-2 { text-decoration: double underline; }
+.xterm-underline-3 { text-decoration: wavy underline; }
+.xterm-underline-4 { text-decoration: dotted underline; }
+.xterm-underline-5 { text-decoration: dashed underline; }
+
+.xterm-overline {
+ text-decoration: overline;
+}
+
+.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
+.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
+.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
+.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
+.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
+
+.xterm-strikethrough {
+ text-decoration: line-through;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration {
+ z-index: 6;
+ position: absolute;
+}
+
+.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
+ z-index: 7;
+}
+
+.xterm-decoration-overview-ruler {
+ z-index: 8;
+ position: absolute;
+ top: 0;
+ right: 0;
+ pointer-events: none;
+}
+
+.xterm-decoration-top {
+ z-index: 2;
+ position: relative;
+}
diff --git a/server.ts b/server.ts
new file mode 100644
index 0000000..645dd7a
--- /dev/null
+++ b/server.ts
@@ -0,0 +1,30 @@
+import { Hono } from "hono";
+import { serveStatic } from "hono/bun";
+
+const app = new Hono()
+
+app.get('/dmmono/*', serveStatic({ root: './dist/dmmono', }))
+
+app.get('*', (r, b) => {
+ r.header('Cross-Origin-Opener-Policy', 'same-origin');
+ r.header('Cross-Origin-Embedder-Policy', 'require-corp');
+ r.header('Cross-Origin-Resource-Policy', 'cross-origin');
+
+ return serveStatic({ root: './dist',})(r, b)
+})
+
+app.get('*', (r, b) => {
+ r.header('Cross-Origin-Opener-Policy', 'same-origin');
+ r.header('Cross-Origin-Embedder-Policy', 'require-corp');
+ r.header('Cross-Origin-Resource-Policy', 'cross-origin');
+
+ return serveStatic({
+ path: './dist/index.html',
+ })(r, b)
+})
+
+
+Bun.serve({
+ port: 8000,
+ fetch: app.fetch
+})
\ No newline at end of file
diff --git a/src/App.ts b/src/App.ts
new file mode 100644
index 0000000..087f628
--- /dev/null
+++ b/src/App.ts
@@ -0,0 +1,20 @@
+import { JDOMComponent, html } from 'jdomjs'
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+import Router from "jdomjs/src/router/Router.js";
+
+@CustomElement("app-root")
+export default class App extends JDOMComponent {
+ router: Router
+
+ constructor() {
+ super({ shadowed: false });
+ }
+
+ render() {
+ return html`
+
+ ${this.router.view}
+
+ `
+ }
+}
\ No newline at end of file
diff --git a/src/IAOauth.js b/src/IAOauth.js
new file mode 100644
index 0000000..2e8b918
--- /dev/null
+++ b/src/IAOauth.js
@@ -0,0 +1,88 @@
+class IAOAuth2 {
+ constructor(clientId){
+ this.clientId = clientId
+ this.redirectUrl = window.location.origin+"/callback"
+ this.scopes = []
+ this.state = ""
+ }
+
+ setState(s) {
+ this.state = s
+ return this
+ }
+
+ addScope(scope) {
+ this.scopes.push(scope)
+ return this
+ }
+
+ setRedirect(redirect){
+ if (redirect.includes("://"))
+ this.redirectUrl = redirect
+ else
+ this.redirectUrl = window.location.origin+(redirect.startsWith("/") ? "" : window.location.pathname.replace(/[^\\/]*$/, ''))+redirect
+
+ return this
+ }
+
+ buildURL(){
+ return "https://accounts.interaapps.de/auth/oauth2"+
+ "?client_id="+encodeURIComponent(this.clientId)
+ +"&redirect_uri="+encodeURIComponent(this.redirectUrl)
+ +"&scope="+encodeURIComponent(this.scopes.join(" "))
+ +"&response_type=token"
+ +(this.state != "" ? "&state="+encodeURIComponent(this.state) : '')
+ }
+
+ open(newTab = false){
+ if (newTab)
+ window.open(this.buildURL())
+ else
+ window.location = this.buildURL()
+ }
+
+ openInNewWindow(fakeURL = "/ia_fakecallback/success", checkPath=null){
+ if (!checkPath)
+ checkPath = fakeURL
+ return new Promise((resolve, reject)=>{
+ this.redirectUrl = window.location.origin+fakeURL
+ const newWindow = window.open(this.buildURL(), "Authorize", "width=400,height=500")
+ if(!newWindow || newWindow.closed || typeof newWindow.closed=='undefined')
+ {
+ this.open()
+ return;
+ }
+ let interval;
+
+ interval = setInterval(()=>{
+ if (newWindow.closed) {
+ clearInterval(interval)
+ console.log('REJECT')
+ reject()
+ return;
+ }
+ if (newWindow.location.pathname.startsWith(checkPath)) {
+ console.log("DONE!", newWindow);
+ newWindow.close()
+
+ let params = {}
+
+ const paramsString = newWindow.location.hash ? newWindow.location.hash.substring(1) : newWindow.location.search.split("?")[1]
+
+
+ for (const paramString of paramsString.split("&")) {
+ const splitParamString = paramString.split("=")
+ params[decodeURIComponent(splitParamString[0])] = decodeURIComponent(splitParamString[1])
+ }
+
+ resolve(params)
+
+ clearInterval(interval)
+ }
+ console.log(interval);
+ }, 200)
+ })
+ }
+}
+
+export default IAOAuth2;
\ No newline at end of file
diff --git a/src/components/Logo.ts b/src/components/Logo.ts
new file mode 100644
index 0000000..437ee53
--- /dev/null
+++ b/src/components/Logo.ts
@@ -0,0 +1,54 @@
+import { css, html, JDOMComponent } from 'jdomjs'
+
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+import { router } from "../main.ts";
+
+
+@CustomElement("pastefy-code-box-logo")
+export default class Logo extends JDOMComponent.unshadowed {
+ render() {
+ return html`
+ router.go('/') } class="pastefy-box-logo">
+
+ Pastefy CodeBox
+
+ `
+ }
+
+ styles(): string {
+
+ /*@language css*/
+ return css`
+ .pastefy-box-logo {
+ height: 100%;
+ width: fit-content;
+ cursor: pointer;
+ display: grid;
+ grid-auto-flow: column;
+ align-items: center;
+ margin-left: -5px;
+ border-radius: 7px;
+ padding: 0 5px;
+
+ i {
+ margin-right: 10px;
+ font-size: 30px;
+ display: inline-block;
+ }
+ span {
+ display: inline-block;
+ font-size: 19px;
+ &, b {
+ font-family: 'Plus Jakarta Sans', 'DM Mono';
+ }
+ b {
+ font-weight: 600;
+ }
+ }
+ &:hover {
+ background: #FFFFFF11;
+ }
+ }
+ `;
+ }
+}
\ No newline at end of file
diff --git a/src/components/TopBar.ts b/src/components/TopBar.ts
new file mode 100644
index 0000000..a04b6eb
--- /dev/null
+++ b/src/components/TopBar.ts
@@ -0,0 +1,27 @@
+import { css, html, JDOMComponent } from 'jdomjs'
+
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+import { router } from "../main.ts";
+import UserProfile from "./UserProfile.ts";
+import Logo from "./Logo.ts";
+
+
+@CustomElement("pastefy-top-bar")
+export default class TopBar extends JDOMComponent.unshadowed {
+ render() {
+ return html`
+
+ <${Logo} />
+
+
+
+
+
+
+
+ <${UserProfile} />
+
+
+ `
+ }
+}
\ No newline at end of file
diff --git a/src/components/UserProfile.ts b/src/components/UserProfile.ts
new file mode 100644
index 0000000..a442260
--- /dev/null
+++ b/src/components/UserProfile.ts
@@ -0,0 +1,41 @@
+import {computed, css, html, JDOMComponent} from 'jdomjs'
+
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+import { router } from "../main.ts";
+import {login, user} from "../user.ts";
+import {scss} from "../scss.ts";
+
+
+@CustomElement("pastefy-code-user-profile")
+export default class UserProfile extends JDOMComponent.unshadowed {
+ render() {
+ return html`
+ ${computed(() => user.value
+ ? html`
+
+
+
+ `
+ : html``,
+ [user])}
+ `
+ }
+
+ styles(): string {
+
+ /*@language css*/
+ return scss`
+ .profile {
+ display: inline-block;
+ margin-left: 10px;
+ img {
+ display: block;
+ height: 30px;
+ width: 30px;
+ border-radius: 40px;
+ object-fit: cover;
+ }
+ }
+ `;
+ }
+}
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..e463d89
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,25 @@
+import App from "./App";
+import { $, html } from "jdomjs";
+
+import Router from "jdomjs/src/router/Router.js";
+import routes from "./routes";
+import { Cajax } from 'cajaxjs'
+import { initUser } from './user.ts'
+
+export const router = new Router(routes);
+
+export const api = new Cajax('https://pastefy.app/api/v2')
+console.log(api)
+const session = localStorage['box_session']
+
+if (session) {
+ api.bearer(session)
+}
+
+api.promiseInterceptor = r => r.json()
+initUser()
+
+html`<${App} router=${router} />`
+ .appendTo(document);
+
+router.init();
diff --git a/src/routes.ts b/src/routes.ts
new file mode 100644
index 0000000..1be0b56
--- /dev/null
+++ b/src/routes.ts
@@ -0,0 +1,17 @@
+import { html } from "jdomjs";
+
+import HomeView from "./views/HomeView";
+import EditorView from "./views/editor/EditorView.ts";
+
+export default [
+ {
+ path: "/",
+ name: "home",
+ view: () => new HomeView(),
+ },
+ {
+ path: "/:paste",
+ name: "editor",
+ view: () => html`<${EditorView} test.attr="YOo" />`,
+ },
+];
diff --git a/src/scss.ts b/src/scss.ts
new file mode 100644
index 0000000..1dacd92
--- /dev/null
+++ b/src/scss.ts
@@ -0,0 +1,14 @@
+import { compileString } from 'sass'
+
+export function scss(strings, ...values) {
+ let out = ''
+ let i = 0
+ for (const str of strings) {
+ out += str
+ if (values[i]) {
+ out += values[i]
+ i++
+ }
+ }
+ return compileString(out).css
+}
\ No newline at end of file
diff --git a/src/user.ts b/src/user.ts
new file mode 100644
index 0000000..ca1a302
--- /dev/null
+++ b/src/user.ts
@@ -0,0 +1,32 @@
+import IAOauth from "./IAOauth.js";
+import {state} from "jdomjs";
+import { api } from "./main.ts";
+
+const session = localStorage['box_session']
+
+export const user = state(null)
+
+user.addListener(u => {
+ console.log('LOGGED_IN', u)
+})
+
+export async function initUser() {
+ const userObj = await api.get('/user')
+ if (userObj.logged_in) {
+ user.value = userObj
+ }
+}
+
+export async function login(){
+ const url = new URL(window.location.toString())
+ url.pathname = '/logged_in.html'
+
+ new IAOauth('yio57t9r9tsgmmf')
+ .addScope("user:read")
+ .addScope("pastefy|pastes")
+ .addScope("pastefy|folders")
+ .addScope("pastefy|notifications")
+ .setState(window.location.toString())
+ .setRedirect(url.toString())
+ .open()
+}
\ No newline at end of file
diff --git a/src/views/HomeView.ts b/src/views/HomeView.ts
new file mode 100644
index 0000000..e9260bb
--- /dev/null
+++ b/src/views/HomeView.ts
@@ -0,0 +1,141 @@
+import {computed, css, html, JDOMComponent, state} from 'jdomjs'
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+import {api, router} from "../main";
+import Logo from "../components/Logo.ts";
+import {ForEach} from "jdomjs/src/template/helper/components.js";
+import configs from "./editor/configs";
+import {user} from "../user.ts";
+import UserProfile from "../components/UserProfile.ts";
+import TopBar from "../components/TopBar.ts";
+import {scss} from "../scss.ts";
+
+@CustomElement("home-view")
+export default class HomeView extends JDOMComponent.unshadowed {
+ latestPastes = state([])
+
+
+ transformPasteRequest(pastes) {
+ pastes.forEach(p => {
+ p.tags.forEach(t => {
+ if (t.startsWith('codebox-type-')) {
+ p.config = configs[t.replace('codebox-type-', '')]
+ }
+ })
+ })
+ }
+
+ loadUserPastes() {
+ api.get('/user/pastes?filter_tags=codebox&shorten_content=true')
+ .then(pastes => {
+ if (Array.isArray(pastes)) {
+ this.transformPasteRequest(pastes)
+ this.latestPastes.value = pastes
+ }
+ })
+ }
+
+ render() {
+ if (user.value) {
+ this.loadUserPastes()
+ }
+
+ user.addListener(() => {
+ this.loadUserPastes()
+ })
+
+ const publicPastes = state([])
+ api.get('/public-pastes?filter_tags=codebox&shorten_content=true')
+ .then(pastes => {
+ this.transformPasteRequest(pastes)
+ publicPastes.value = pastes
+ })
+
+
+ return html`
+ <${TopBar} />
+
+
Latest Projects
+
+ <${ForEach}
+ value=${this.latestPastes}
+ content=${(paste) => html`
+
router.go(`/${paste.id}`)}>
+
+ ${paste.title}
+
+ `}
+ />
+
+
New Project
+
+
+
+
+
publicPastes.value.length, [publicPastes])}>Latest Public Projects
+
+ `
+ }
+
+ styles(): string {
+ // language=SCSS
+ return scss`
+ .home-page {
+ padding: 7px 10px;
+ h2 {
+ margin-bottom: 10px;
+ margin-top: 30px;
+ }
+ .projects {
+ .project {
+ border: 2px solid #FFFFFF33;
+ border-radius: 8px;
+ display: inline-block;
+ text-align: center;
+ padding: 14px;
+ cursor: pointer;
+ width: 200px;
+
+ margin-right: 10px;
+ margin-bottom: 10px;
+
+ i {
+ font-size: 44px;
+ display: block;
+ }
+
+ span {
+ display: block;
+ margin-top: 10px;
+ font-size: 18px;
+ font-family: "Plus Jakarta Sans";
+ }
+
+ &:hover {
+ background: #FFFFFF11;
+ }
+ }
+ }
+ }
+ `;
+ }
+}
\ No newline at end of file
diff --git a/src/views/editor/CodeEditorPart.ts b/src/views/editor/CodeEditorPart.ts
new file mode 100644
index 0000000..ba75114
--- /dev/null
+++ b/src/views/editor/CodeEditorPart.ts
@@ -0,0 +1,152 @@
+import {css, Hook, html, JDOMComponent, state} from 'jdomjs'
+
+import {CustomElement, State} from 'jdomjs/src/decorators.ts'
+
+import { CodeEditor } from 'petrel/index.js'
+import { JavaScriptAutoComplete, DockerfileAutoComplete, HTMLAutoComplete, JSONAutoComplete, JavaAutoComplete, MarkdownAutoComplete, PHPAutoComplete, SQLAutoComplete, YAMLAutoComplete } from 'petrel/autocompletions.js'
+import hljs from 'highlight.js'
+import LANGUAGE_REPLACEMENTS from './langReplacements.ts'
+const LANGUAGES = hljs.listLanguages()
+
+const AUTOCOMPLETIONS = [
+ {language: "javascript", file: JavaScriptAutoComplete},
+ {language: "dockerfile", file: DockerfileAutoComplete},
+ {language: "html", file: HTMLAutoComplete},
+ {language: "json", file: JSONAutoComplete},
+ {language: "java", file: JavaAutoComplete},
+ {language: "markdown", file: MarkdownAutoComplete},
+ {language: "php", file: PHPAutoComplete},
+ {language: "sql", file: SQLAutoComplete},
+ {language: "yaml", file: YAMLAutoComplete},
+]
+const DEFAULT_HIGHLIGHTER = v => v.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'")
+
+@CustomElement("editor-editor-part")
+export default class CodeEditorPart extends JDOMComponent {
+ @State()
+ selected = state
({})
+
+ codeEditor = new CodeEditor(null)
+
+ currentLanguage = ''
+
+ selectListener = null
+
+ timer: number = null
+
+ constructor() {
+ super({
+ shadowed: false
+ });
+ }
+
+ detach() {
+ if (this.timer !== null) {
+ clearInterval(this.timer)
+ }
+ }
+
+ updateEditorLang() {
+ console.log('huh', this.selected.value.name)
+ if (!this.selected.value.name || (this.selected.value.name instanceof Hook))
+ return;
+ let language;
+ const split = this.selected.value.name.split(".")
+ let isHTML = false
+ if (split.length > 1) {
+ language = split[split.length - 1]
+ if (language == "html" || language == "htm")
+ isHTML = true
+
+ for (const name in LANGUAGE_REPLACEMENTS) {
+ if (language == name) {
+ language = LANGUAGE_REPLACEMENTS[name]
+ break;
+ }
+ }
+ } else if (split[0] == 'Dockerfile')
+ language = 'dockerfile'
+
+
+ if (this.currentLanguage != language) {
+ this.currentLanguage = language
+ if (LANGUAGES.includes(language)) {
+ this.codeEditor.setHighlighter(c => hljs.highlight(language, c).value)
+
+ this.codeEditor.setAutoCompleteHandler(null)
+ for (const autocompletion of AUTOCOMPLETIONS) {
+ if (autocompletion.language == language || (autocompletion.language == "html" && isHTML)) {
+ (async () => {
+ this.codeEditor.setAutoCompleteHandler(new autocompletion.file)
+ })()
+ }
+ }
+ }
+ this.codeEditor.update()
+ }
+
+
+ if (this.codeEditor.value.length > 7000) {
+ this.codeEditor.setAutoCompleteHandler(null)
+ if (this.codeEditor.value.length > 11000) {
+ this.codeEditor.setHighlighter(DEFAULT_HIGHLIGHTER)
+ }
+ }
+ }
+
+ dettach() {
+ this.selected.removeListener(this.selectListener)
+ }
+
+ render() {
+ const element = document.createElement('div')
+ this.codeEditor.parentElement = element
+
+ this.selectListener = this.selected.addListener(v => {
+ this.updateEditorLang()
+ if (this.codeEditor.value !== v.contents) {
+ this.codeEditor.setValue(v.contents)
+ }
+ })
+
+ let changed = false
+ this.codeEditor.textAreaElement.addEventListener('keydown', (e: KeyboardEvent) => {
+ if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
+ this.dispatchEvent(new CustomEvent('save_paste'))
+ e.stopPropagation()
+ e.preventDefault()
+ }
+ })
+ this.codeEditor.textAreaElement.addEventListener('keyup', e => {
+ this.selected.value.contents = this.codeEditor.value
+ changed = true
+ })
+
+ this.timer = setInterval(() => {
+ if (changed) {
+ this.dispatchEvent(new CustomEvent('changed'))
+ changed = false
+ }
+ }, 1000)
+
+ this.codeEditor.setHighlighter(code => hljs.highlight("javascript", code).value)
+ if (this.selected.value.contents)
+ this.codeEditor.setValue(this.selected.value.contents)
+
+ setTimeout(() => this.codeEditor.update(), 200)
+
+ this.codeEditor.setAutoCompleteHandler(new JavaScriptAutoComplete())
+ this.codeEditor.create()
+ this.updateEditorLang()
+
+ return html`${ element }`
+ }
+
+ styles(): string {
+ return css`
+ .petrel-code-editor {
+ height: calc(100% - 40px);
+ }
+ `;
+ }
+}
\ No newline at end of file
diff --git a/src/views/editor/EditorView.ts b/src/views/editor/EditorView.ts
new file mode 100644
index 0000000..9368af7
--- /dev/null
+++ b/src/views/editor/EditorView.ts
@@ -0,0 +1,519 @@
+import {computed, css, html, JDOMComponent, state} from 'jdomjs'
+
+import {Attribute, CustomElement, State} from 'jdomjs/src/decorators.ts'
+import { Awaiting, ForEach } from 'jdomjs/src/template/helper/components.js'
+import {api, router} from "../../main.ts";
+import { WebContainer } from '@webcontainer/api';
+import {Terminal} from "@xterm/xterm";
+import CodeEditorPart from "./CodeEditorPart.ts";
+import {login, user} from "../../user.ts";
+import configs from "./configs";
+import Logo from "../../components/Logo.ts";
+import UserProfile from "../../components/UserProfile.ts";
+import TopBar from "../../components/TopBar.ts";
+import {scss} from "../../scss.ts";
+
+
+@CustomElement("editor-view")
+export default class EditorView extends JDOMComponent.unshadowed {
+ webContainer: WebContainer
+ frame: HTMLIFrameElement
+ frameURL = state('')
+ files = state([])
+ paste = state({})
+ pasteNameEdit = state('')
+ selectedFile = state({})
+ selectedTabIndex = state(0)
+ previewShown = state(false)
+ fullscreen = state(false)
+
+ terminal: Terminal = new Terminal({
+ convertEol: true,
+ rows: 10,
+ cols: 120,
+ theme: {
+ background: '#161921'
+ }
+ })
+
+ async getPaste(id: string) {
+ console.log('Getting', id)
+ if (id === 'new') {
+ this.pasteNameEdit.value = 'New Paste'
+
+ let newType = router.currentRoute.value?.query?.type || 'html-js-css'
+
+ if (!configs[newType])
+ newType = 'html-js-css'
+
+ const config = configs[newType]
+
+ this.paste.value = { exists: false }
+ this.files.value = config.createNewFiles()
+
+ this.selectedFile.value = {
+ name: this.files.value[0].name,
+ contents: this.files.value[0].contents
+ }
+
+ return {}
+ }
+ const paste = await api.get(`/paste/${id}`)
+
+ const out = {...paste}
+
+ if (paste.type === 'MULTI_PASTE') {
+ out.parts = JSON.parse(paste.content)
+ out.parts.sort((a, b) => {
+ if (b.name === '.codebox') {
+ return -1
+ }
+ if (
+ a.name?.toLowerCase().startsWith('main.') ||
+ a.name?.toLowerCase().startsWith('index.') ||
+ a.name?.toLowerCase().startsWith('myapp.') ||
+ a.name?.toLowerCase().startsWith('app.')
+ ) {
+ if (b.name === 'index.html') {
+ return -2
+ }
+ return -1
+ }
+ return 0
+ })
+ this.files.value = out.parts
+ }
+
+ if (this.files.value[0]) {
+ this.selectedFile.value = {
+ name: this.files.value[0].name,
+ contents: this.files.value[0].contents
+ }
+ }
+
+ this.paste.value = out
+ this.pasteNameEdit.value = out.title
+ }
+
+ getFile(name) {
+ return this.files.value.find(c => c.name === name)
+ }
+
+ async savePaste() {
+ let configFile = this.getFile('.codebox')
+ if (configFile) {
+ try {
+ configFile = JSON.parse(configFile.contents)
+ } catch (e) {}
+ }
+ let configType = `codebox-type-${configFile?.type || 'unknown'}`
+
+ const content = JSON.stringify(this.files.value)
+
+ if (this.paste.value?.id) {
+ await api.put(`/paste/${this.paste.value.id}`, {
+ title: this.pasteNameEdit.value,
+ tags: ['codebox', configType],
+ content
+ })
+ } else {
+ const { paste } = await api.post(`/paste`, {
+ title: this.pasteNameEdit.value,
+ type: 'MULTI_PASTE',
+ tags: ['codebox', configType],
+ content
+ })
+
+ this.paste.value = paste
+ window.history.pushState(`/${paste.id}`, `/${paste.id}`, `/${paste.id}`)
+ }
+ }
+
+ async initWebContainer() {
+ this.webContainer?.teardown()
+
+ console.log('STARTING WEBCONTAINER')
+ this.webContainer = await WebContainer.boot();
+ this.webContainer.on('error', e => {
+ alert('Web-Containers do not work here. See a tutorial on how to enable them them https://webcontainers.io/guides/browser-config')
+ })
+
+ for (const {name, contents} of this.files.value) {
+ await this.webContainer.fs.writeFile(name, contents)
+ }
+
+ this.webContainer.on('server-ready', (port, url) => {
+ this.previewShown.value = true
+ console.log('server ready', url, port)
+ this.frame.src = url
+ this.frameURL.value = url
+ });
+
+
+ ;(async () => {
+ const sh = await this.webContainer.spawn("sh")
+ sh.output.pipeTo(new WritableStream({
+ write: (data) => {
+ this.terminal.write(data)
+ }
+ }));
+
+ const input = sh.input.getWriter();
+ this.terminal.onData((data) => {
+ input.write(data);
+ });
+
+ const configFile = this.files.value.find(s => s.name === '.codebox')?.contents
+ if (configFile) {
+ const config = JSON.parse(configFile)
+
+ configs[config.type]?.initContainer(config, this.files.value, sh, this.webContainer, input)
+ }
+ })().then(r => null);
+ }
+
+ selectFile(index: number) {
+ this.saveCurrent()
+ this.selectedTabIndex.value = index
+ this.selectedFile.value = {
+ name: this.files.value[index].name,
+ contents: this.files.value[index].contents,
+ }
+ }
+
+ detach() {
+ console.log('REMOVING CONTAINER')
+ this.webContainer?.teardown()
+ }
+
+ removeFile(index: number) {
+ this.files.value = this.files.value.filter((_, i) => i !== index)
+ this.webContainer?.fs?.rm(this.files.value[index].name)
+
+ if (this.selectedTabIndex.value === index) {
+ this.selectFile((index - 1) || 0)
+ }
+ }
+
+ saveCurrent() {
+ if (this.selectedFile.value.name) {
+ this.webContainer?.fs?.writeFile(this.selectedFile.value.name, this.selectedFile.value.contents)
+ }
+ }
+
+ fork() {
+ this.paste.value = null
+ window.history.pushState('/new', '/new', '/new')
+ }
+
+ render() {
+ this.frame = document.createElement('iframe')
+ const termDiv = document.createElement('div')
+ termDiv.classList.add('terminal')
+ this.terminal.open(termDiv)
+
+ this.selectedFile.addListener(v => {
+ this.files.value[this.selectedTabIndex.value] = v
+ })
+
+ this.getPaste(router.currentRoute.value.params.paste)
+ .then(() => this.initWebContainer())
+
+ return html`
+
+
+ <${Logo} />
+
+
+
+
+
+
+
+ ${computed(() => user.value?.id === this.paste.value.user_id || !this.paste.value.exists
+ ? html`
+ `
+ : null,
+ [this.paste, user])}
+
+
+ <${UserProfile} />
+
+
+
this.previewShown.value ? 'auto 25%' : 'auto', [this.previewShown])}}>
+
+
+
+
+ <${ForEach}
+ value=${this.files}
+ content=${({name, contents}, index) => html`
+
+ `}
+ />
+
+
+
+
+ <${CodeEditorPart}
+ selected=${this.selectedFile}
+ @changed=${() => this.saveCurrent()}
+ @save_paste=${() => this.savePaste()}
+ />
+
+
+
+ this.frame.src = `${this.frame.src}`}
+ />
+ e.key === 'Enter' ? this.frame.src = this.frameURL.value : null}
+ >
+ !this.fullscreen.value, [this.fullscreen]),
+ "icon-button": true
+ }}
+ @click=${() => this.fullscreen.value = !this.fullscreen.value}
+ />
+
+ ${this.frame}
+
+
+
+ ${termDiv}
+
+
+ `
+ }
+
+ styles(): string {
+ // language=SCSS
+ return scss`
+ .editor {
+ display: grid;
+ grid-template-rows: 44px auto 25%;
+ height: 100%;
+ background: #00000022;
+
+ .top-bar {
+ input {
+ font-family: "Plus Jakarta Sans";
+ font-size: 15px;
+ height: 100%;
+ background: transparent;
+ color: #FFFFFF;
+ padding: 10px;
+ border: none;
+ text-align: center;
+ width: 100%;
+ }
+ }
+
+ .editor-terminal-area {
+ padding: 10px;
+ width: 100%;
+ overflow: hidden;
+
+ .terminal {
+ border-radius: 10px;
+ overflow: hidden;
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ .editor-vertical {
+ display: grid;
+ grid-template-columns: auto 25%;
+ grid-gap: 10px;
+ padding: 0 10px 10px;
+
+ .editor-code-editor-area {
+ color: #FFF;
+ height: 100%;
+ border-radius: 10px;
+ overflow: hidden;
+ position: relative;
+
+ .editor-code-files {
+ border-bottom: 2px solid #FFFFFF22;
+ background: #00000022;
+ overflow-y: auto;
+ position: relative;
+ white-space: pre;
+
+ ::-webkit-scrollbar {
+ height: 0;
+ }
+
+ button {
+ color: #FFF;
+ border: none;
+ background: transparent;
+ vertical-align: middle;
+ }
+
+ .files-tab {
+ padding: 10px;
+ cursor: pointer;
+
+ .file-icon {
+ font-size: 18px;
+ margin-right: 8px;
+ }
+
+ input {
+ background: transparent;
+ border: none;
+ width: 130px;
+ color: #FFF;
+ }
+
+ i {
+ vertical-align: middle;
+ font-size: 16px;
+ }
+
+ &.selected {
+ background: #FFFFFF44;
+ }
+ }
+
+ .add-btn {
+ border: none;
+ cursor: pointer;
+ border-radius: 6px;
+ height: 28px;
+ color: #FFFFFFAA;
+ padding: 0;
+ width: 28px;
+ font-size: 17px;
+ margin-left: 5px;
+ margin-right: 100px;
+ &:hover {
+ color: #FFFFFF;
+ background: #FFFFFF11;
+ }
+ }
+ }
+
+ .run-btn {
+ color: #FFF;
+ border: none;
+ z-index: 1;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ background: #5cba37;
+ border-radius: 6px;
+ height: 28px;
+ padding: 0;
+ width: 28px;
+ font-size: 17px;
+ }
+
+ editor-editor-part {
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ .editor-preview {
+ background: #FFFFFF;
+ border-radius: 10px;
+ overflow: hidden;
+
+ display: grid;
+ grid-template-rows: 40px auto;
+
+ .editor-preview-toolbar {
+ height: 40px;
+ background: #181a24;
+ display: grid;
+ border-bottom: 2px solid #FFFFFF44;
+ grid-template-columns: fit-content(10px) auto fit-content(10px);
+ align-items: center;
+ grid-gap: 10px;
+ padding: 0 5px;
+ .icon-button {
+ vertical-align: middle;
+ font-size: 19px;
+ width: 100%;
+ }
+ input {
+ background: #212531;
+ padding: 4px;
+ font-size: 14px;
+ border-radius: 4px;
+ border: none;
+ color: #FFFFFF;;
+ }
+ }
+ iframe {
+ width: 100%;
+ height: 100%;
+ border: none;
+ }
+
+ &.fullscreen {
+ position: fixed;
+ width: calc(100% - 20px);
+ height: calc(100% - 55px);
+ z-index: 10;
+ }
+ }
+ }
+ }
+ `;
+ }
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/helper.ts b/src/views/editor/configs/helper.ts
new file mode 100644
index 0000000..68e6b24
--- /dev/null
+++ b/src/views/editor/configs/helper.ts
@@ -0,0 +1,84 @@
+import {WebContainer} from "@webcontainer/api";
+
+export function isObject(item: any) {
+ return (item && typeof item === 'object' && !Array.isArray(item));
+}
+
+export function mergeDeep(target: any, ...sources: any) {
+ if (!sources.length) return target;
+ const source = sources.shift();
+
+ if (isObject(target) && isObject(source)) {
+ for (const key in source) {
+ if (isObject(source[key])) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ mergeDeep(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+
+ return mergeDeep(target, ...sources);
+}
+
+export async function mergePackageJSON(merge: any, webContainer: WebContainer): Promise {
+ let packageJson = {
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ }
+
+ let adds = []
+
+ try {
+ const f = await webContainer.fs.readFile('package.json')
+ adds.push(JSON.parse(new TextDecoder().decode(f)))
+ } catch (e) {}
+
+ adds.push(merge)
+ return mergeDeep(packageJson, ...adds)
+}
+
+export function htmlBody(body = '', head = '') {
+ return `
+
+
+
+
+
+ Pastefy-Codebox
+ ${head}
+
+
+ ${body}
+
+ `
+}
+export function createTSConfig(): string {
+ return `{
+"compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["."]
+}`
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/html-js-css.ts b/src/views/editor/configs/html-js-css.ts
new file mode 100644
index 0000000..e9c6253
--- /dev/null
+++ b/src/views/editor/configs/html-js-css.ts
@@ -0,0 +1,42 @@
+export default {
+ name: 'HTML-CSS-JS',
+ icon: 'brand-html5',
+ descriptions: ``,
+ createNewFiles: () => [
+ {
+ name: 'index.html',
+ contents: `
+
+
+
+
+
+ Document
+
+
+ Hi
+
+
+
+`
+ },
+ {
+ name: 'index.js',
+ contents: `document.querySelector('h1').textContent = 'Hello World!'`
+ },
+ {
+ name: 'styles.css',
+ contents: `* {
+ font-family: sans-serif;
+ margin: 0;
+ padding: 0;
+}`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "javascript-web"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/index.ts b/src/views/editor/configs/index.ts
new file mode 100644
index 0000000..d2749c6
--- /dev/null
+++ b/src/views/editor/configs/index.ts
@@ -0,0 +1,23 @@
+import javascriptWeb from "./javascript-web.ts";
+import typescriptWeb from "./typescript-web.ts";
+import typescriptCli from "./typescript-cli.ts";
+import javascriptCli from "./javascript-cli.ts";
+import htmlJsCss from "./html-js-css.ts";
+import vue from "./vue.ts";
+import react from "./react.ts";
+import jdomTemplateApp from "./jdom-template-app.ts";
+import svelte from "./svelte.ts";
+import jdomTemplateTsApp from "./jdom-template-ts-app.ts";
+
+export default {
+ 'html-js-css': htmlJsCss,
+ 'javascript-web': javascriptWeb,
+ 'typescript-web': typescriptWeb,
+ 'vue': vue,
+ 'react': react,
+ 'svelte': svelte,
+ 'jdom-template-app': jdomTemplateApp,
+ 'jdom-template-ts-app': jdomTemplateTsApp,
+ 'javascript-cli': javascriptCli,
+ 'typescript-cli': typescriptCli,
+}
diff --git a/src/views/editor/configs/javascript-cli.ts b/src/views/editor/configs/javascript-cli.ts
new file mode 100644
index 0000000..884fa66
--- /dev/null
+++ b/src/views/editor/configs/javascript-cli.ts
@@ -0,0 +1,26 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+import {htmlBody, mergePackageJSON} from "./helper.ts";
+
+export default {
+ name: 'Typescript-CLI',
+ icon: 'brand-javascript',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ await webContainer.fs.writeFile('package.json', JSON.stringify(await mergePackageJSON({}, webContainer)))
+
+ await input.write(`npm install && node ${config.main || 'app.js'}\n`)
+ },
+ createNewFiles: () => [
+ {
+ name: 'main.js',
+ contents: `console.log('Hello World')`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "javascript-cli",
+ "main": "main.js"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/javascript-web.ts b/src/views/editor/configs/javascript-web.ts
new file mode 100644
index 0000000..7607358
--- /dev/null
+++ b/src/views/editor/configs/javascript-web.ts
@@ -0,0 +1,55 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+
+export default {
+ name: 'Typescript-Web',
+ icon: 'brand-javascript',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', `
+
+
+
+
+
+
+ Pastefy-Codebox
+
+
+
+
+
+ `)
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^5.1.4"
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'main.js',
+ contents: `document.body.appendChild(document.createTextNode('Hello World!'))`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "javascript-web",
+ "main": "main.js"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/jdom-template-app.ts b/src/views/editor/configs/jdom-template-app.ts
new file mode 100644
index 0000000..2e5ce29
--- /dev/null
+++ b/src/views/editor/configs/jdom-template-app.ts
@@ -0,0 +1,86 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+
+export default {
+ name: 'JDOM-Template',
+ icon: 'letter-j-small',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', `
+
+
+
+
+
+
+ Pastefy-Codebox
+
+
+
+
+
+ `)
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "jdomjs": "3.1.11"
+ },
+ "devDependencies": {
+ "vite": "^5.1.4"
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'MyApp.js',
+ contents: `import { html, css, JDOMComponent, $r, state } from 'jdomjs'
+
+export default class MyApp extends JDOMComponent {
+ count = state(0)
+
+ render() {
+ return html\`
+ My JDOM App
+
+ \`
+ }
+
+ styles() {
+ return css\`
+ * {
+ font-family: sans-serif
+ }
+ \`
+ }
+}
+
+$r('my-app', MyApp)`
+ },
+ {
+ name: 'main.js',
+ contents: `import { html } from 'jdomjs'
+import MyApp from './MyApp.js'
+
+html\`<\${MyApp} />\`.appendTo(document)`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "jdom-template-app",
+ "main": "main.js"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/jdom-template-ts-app.ts b/src/views/editor/configs/jdom-template-ts-app.ts
new file mode 100644
index 0000000..dc65cc9
--- /dev/null
+++ b/src/views/editor/configs/jdom-template-ts-app.ts
@@ -0,0 +1,79 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+import {createTSConfig, htmlBody} from "./helper.ts";
+
+export default {
+ name: 'JDOM-Template TS',
+ icon: 'letter-j-small',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', htmlBody(``))
+ }
+
+ if (!files.find(f => f.name === 'tsconfig.json')) {
+ await webContainer.fs.writeFile('tsconfig.json', createTSConfig())
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "jdomjs": "3.1.11"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.1.4"
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'MyApp.ts',
+ contents: `import { html, css, JDOMComponent, $r, state } from 'jdomjs'
+import { CustomElement } from 'jdomjs/src/decorators.ts'
+
+@CustomElement('my-app')
+export default class MyApp extends JDOMComponent {
+ count = state(0)
+
+ render() {
+ return html\`
+ My JDOM App
+
+ \`
+ }
+
+ styles() {
+ return css\`
+ * {
+ font-family: sans-serif
+ }
+ \`
+ }
+}`
+ },
+ {
+ name: 'main.ts',
+ contents: `import { html } from 'jdomjs'
+import MyApp from './MyApp.ts'
+
+html\`<\${MyApp} />\`.appendTo(document)`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "jdom-template-ts-app",
+ "main": "main.ts"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/react.ts b/src/views/editor/configs/react.ts
new file mode 100644
index 0000000..7948364
--- /dev/null
+++ b/src/views/editor/configs/react.ts
@@ -0,0 +1,143 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+
+export default {
+ name: 'React',
+ icon: 'brand-react',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', `
+
+
+
+
+
+
+ Pastefy-Codebox
+
+
+
+
+
+
+ `)
+ }
+ if (!files.find(f => f.name === 'vite.config.js')) {
+ await webContainer.fs.writeFile('vite.config.js', `import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})`)
+ }
+ if (!files.find(f => f.name === '.eslint.cjs')) {
+ await webContainer.fs.writeFile('.eslint.cjs', `module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:react/recommended',
+ 'plugin:react/jsx-runtime',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+ settings: { react: { version: '18.2' } },
+ plugins: ['react-refresh'],
+ rules: {
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+}`)
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.56",
+ "@types/react-dom": "^18.2.19",
+ "@vitejs/plugin-react": "^4.2.1",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "vite": "^5.1.4"
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'App.jsx',
+ contents: `import { useState } from 'react'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+ React!
+
+
+
+ Edit App.jsx
and save to test HMR
+
+
+ >
+ )
+}
+
+export default App`
+ },
+ {
+ name: 'main.jsx',
+ contents: `import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './styles.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)`
+ },
+ {
+ name: 'styles.css',
+ contents: `* {
+ font-family: sans-serif
+}
+
+h1 {
+ color: #4444AA
+}`,
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "react",
+ "main": "main.jsx"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/svelte.ts b/src/views/editor/configs/svelte.ts
new file mode 100644
index 0000000..5487fb2
--- /dev/null
+++ b/src/views/editor/configs/svelte.ts
@@ -0,0 +1,95 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+import {htmlBody} from "./helper.ts";
+
+export default {
+ name: 'Svelte',
+ icon: 'brand-svelte',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', htmlBody(`
+`))
+ }
+ if (!files.find(f => f.name === 'vite.config.js')) {
+ await webContainer.fs.writeFile('vite.config.js', `
+ import { defineConfig } from 'vite'
+import { svelte } from '@sveltejs/vite-plugin-svelte'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svelte()],
+})
+`)
+ }
+
+ if (!files.find(f => f.name === 'svelte.config.js')) {
+ await webContainer.fs.writeFile('svelte.config.js', `import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+export default {
+ preprocess: vitePreprocess(),
+}`)
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^3.0.2",
+ "svelte": "^4.2.11",
+ "vite": "^5.1.4"
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'App.svelte',
+ contents: `
+
+
+ Svelte
+
+
+
+
+
+
+`
+ },
+ {
+ name: 'main.js',
+ contents: `import App from './App.svelte'
+
+const app = new App({
+ target: document.getElementById('app'),
+})
+
+export default app`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "svelte",
+ "main": "main.js"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/typescript-cli.ts b/src/views/editor/configs/typescript-cli.ts
new file mode 100644
index 0000000..6bed344
--- /dev/null
+++ b/src/views/editor/configs/typescript-cli.ts
@@ -0,0 +1,31 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+import {htmlBody, mergePackageJSON} from "./helper.ts";
+
+export default {
+ name: 'Typescript-CLI',
+ icon: 'brand-typescript',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ await webContainer.fs.writeFile('package.json', JSON.stringify(await mergePackageJSON({
+ "type": undefined,
+ "devDependencies": {
+ "typescript": "^5.2.2"
+ }
+ }, webContainer)))
+
+ await input.write(`npm install && npx -y ts-node ${config.main || 'app.ts'}\n`)
+ },
+ createNewFiles: () => [
+ {
+ name: 'main.ts',
+ contents: `console.log('Hello World')`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "typescript-cli",
+ "main": "main.ts"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/typescript-web.ts b/src/views/editor/configs/typescript-web.ts
new file mode 100644
index 0000000..72f41b3
--- /dev/null
+++ b/src/views/editor/configs/typescript-web.ts
@@ -0,0 +1,47 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+import {createTSConfig, htmlBody, mergePackageJSON} from "./helper.ts";
+
+export default {
+ name: 'Typescript-Web',
+ icon: 'brand-typescript',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile(
+ 'index.html',
+ htmlBody(``)
+ )
+ }
+
+ if (!files.find(f => f.name === 'tsconfig.json')) {
+ await webContainer.fs.writeFile('tsconfig.json', createTSConfig())
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify(await mergePackageJSON({
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.1.4"
+ }
+ }, webContainer)))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'main.ts',
+ contents: `document.body.appendChild(document.createTextNode('Hello World!'))`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "typescript-web",
+ "main": "main.ts"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/configs/vue.ts b/src/views/editor/configs/vue.ts
new file mode 100644
index 0000000..a742d7e
--- /dev/null
+++ b/src/views/editor/configs/vue.ts
@@ -0,0 +1,89 @@
+import {WebContainer, WebContainerProcess} from "@webcontainer/api";
+
+export default {
+ name: 'Vue',
+ icon: 'brand-vue',
+ descriptions: ``,
+ async initContainer(config: any, files: any[], sh: WebContainerProcess, webContainer: WebContainer, input: WritableStreamDefaultWriter) {
+ if (!files.find(f => f.name === 'index.html')) {
+ await webContainer.fs.writeFile('index.html', `
+
+
+
+
+
+
+ Pastefy-Codebox
+
+
+
+
+
+
+ `)
+ }
+ if (!files.find(f => f.name === 'vite.config.js')) {
+ await webContainer.fs.writeFile('vite.config.js', `
+ import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+})
+`)
+ }
+
+ await webContainer.fs.writeFile('package.json', JSON.stringify({
+ "name": "testvite1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^5.1.4",
+ "@vitejs/plugin-vue": "^5.0.4",
+ }
+ }))
+
+ await input.write('npm install && npm run dev\n')
+ },
+ createNewFiles: () => [
+ {
+ name: 'App.vue',
+ contents: `
+
+
+
+
Example Vue!
+
+
+
+`
+ },
+ {
+ name: 'main.js',
+ contents: `import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')`
+ },
+ {
+ name: '.codebox',
+ contents: `{
+ "type": "vue",
+ "main": "main.js"
+}`
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/views/editor/langReplacements.ts b/src/views/editor/langReplacements.ts
new file mode 100644
index 0000000..9bb232f
--- /dev/null
+++ b/src/views/editor/langReplacements.ts
@@ -0,0 +1,24 @@
+export default {
+ md: 'markdown',
+ js: 'javascript',
+ jsx: 'javascript',
+ jsm: 'javascript',
+ ts: "typescript",
+ tsm: "typescript",
+ tsx: "typescript",
+ html: 'xml',
+ svg: 'xml',
+ htm: 'xml',
+ xhtml: 'xml',
+ iml: 'xml',
+ vue: 'xml',
+ yml: 'yaml',
+ py: 'python',
+ css: 'scss',
+ sass: 'yaml',
+ cs: 'csharp',
+ env: 'properties',
+ txt: "text",
+ h: "c",
+ ino: "c",
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..152599a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowJs": true,
+ "allowImportingTsExtensions": true,
+ "target": "ES5",
+ "lib": [ "es2017", "dom" ]
+ }
+}
\ No newline at end of file