Compare commits

..

21 Commits

Author SHA1 Message Date
sam
49b153b0ff Update README.md 2026-02-09 16:06:49 -06:00
sam
b801ac9da6 Update README.md 2026-02-09 16:06:27 -06:00
sam
656a673ca9 Update README.md 2026-01-23 19:10:29 -06:00
sam
61af976a7a Update README.md 2026-01-23 19:09:10 -06:00
metacryst
f1e8be1cd8 fix test log 2025-12-27 04:36:11 -06:00
metacryst
cee4d4fa8a fixing stack state problem 2025-12-27 02:35:48 -06:00
metacryst
188837d873 patching stack problem 2025-12-26 06:33:21 -06:00
metacryst
12bb5346e8 stack state working (when top-level parent) 2025-12-26 05:54:27 -06:00
metacryst
eb6975c7de state array working 2025-12-26 04:22:22 -06:00
metacryst
c4560aba37 state test working, added random experimental html 2025-12-26 01:36:04 -06:00
metacryst
b08e2767f6 12.16, 12.17 changes 2025-12-25 06:41:43 -06:00
metacryst
ed6d885557 11.2025: new version released, sample folder added 2025-11-30 02:50:10 -06:00
metacryst
6e2b4dcdbd extra functions, new test from amplify lessons 2024-09-25 22:03:32 -05:00
metacryst
71b08bd184 Page refers to body, add ZStack, add h1 h2 h3, add positioning validation 2024-09-24 13:08:38 -05:00
metacryst
1060797170 Moving styles, Stacks working in Group 2024-09-12 15:35:13 -05:00
metacryst
cfdf67998d Adding Group 2024-09-12 15:08:51 -05:00
metacryst
9432c65fa4 update readme 2024-09-03 13:17:27 -05:00
metacryst
65f79d1631 moving things, adding a test 2024-05-20 17:26:29 -05:00
metacryst
41ec6b7dd3 ternaries almost done, adding margin function 2024-05-17 12:47:17 -05:00
metacryst
8b2f9e2b77 add vstack children 2024-05-05 15:48:32 -05:00
metacryst
07725994b9 add fontSize custom func 2024-05-05 15:47:38 -05:00
40 changed files with 3021 additions and 1476 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
VSCE_PAT=9wucEHBNFD5RTS403Oyoflyi984cAwAfa42FHN7tmqmzfbXsSKP9JQQJ99BKACAAAAAAAAAAAAAGAZDO25Ro

171
README.md
View File

@@ -1,38 +1,163 @@
<p align="center">
<img src="VSCode/docs/Quill.png" alt="drawing" width="100"/>
<h1 align="center">Quill</h1>
</p>
<br>
Usage: Install the VSCode extension "Quill".
Quill is a SwiftUI-style JavaScript framework. It makes use of components called Shadows, which are HTML Custom Elements.
## Rendering Elements:
### Getting Started:
Take index.js and put it in your app. Typically as quill.js. Then import it in the head of the HTML.
### Basic Overview:
Quill uses components called Shadows. Each Shadow is a Custom HTML Element (https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
```
document.body.append(
p("Hi")
.padding("top", 12),
class Home extends Shadow {
render() {
}
}
Link({href: "google.com", name: "hey"})
.background("green")
// NavigationBar()
// .onClick()
)
register(Home)
```
## Needs Support:
Ternaries within render()
Other statements within render()
Once created, it can be imported like
```
import "Home.js"
```
(Not how we are NOT importing the actual class object. If that happens, it will fail.)
## Boilerplate:
- ```*html```: Type in an HTML file and select the suggestion to create HTML boilerplate.
- ```*element```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate.
Here is an example of Hello World:
## Functions:
Clone this repository into the top level of the project you are working on, so the HTML can find the "quill.js" functions.
Use backticks with both to get HTML and CSS syntax highlighting.
- ```css() or addStyle()```: Adds a style to a Quill style tag in the head.
- ```html()```: Creates a parsed HTML element (which is not yet in the DOM)
```
class Home extends Shadow {
render() {
p("Hello World")
.x(50, vw)
.y(50, vh)
}
}
register(Home)
```
This will render a paragraph tag in the middle of the screen.
Here's what it will look like in HTML:
```
<body>
<home->
<p style="position: absolute; top: 50vh; left: 50vw;">Hello World</p>
</home->
</body>
```
Note: .x() and .y() are quill-specific functions that were created simply for nice syntax. However, this would also be valid:
```
p("Hello World")
.top(50, vh)
.left(50, vw)
```
There are quill functions for every HTML style attribute. If they have units, they will follow the pattern directly above, where the first parameter is the amount and the second parameter is the unit.
### Real Basic Example:
First, you need your index.html. Here is one:
```
<!DOCTYPE html>
<html lang="en" class="public">
<head>
<title>Parchment</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/_/icons/quill.svg">
<link rel="stylesheet" href="/_/code/shared.css">
<script src="/_/code/quill.js"></script>
<script type="module" src="75820185/index.js"></script>
</head>
<body>
</body>
</html>
```
When starting, it is typical to make a "Home" shadow and import it in index.js. Here is an example:
index.js:
```
import "./Home.js"
Home()
```
Home.js:
```
import "../components/NavBar.js"
import "./HomeContent.js"
import "./Why.js"
import "./Events.js"
import "./Join.js"
import "./SignIn.js"
import "./Success.js"
class Home extends Shadow {
render() {
ZStack(() => {
NavBar()
img("/_/icons/logo.svg", "2.5em")
.onClick((done) => {
if(!done) return
window.navigateTo("/")
})
.position("absolute")
.left(50, vw).top(4, em)
.center()
.transform(`translate(${window.isMobile() ? "-50%" : "-2em"}, -50%)`)
switch(window.location.pathname) {
case "/":
HomeContent()
break;
case "/why":
Why()
break;
case "/events":
Events()
break;
case "/join":
Join()
break;
case "/success":
Success()
break;
}
})
.onNavigate(() => {
this.rerender()
})
}
}
register(Home)
```
Success.js:
```
class Success extends Shadow {
render() {
p("Thanks for your purchase! You will receive a confirmation email shortly. <br><br> <b>Keep that email; it will be checked at the door.</b>")
.x(50, vw).y(50, vh)
.center()
}
}
register(Success)
```

2
Sample/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
package-lock.json

0
Sample/db/db.json Normal file
View File

18
Sample/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "Quill Starter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"moment": "^2.30.1"
}
}

