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` +
+ profile-picture +
+ ` + : 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

+
+ <${ForEach} + value=${Object.entries(configs)} + content=${([name, config]) => html` + router.go(`/new?type=${name}`)}> + + ${config.name} + + `} + /> +
+ + + +

publicPastes.value.length, [publicPastes])}>Latest Public Projects

+
+ <${ForEach} + value=${publicPastes} + content=${(paste) => html` + router.go(`/${paste.id}`)}> + + ${paste.title} + + `} + /> +
+ ` + } + + 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: ` + + + +` + }, + { + 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