12
Sample/server/db/db.js Normal file
View File

@@ -0,0 +1,12 @@
const fs = require('fs/promises');
export default class Database {
constructor() {
}
async saveData() {
}
}

100
Sample/server/index.js Normal file
View File

@@ -0,0 +1,100 @@
const express = require('express');
const cors = require('cors');
const http = require('http');
const chalk = require('chalk');
const moment = require('moment');
const path = require('path');
class Server {
db;
UIPath = path.join(__dirname, '../ui')
registerRoutes(router) {
router.post('/join', this.join)
router.post('/contact', this.contact)
router.get('/*', this.get)
return router
}
join = (req, res) => {
}
contact = (req, res) => {
}
get = async (req, res) => {
console.log(this.UIPath)
let url = req.url
let filePath;
if(url.startsWith("/_")) {
filePath = path.join(this.UIPath, url);
} else if(url.includes("75820185")) {
filePath = path.join(this.UIPath, url.split("75820185")[1]);
} else {
filePath = path.join(this.UIPath, "index.html");
}
res.sendFile(filePath);
}
logRequest(req, res, next) {
const formattedDate = moment().format('M.D');
const formattedTime = moment().format('h:mma');
if(req.url.includes("/api/")) {
console.log(chalk.blue(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
} else {
if(req.url === "/")
console.log(chalk.gray(` ${req.method} ${req.url} | ${formattedDate} ${formattedTime}`));
}
next();
}
logResponse(req, res, next) {
const originalSend = res.send;
res.send = function (body) {
if(res.statusCode >= 400) {
console.log(chalk.blue( `<-${chalk.red(res.statusCode)}- ${req.method} ${req.url} | ${chalk.red(body)}`));
} else {
console.log(chalk.blue(`<-${res.statusCode}- ${req.method} ${req.url}`));
}
originalSend.call(this, body);
};
next();
}
constructor() {
const app = express();
app.use(cors({ origin: '*' }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(this.logRequest);
app.use(this.logResponse);
let router = express.Router();
this.registerRoutes(router)
app.use('/', router);
const server = http.createServer(app);
const PORT = 3004;
server.listen(PORT, () => {
console.log("\n")
console.log(chalk.yellow("*************** Comal YR ***************"))
console.log(chalk.yellowBright(`Server is running on port ${PORT}: http://localhost`));
console.log(chalk.yellow("***************************************"))
console.log("\n")
});
process.on('SIGINT', async () => {
console.log(chalk.red('Closing server...'));
console.log(chalk.green('Database connection closed.'));
process.exit(0);
});
Object.preventExtensions(this);
}
}
const server = new Server()

1047
Sample/ui/_/code/quill.js Normal file

File diff suppressed because one or more lines are too long

View File

View File

@@ -0,0 +1,7 @@
class Home extends Shadow {
render() {
}
}
register(Home)

13
Sample/ui/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Quill Starter</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/_/icons/logo.svg">
<link rel="stylesheet" href="/_/code/shared.css">
<script src="/_/code/quill.js"></script>
<script type="module" src="75820185/index.js"></script>
</head>
<body style="margin: 0px">
</body>
</html>

2
Sample/ui/index.js Normal file
View File

@@ -0,0 +1,2 @@
import "./components/Home.js"
Home()

View File

@@ -0,0 +1,38 @@
window.testSuites.push( class testGroup {
BasicDivs() {
let divs = Group(() => {
div()
div()
div()
})
if(!(divs instanceof HTMLElement)) {
return `Did not receive an element!`
}
if(!(divs.children.length === 3)) {
return `Incorrect amount of children!`
}
}
VStack() {
let divs = Group(() => {
VStack(() => {
div()
div()
div()
})
})
if(!(divs instanceof HTMLElement)) {
return `Did not receive an element!`
}
if(!(divs.style.display === "flex")) {
return `Did not receive a flex container!`
}
if(!(divs.children.length === 3)) {
return `Incorrect amount of children!`
}
}
})

View File

@@ -0,0 +1,79 @@
window.testSuites.push( class testStacks {
NestedChildren() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
VStack(() => {
p("Learn to code inside of a startup")
p("➵")
.position("absolute")
.fontSize(30)
.left("50%")
.top("50%")
.transformOrigin("center")
.transform("translate(-50%, -50%) rotate(90deg)")
.transition("all 1s")
.userSelect("none")
.onAppear((self) => {
setTimeout(() => {
self.style.top = "88%"
}, 100)
})
})
}
constructor() {
super()
}
}, randomName("file"))
const file = File("asdf")
if(file.querySelector(".VStack")?.children.length !== 2) {
return "Incorrect amount of children inside vstack!"
}
}
NestedStacks() {
register(class File extends Shadow {
$name
render = () => {
VStack(() => {
HStack(() => {
p("hi")
})
})
}
}, randomName("file"))
const file = File("asdf")
console.log(file)
let fileStyle = Registry.styles.children[Registry.styles.children.length-1].sheet.cssRules
if(fileStyle[0].cssText.includes("row")) {
return "Should not be horizontal"
}
console.log(file.innerHTML)
if(!file.children[0].matches("div.HStack")) {
return "The child is not an HStack"
}
file.rerender()
if(!file.children[0].matches("div.HStack")) {
return "The child is not an HStack"
}
let fileStyle2 = Registry.styles.children[Registry.styles.children.length-1].sheet.cssRules
if(fileStyle2[0].cssText.includes("row")) {
return "Should not be horizontal"
}
}
})

View File

@@ -1,7 +0,0 @@
class Home extends Page {
render = () => {
}
}
export default Home

View File

@@ -1,75 +1,45 @@
window.testSuites.push( class testRender {
window.testSuites.push( class testObservedObject {
SimpleParagraphWithState() {
class File extends Shadow {
$form
render = () => {
p(this.form.data)
FromJSONFailsWithoutAllFields() {
class Form extends ObservedObject {
id
path
$canvasPosition
}
constructor() {
super()
try {
let obj = Form.create({id: "123"})
return "Not implemented"
} catch {}
}
FromJSONInitsAllFields() {
class Form extends ObservedObject {
id
path
$canvasPosition
}
let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
if(!(obj && obj["id"] === "123" && obj["path"] === "/" && obj["canvasPosition"] === "25|25")) {
return "Not all fields initialized!"
}
}
window.register(File, randomName("file"))
let form = {data: "asdf"}
const el = window.File(form)
if(!(el.firstChild?.matches("p"))) {
return `Child paragraph not rendered`
DefaultValueWorks() {
class WindowState extends ObservedObject {
$sidebarOut = false
}
if(!(el.firstChild.innerText === "asdf")) {
return "Child para does not have inner text"
let obj = WindowState.create()
console.log(obj)
if(obj.sidebarOut !== false) {
return "Default field not set"
}
}
ParagraphConstructorChangeState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
file.name = "hey123"
if(file.firstChild.innerText !== "hey123") {
return "Paragraph did not react to change!"
}
}
LiteralDoesNotCreateFalseReactivity() {
register(class File extends Shadow {
$name = "asd"
render = () => {
p(this.name)
p("asd")
}
constructor() {
super()
}
}, randomName("file"))
const file = File()
file.name = "hey123"
if(file.children[1].innerText === "hey123") {
return "Paragraph innertext falsely changed"
}
}
// throw some sort of warning if a global OO is accessed without "this"
DefaultObservedObject() {
window.Form = class Form extends ObservedObject {
@@ -156,4 +126,37 @@ window.testSuites.push( class testRender {
}
}
NotExtensible() {
return "not done"
}
// MustInitAllFields() {
// class Form extends ObservedObject {
// id
// path
// $canvasPosition
// }
// let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
// if(!(obj && obj["id"] === "123" && obj["path"] === "/" && obj["canvasPosition"] === "25|25")) {
// return "Not all fields initialized!"
// }
// }
// ChangingObjChangesInstance() {
// class Form extends ObservedObject {
// id
// path
// $canvasPosition
// }
// let json = {id: "123", path: "/", canvasPosition: "25|25"}
// let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
// json.id = "456"
// if(!(obj["id"] === "456")) {
// return "Change to original object was not reflected!"
// }
// }
})

View File

@@ -1,13 +1,19 @@
/*
"(" is preceding character: el, el.attr, if, switch
el: the el is window.rendering
el.attr:
find the function and attr in the string
if there are multiple instances of being used with this el, add to a list (and if no list then make it and we are first)
if: the el is window.rendering. rerender el
switch: the el is window.rendering. rerender el
*/
window.testSuites.push(
class ParseRender {
// CopyTo() {
// let str = "render=()=>{VStack(()=>{"
// let ret = str.copyTo("{")
// if(ret !== "render=()=>") return "Copy 1 failed!"
// }
ParseRender() {
class Sidebar extends Shadow {

126
Test/Skeleton/state.test.js Normal file
View File

@@ -0,0 +1,126 @@
window.testSuites.push( class testState {
SimpleParagraphWithState() {
class File extends Shadow {
$form
render = () => {
p(this.form.data)
}
constructor() {
super()
}
}
window.register(File, randomName("file"))
let form = {data: "asdf"}
const el = window.File(form)
if(!(el.firstChild?.matches("p"))) {
return `Child paragraph not rendered`
}
if(!(el.firstChild.innerText === "asdf")) {
return "Child para does not have inner text"
}
}
ParagraphConstructorChangeState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
file.name = "hey123"
if(file.firstChild.innerText !== "hey123") {
return "Paragraph did not react to change!"
}
}
LiteralDoesNotCreateFalseReactivity() {
register(class File extends Shadow {
$name = "asd"
render = () => {
p(this.name)
p("asd")
}
constructor() {
super()
}
}, randomName("file"))
const file = File()
file.name = "hey123"
if(file.children[1].innerText === "hey123") {
return "Paragraph innertext falsely changed"
}
}
/*
State itself should check if the reactivity is based on an element or a standalone expression
If standalone, handle it
If element, push the info for initReactivity to handle it
*/
TernaryInState() {
register(class File extends Shadow {
$name
render = () => {
p(this.name)
.fontSize(this.name === "asdf" ? 16 : 32)
}
constructor() {
super()
}
}, randomName("file"))
let name = "asdf"
const file = File(name)
if(file.style.fontSize !== "16px") {
return "fail"
}
}
StateWorksWithCustomStyleFunctions() {
// reactive setting needs to use the actual style functions
register(class File extends Shadow {
$paraWidth = 16
render = () => {
p("guppy")
.width(this.paraWidth)
}
constructor() {
super()
}
}, randomName("file"))
const file = File()
file.paraWidth = 18
if(file.firstChild.style.width !== "18px") {
return "Width did not reactively change!"
}
}
})

View File

@@ -1,77 +0,0 @@
window.testSuites.push( class testObservedObject {
FromJSONFailsWithoutAllFields() {
class Form extends ObservedObject {
id
path
$canvasPosition
}
try {
let obj = Form.create({id: "123"})
return "Not implemented"
} catch {}
}
FromJSONInitsAllFields() {
class Form extends ObservedObject {
id
path
$canvasPosition
}
let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
if(!(obj && obj["id"] === "123" && obj["path"] === "/" && obj["canvasPosition"] === "25|25")) {
return "Not all fields initialized!"
}
}
DefaultValueWorks() {
class WindowState extends ObservedObject {
$sidebarOut = false
}
let obj = WindowState.create()
console.log(obj)
if(obj.sidebarOut !== false) {
return "Default field not set"
}
}
// throw some sort of warning if a global OO is accessed without "this"
NotExtensible() {
return "not done"
}
// MustInitAllFields() {
// class Form extends ObservedObject {
// id
// path
// $canvasPosition
// }
// let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
// if(!(obj && obj["id"] === "123" && obj["path"] === "/" && obj["canvasPosition"] === "25|25")) {
// return "Not all fields initialized!"
// }
// }
// ChangingObjChangesInstance() {
// class Form extends ObservedObject {
// id
// path
// $canvasPosition
// }
// let json = {id: "123", path: "/", canvasPosition: "25|25"}
// let obj = Form.create({id: "123", path: "/", canvasPosition: "25|25"})
// json.id = "456"
// if(!(obj["id"] === "456")) {
// return "Change to original object was not reflected!"
// }
// }
})

73
Test/state/state.test.js Normal file
View File

@@ -0,0 +1,73 @@
window.testSuites.push( class testState {
SimpleState() {
class Home extends Shadow {
state = {
pathname: "/"
}
render() {
VStack(() => {
p("hi")
.top(() => {return (this.state.pathname === "/" ? [11, vw] : [7, vw])})
})
.onAppear(() => {
this.state.pathname = "/asd"
})
}
}
register(Home, randomName("home"))
window.Home()
if(!($("p").style.top === "7vw")) return "state was not respeccted"
}
StateArrayPush() {
class Home extends Shadow {
state = {
logs: []
}
render() {
VStack(() => {
p("hi")
.fontSize(() => {return this.state.logs.length > 0 ? [2, em] : [1, em]})
})
.onAppear(() => {
this.state.logs.push("one")
})
}
}
register(Home, randomName("home"))
window.Home()
if(!($("p").style.fontSize === "2em")) return "state did not update!"
}
SimpleStack() {
class Home extends Shadow {
state = {
logs: []
}
render() {
VStack(() => {
this.state.logs.forEach((log) => {
p(log)
})
})
.onAppear(() => {
this.state.logs.push("one")
this.state.logs.push("two")
})
}
}
register(Home, randomName("home"))
window.Home()
if(!$("p")) return "no p's rendered"
if($$("p")[0].innerText !== "one") return "state did not update!"
if($$("p")[1].innerText !== "two") return "state did not update!"
}
})

View File

@@ -2,17 +2,10 @@
<html lang="en">
<head>
<title>Quill</title>
<link rel="icon" href="">
<link rel="icon" href="../_/Quill.png">
<link rel="stylesheet" href="">
<script src="../index.js"></script>
<script src="test.js" type="module"></script>
<script type="module">
import Home from "./Pages/home.js";
window.routes = {
"/Test": Home
}
</script>
</head>
<body style="background: rgb(242, 194, 147)">

View File

@@ -1,11 +1,7 @@
console.log("Tests initializing.")
window.testSuites = [];
await import ("./parse.test.js")
await import ("./init.test.js")
await import ("./render.test.js")
await import ("./observedobject.test.js")
await import ("./parserender.test.js")
await import ("./state/state.test.js")
window.randomName = function randomName(prefix) {
const sanitizedPrefix = prefix.toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -41,6 +37,7 @@ window.test = async function() {
let test = suiteContents[i];
if(typeof suite[test] === 'function' && test !== "constructor") {
testNum++;
document.body.innerHTML = ""
console.log(`%c${testNum}. ${test}`, "margin-top: 10px; border-top: 2px solid #e9c9a0; color: #e9c9a0; border-radius: 10px; padding: 10px;");
let fail;
@@ -76,12 +73,12 @@ window.test = async function() {
console.log("")
let elapsed = new Date() - start;
if(failed === 0) {
console.log(`%cRan ${failed+success} tests in ${elapsed}ms`, 'color: #00FF00');
console.log(`%c ${success} passed`, 'color: #00FF00');
console.log(`%c ${failed} failed`);
console.log(`%cRan ${failed+success} tests in ${elapsed}ms`, 'color: #9cd499ff');
console.log(`%c ${success} passed`, 'color: #9cd499ff');
console.log(` ${failed} failed`);
} else {
console.log(`%cRan ${failed+success} tests in ${elapsed}ms`, 'color: rgb(254, 62, 43)');
console.log(`%c ${success} `, 'color: #00FF00', "passed");
console.log(`%c ${success} `, 'color: #9cd499ff', "passed");
console.log(`%c ${failed} failed`, 'color: rgb(254, 62, 43)');
}
}

View File

@@ -5,7 +5,7 @@ Forked from this repository:
## Boilerplate:
- ```*html```: Type in an HTML file and select the suggestion to create HTML boilerplate.
- ```*element```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate.
- ```*shadow```: Type in a JS file and select the suggestion to create JS Custom Element boilerplate.
## Functions:
Clone this repository into the top level of the project you are working on, so the HTML can find the "quill.js" functions.

15
VSCode/deploy-howto.md Normal file
View File

@@ -0,0 +1,15 @@
# To Deploy Extension
```vsce package```
```vsce publish```
# To Get PAT
Login to Azure
Search "Devops"
Login to devops (samuel@sun.museum)
Click "User Settings" in the top right > "Personal Access Tokens"
Create one which has "Allow all Organizations" and "Marketplace" > "Manage" selected
Put it in the .env file here
set it in the terminal like `export VSCE_PAT="<token>"`

View File

@@ -2,7 +2,7 @@
"name": "quill",
"displayName": "Quill",
"description": "HTML/CSS Syntax highlighting, best used with the Quill framework",
"version": "1.0.4",
"version": "1.0.6",
"publisher": "capturedsun",
"icon": "docs/Quill.png",
"engines": {

BIN
VSCode/quill-1.0.5.vsix Normal file

Binary file not shown.

View File

@@ -6,42 +6,13 @@
"<html lang=\"en\">",
" <head>",
" <title>Quill</title>",
" <link rel=\"icon\" href=\"\">",
" <link rel=\"stylesheet\" href=\"\">",
" <script type=\"module\">",
" window.addStyle = function addStyle(cssString) {",
" let container = document.querySelector(\"style#quillStyles\");",
" if(!container) {",
" container = document.createElement('style');",
" container.id = \"quillStyles\";",
" document.head.appendChild(container);",
" }",
"",
" let primarySelector = cssString.substring(0, cssString.indexOf(\"{\"));",
" primarySelector = primarySelector.replace(/\\*/g, \"all\");",
" primarySelector = primarySelector.replace(/#/g, \"id-\");",
" primarySelector = primarySelector.replace(/,/g, \"\");",
" let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`)",
" if(!stylesheet) {",
" stylesheet = document.createElement('style');",
" stylesheet.id = primarySelector;",
" stylesheet.appendChild(document.createTextNode(cssString));",
" container.appendChild(stylesheet);",
" } else {",
" stylesheet.innerText = cssString",
" }",
" }",
"",
" window.html = function html(elementString) {",
" let parser = new DOMParser();",
" let doc = parser.parseFromString(elementString, 'text/html');",
" return doc.body.firstChild;",
" }",
" </script>",
" <script type=\"module\" src=\"\"></script>",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
" <link rel=\"icon\" href=\"/_/icons/logo.svg\">",
" <link rel=\"stylesheet\" href=\"/_/code/shared.css\">",
" <script src=\"/_/code/quill.js\"></script>",
" <script type=\"module\" src=\"75820185/index.js\"></script>",
" </head>",
" <body>",
"",
" <body style=\"margin: 0px\">",
" </body>",
"</html>"
],

View File

@@ -2,7 +2,7 @@
"html": {
"prefix": "html",
"body": [
"html(`$1`);"
"html(`$`);"
],
"description": "Create a DOM node"
},
@@ -15,35 +15,17 @@
"description": "Use the DOM collection of functions"
},
"*element": {
"prefix": "*element",
"*shadow": {
"prefix": "*shadow",
"body": [
"class Index extends HTMLElement {",
"",
" css = /*css*/ `",
" index-el {",
"class Index extends Shadow {",
" render() {",
" ",
" }",
" `",
"",
" html = /*html*/ `",
" `",
"",
" constructor() {",
" super();",
" addStyle(this.css);",
" this.innerHTML = this.html;",
" }",
"",
" connectedCallback() {",
" ",
" }",
"",
"}",
"",
"customElements.define('index-el', Index);",
"export default Index;"
" ",
"register(Index)"
],
"description": "Custom Element Template"
"description": "Custom Shadow Template"
}
}

BIN
_/Quill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -1,26 +0,0 @@
Attribute/State Cases:
Dual Instantiation
- HTML-first instantiation from attributes (when first loaded and parsed)
observedAttributes will pick this up
- JS-first instantiation where attributes are set from constructor (or) from init function (or)
press puts attributes on from state before saving
init function can set attributes and variables - perhaps state is always required to be passed in
Usage Flexibility
- attributes can have default values
$url = "hey"
- attributes can be named or unnamed when passed in to constructor functions
Attribute / State Reflexivity
- when attribute is changed, state value is changed
modify prototype at runtime? Add overrides for setattr and remove? ||
use observedAttributes + attributeChanged (not good) - Forms parent element?
- when state is changed, attribute value is changed
modify prototype at runtime to add a setter for the state such that when it is set it sets the attribute
Bindings
- should be able to have a child variable be bound to that of a parent
Binding is denoted by prior to variable
State is denoted by "$" prior to variable

13
app.js
View File

@@ -1,13 +0,0 @@
/*
Captured Sun
*/
"use strict";
class Home extends Page {
render = () => {
("hello world")
}
}
export default Home

View File

@@ -1,5 +0,0 @@
# To Deploy Extension
```vsce package```
```vsce publish```

229
experiment.html Normal file
View File

@@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Canvas Text Editor</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background: #d8cbb0;
overflow: hidden;
}
canvas {
display: block;
background: #e7dcc6;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1;
function resize() {
const w = window.innerWidth;
const h = window.innerHeight;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
canvas.width = Math.floor(w * dpr);
canvas.height = Math.floor(h * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener("resize", resize);
resize();
/* =====================
Editor State
===================== */
const fontSize = 13;
const lineHeight = 20;
const padding = 20;
const font = `${fontSize}px monospace`;
let lines = [""];
let cursor = { line: 0, col: 0 };
let blink = true;
let blinkEnabled = true;
let typingTimeout = null;
/* =====================
Rendering
===================== */
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#631414";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = font;
ctx.textBaseline = "top";
ctx.fillStyle = "#d2531a";
for (let i = 0; i < lines.length; i++) {
ctx.fillText(
lines[i],
padding,
padding + i * lineHeight
);
}
if (blink) {
const before = lines[cursor.line].slice(0, cursor.col);
const x = padding + ctx.measureText(before).width;
const y = padding - 2 + cursor.line * lineHeight;
ctx.fillRect(x, y, 2, fontSize + 2);
}
}
/* =====================
Cursor Blink Logic
===================== */
setInterval(() => {
if (!blinkEnabled) return;
blink = !blink;
draw();
}, 500);
function stopBlinkWhileTyping() {
blinkEnabled = false;
blink = true;
draw();
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
blinkEnabled = true;
blink = true;
draw();
}, 600);
}
/* =====================
Helpers
===================== */
function clampCursor() {
cursor.line = Math.max(0, Math.min(cursor.line, lines.length - 1));
cursor.col = Math.max(0, Math.min(cursor.col, lines[cursor.line].length));
}
function insertText(text) {
const line = lines[cursor.line];
lines[cursor.line] =
line.slice(0, cursor.col) +
text +
line.slice(cursor.col);
cursor.col += text.length;
}
function newline() {
const line = lines[cursor.line];
lines[cursor.line] = line.slice(0, cursor.col);
lines.splice(cursor.line + 1, 0, line.slice(cursor.col));
cursor.line++;
cursor.col = 0;
}
function backspace() {
if (cursor.col > 0) {
const line = lines[cursor.line];
lines[cursor.line] =
line.slice(0, cursor.col - 1) +
line.slice(cursor.col);
cursor.col--;
} else if (cursor.line > 0) {
const prevLen = lines[cursor.line - 1].length;
lines[cursor.line - 1] += lines[cursor.line];
lines.splice(cursor.line, 1);
cursor.line--;
cursor.col = prevLen;
}
}
/* =====================
Keyboard Input
===================== */
window.addEventListener("keydown", (e) => {
if (e.metaKey || e.ctrlKey) return;
let changed = true;
switch (e.key) {
case "ArrowLeft": cursor.col--; break;
case "ArrowRight": cursor.col++; break;
case "ArrowUp": cursor.line--; break;
case "ArrowDown": cursor.line++; break;
case "Backspace": backspace(); e.preventDefault(); break;
case "Enter": newline(); e.preventDefault(); break;
case "Tab": insertText(" "); e.preventDefault(); break;
default:
if (e.key.length === 1) {
insertText(e.key);
e.preventDefault();
} else {
changed = false;
}
}
clampCursor();
if (changed) {
stopBlinkWhileTyping();
}
draw();
});
/* =====================
Mouse → Cursor
===================== */
canvas.addEventListener("mousedown", (e) => {
ctx.font = font;
const mx = e.offsetX - padding;
const my = e.offsetY - padding;
cursor.line = Math.max(
0,
Math.min(Math.floor(my / lineHeight), lines.length - 1)
);
let x = 0;
cursor.col = 0;
for (let i = 0; i < lines[cursor.line].length; i++) {
const w = ctx.measureText(lines[cursor.line][i]).width;
if (x + w / 2 >= mx) break;
x += w;
cursor.col++;
}
blink = true;
blinkEnabled = true;
draw();
});
/* =====================
Initial Draw
===================== */
draw();
</script>
</body>
</html>

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Quill</title>
<link rel="icon" href="">
<link rel="stylesheet" href="">
<script src=""></script>
<script src="Quill/index.js"></script>
<script type="module">
import Home from "./app.js"
window.routes = {
"/": Home
}
</script>
</head>
<body>
</body>
</html>

1852
index.js

File diff suppressed because one or more lines are too long

151
notes.txt
View File

@@ -1,151 +0,0 @@
Shadow.File = (param) => new class {
trippy = "asd"
constructor() {
console.log(param)
}
}
console.log(Shadow.File("as"))
ObservedObject.WindowState = () => ({
sidebarOut: false,
children: null
})
let windowState = ObservedObject.WindowState()
console.log(windowState)
Shadow(class File {
}, "file-")
3/16
const TestClass = () => new class {
constructor() {
console.log("hey")
}
}()
This works. Could extend shadow. This way the function and the actual code are not separate.
3/10
Ran into a problem where I can't call the same element within itself.
There are two problems:
1. Javascript scoping means that it tries to call the class it is inside of.
2. Quill instantiates the object when registered, to track its state
This is what I ended up going with - simply not using the Space() recursively and instead making a child space.
class Space extends HTMLElement {
form = Forms.observe(window.location.pathname, this)
contents = [
...this.form.children.map(form => {
switch(form.type) {
case "file":
return File()
case "space":
return ChildSpace()
case "link":
return Link()
}
})
]
constructor() {
super()
console.log(this.form.path)
console.log(this.contents)
this.render(...this.contents)
}
}
This was my attempt to see if an anonymous class can be used. The class functions and extends HTMLElement - so problem #2 is solved.
Problem #1 however, scoping, is not solved unless putting "window.Space()" instead of Space() here, so that it does not
attempt to access the named function value. It seems both functions and classes have this problem. Perhaps there is a
different way it could be done in Quill, like so -
const el = () => class extends HTMLElement {
...
}
quill.register(el, "Space", "parchment-space")
However, not naming it at the top is less than desirable.
quill("Space", class extends HTMLElement {
form = Forms.observe(window.location.pathname, this)
contents = [
...this.form.children.map(form => {
switch(form.type) {
case "file":
return File()
case "space":
return ChildSpace()
case "link":
return Link()
}
})
]
constructor() {
super()
console.log(this.form.path)
console.log(this.contents)
this.render(...this.contents)
}
})
this would probably work. Still less than ideal but maybe if we used syntax highlighting it could be alright.
How to name something without the class having access? This is the problem. I didn't try it without being an arrow function so
perhaps this would have a chance.
// const Space = () => class extends HTMLElement {
// form = Forms.observe(window.location.pathname, this)
// contents = [
// ...this.form.children.map(form => {
// switch(form.type) {
// case "file":
// return File()
// case "space":
// return Space()
// case "link":
// return Link()
// }
// })
// ]
// constructor() {
// super()
// console.log(this.form.path)
// console.log(this.contents)
// this.render(...this.contents)
// }
// }
// let instan = Space()
// customElements.define("space-", instan)
// window.Space = () => "boi"
// console.log(new instan())
window.register(Space, "parchment-space")

151
path.js
View File

@@ -1,151 +0,0 @@
class PathProcessor {
path;
hasTrailingSlash = false;
node = (typeof module !== 'undefined' && module.exports) ? true : false;
constructor(path) {
this.path = path;
if(path === undefined) {
this.path = ""
}
}
#removeTrailingSlash(path) {
if(path === "/") {
return path;
}
return path.endsWith("/") ? path.slice(0, -1) : path
}
full() {
if(!this.node) return;
let path = this.path;
this.path = ""
this.join(Path.homedir(), path)
return this;
}
web() {
if(!this.node) return;
const os = require('os')
this.path = this.path.replace(this.#removeTrailingSlash(os.homedir()), "")
if(this.path === "") {
this.path = "/"
}
return this;
}
encode() { // Uses the built in encodeURI and also encodes certain characters that are't covered by it
this.path = encodeURI(this.path).replace(/[!'()*]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
});
return this;
}
decoded() {
this.path = decodeURIComponent(this.path);
return this;
}
parent() {
const parts = this.path.split('/').filter(Boolean);
parts.pop();
this.path = '/' + parts.join('/');
return this;
}
end() {
const parts = this.path.split('/').filter(Boolean);
this.path = parts.pop() || '';
return this;
}
trailingSlash() {
this.path = this.path.endsWith("/") ? this.path : this.path+"/";
this.hasTrailingSlash = true;
return this;
}
noTrailingSlash() {
this.path = this.#removeTrailingSlash(this.path)
return this;
}
join(...segments) {
if (this.path) {
segments.unshift(this.path);
}
this.path = segments
.map((part, index) => {
if (index === 0) {
return part.trim().replace(/[/]*$/g, '');
} else {
return part.trim().replace(/(^[/]*|[/]*$)/g, '');
}
})
.filter(Boolean) // Remove empty segments
.join('/');
return this;
}
components() {
return this.path.split('/').filter(Boolean);
}
build() {
return this.hasTrailingSlash ? this.path : this.#removeTrailingSlash(this.path)
}
}
export default class Path {
static full(path) {
return new PathProcessor(path).full();
}
static web(path) {
return new PathProcessor(path).web();
}
static decoded(path) {
return new PathProcessor(path).decoded()
}
static encode(path) {
return new PathProcessor(path).encode()
}
static parent(path) {
return new PathProcessor(path).parent();
}
static end(path) {
return new PathProcessor(path).end();
}
static trailingSlash(path) {
return new PathProcessor(path).trailingSlash();
}
static noTrailingSlash(path) {
return new PathProcessor(path).noTrailingSlash();
}
static components(path) {
return new PathProcessor(path).components();
}
static join(...segments) {
return new PathProcessor(null).join(...segments);
}
static homedir() {
if(typeof module === 'undefined' || !module.exports) return;
const os = require('os')
let ret = os.homedir().replace(/\\/g, '/').replace(/^[a-zA-Z]:/, '');
return ret
}
}
window.Path = Path;