Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
ab4c5b6f58 | |||
9fe7d6820c | |||
c6870aa4f0 | |||
636dc6d59d |
|
@ -17,25 +17,6 @@ I also need to find out a solution to virtual tags, and especially ID
|
|||
|
||||
I need it to keep integration with Kitaab. That largely means a _json interface, which is fine. Some compatibility layer with Taskwarrior for taskwarrior-tui would be cool too, but isn't yet on the goal list.
|
||||
|
||||
## MVP
|
||||
|
||||
For me to start using it one more piece of the puzzle needs to be worked out:
|
||||
|
||||
Identity. I am vaguely aware of how to generate a room, and then use the room id to sync across the network
|
||||
But that doesn't stop anybody from then connecting to your room, and messing up your data
|
||||
For now, something simple like creating a key in localStorage and using that in some fashion to validate that the data is from the right person. I'll then need some kind of key transfer thing, such that we can authorize or generate subkeys.
|
||||
|
||||
I'm not really sure how that will work yet.
|
||||
|
||||
## Parsing
|
||||
|
||||
For the react application (in `mast-react-app`) I actually had to copy the parser to `src/lib/` in order to get it to build.
|
||||
The command to build the parsers is
|
||||
|
||||
```
|
||||
npx peggy --format es command_js.peg
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
A vaguely unordered list of the next tasks to be accomplished, and those recently completed
|
||||
|
@ -50,7 +31,3 @@ Next:
|
|||
Completed:
|
||||
- Syncing between JS & Go clients
|
||||
- Parsing `task add` grammar (For Go)
|
||||
|
||||
## Architecture
|
||||
|
||||
![architecture diagram](./architecture.png)
|
BIN
architecture.png
BIN
architecture.png
Binary file not shown.
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 111 KiB |
|
@ -2,13 +2,8 @@ package mast
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:embed migrations/0001.sql
|
||||
var migration0001 embed.FS
|
||||
|
||||
func RunMigrations(db *sql.DB) error {
|
||||
// Load the cr-sqlite extension
|
||||
// _, err := db.Exec("SELECT load_extension('./crsqlite.so', 'sqlite3_crsqlite_init')")
|
||||
|
@ -16,15 +11,11 @@ func RunMigrations(db *sql.DB) error {
|
|||
// return fmt.Errorf("failed to load cr-sqlite extension: %w", err)
|
||||
// }
|
||||
|
||||
// Read the embedded SQL file
|
||||
sqlData, err := migration0001.ReadFile("migrations/0001.sql")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read embedded SQL file: %v", err)
|
||||
}
|
||||
sqlQuery := string(sqlData)
|
||||
|
||||
// Create the todos table and initialize it as a CRDT
|
||||
_, err = db.Exec(sqlQuery)
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS todos (id blob PRIMARY KEY NOT NULL, content TEXT, completed INTEGER NOT NULL DEFAULT 0);
|
||||
SELECT crsql_as_crr('todos');
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS todos
|
||||
(
|
||||
id blob PRIMARY KEY NOT NULL,
|
||||
description text,
|
||||
project text,
|
||||
tags text,
|
||||
due text,
|
||||
wait text,
|
||||
priority text,
|
||||
urgency real,
|
||||
completed integer NOT NULL DEFAULT 0
|
||||
);
|
||||
SELECT crsql_as_crr('todos');
|
34
flake.lock
34
flake.lock
|
@ -1,23 +1,5 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1708407374,
|
||||
|
@ -36,24 +18,8 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
122
flake.nix
122
flake.nix
|
@ -1,119 +1,11 @@
|
|||
{
|
||||
description = "A basic flake with a shell";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
outputs = { nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
username = "admin";
|
||||
passwordHash =
|
||||
"$apr1$bhZ6EAAr$E8cv5p/2RUBrxLZD.9Jpi."; # Replace with your hash
|
||||
|
||||
nginxConfig = pkgs.writeText "nginx.conf" ''
|
||||
worker_processes auto;
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include ${pkgs.nginx}/conf/mime.types;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
location / {
|
||||
auth_basic "Restricted Content";
|
||||
auth_basic_user_file /etc/nginx/htpasswd;
|
||||
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
'';
|
||||
|
||||
htpasswd = pkgs.writeText "htpasswd" ''
|
||||
${username}:${passwordHash}
|
||||
'';
|
||||
in {
|
||||
packages = rec {
|
||||
react = pkgs.buildNpmPackage {
|
||||
pname = "mast-react";
|
||||
version = "0.0.1";
|
||||
src = ./mast-react-vite;
|
||||
|
||||
npmDepsHash = "sha256-oqq55MEzXGEiF9UW7rZFyQrkyTPrUEkh2enZmbbZ7Ks=";
|
||||
buildInputs = with pkgs; [ nodejs typescript ];
|
||||
nativeBuildInputs = with pkgs; [ nodejs ];
|
||||
|
||||
# npmWorkspace = ./mast-react-vite;
|
||||
npmBuildScript = "build";
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r dist $out/
|
||||
'';
|
||||
};
|
||||
react-server = pkgs.dockerTools.buildImage {
|
||||
name = "react-server";
|
||||
tag = "latest";
|
||||
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = [
|
||||
pkgs.nginx
|
||||
react
|
||||
(pkgs.writeScriptBin "start-server" ''
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Create necessary directories
|
||||
mkdir -p /etc/nginx
|
||||
cp ${htpasswd} /etc/nginx/htpasswd
|
||||
|
||||
# Create app directory and copy built files
|
||||
mkdir -p /app
|
||||
cp -r ${react}/* /app/
|
||||
|
||||
# Start nginx
|
||||
${pkgs.nginx}/bin/nginx -c ${nginxConfig}
|
||||
'')
|
||||
];
|
||||
pathsToLink = [ "/bin" ];
|
||||
};
|
||||
|
||||
config = {
|
||||
Cmd = [ "/bin/start-server" ];
|
||||
ExposedPorts = { "8080/tcp" = { }; };
|
||||
};
|
||||
};
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [ go pigeon nodejs sqlite git-bug openssl ];
|
||||
|
||||
# TODO:
|
||||
# Shell Aliases don't work for direnv because they're not shell portable
|
||||
# Find an alternative to this
|
||||
shellHook = ''
|
||||
alias start-dev='npm run dev'
|
||||
alias build-dev='npm run build'
|
||||
alias clean-dev='rm -rf dist node_modules'
|
||||
alias ws-server='go run ./server/main.go'
|
||||
alias gen-pass='openssl passwd -apr1 '
|
||||
|
||||
echo "System: ${system}"
|
||||
echo "Available commands:"
|
||||
echo " start-dev - Start Vite dev server"
|
||||
echo " build-dev - Build the application"
|
||||
echo " clean-dev - Clean build artifacts"
|
||||
echo " ws-server - Run the websocket server"
|
||||
echo " gen-pass - Generate a basic_auth password"
|
||||
'';
|
||||
};
|
||||
});
|
||||
outputs = { nixpkgs, ... }:
|
||||
let pkgs = nixpkgs.legacyPackages."x86_64-linux";
|
||||
in {
|
||||
devShells."x86_64-linux".default =
|
||||
pkgs.mkShell { packages = with pkgs; [ go sqlite nodejs pigeon ]; };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import wasmUrl from '@vlcn.io/crsqlite-wasm/crsqlite.wasm?url';
|
||||
import { start } from "@vlcn.io/ws-client/worker.js";
|
||||
import { createDbProvider } from "@vlcn.io/ws-browserdb";
|
||||
import * as addParser from '../../parser/add_parser.js';
|
||||
|
||||
let db;
|
||||
let ws;
|
||||
|
@ -18,13 +17,7 @@
|
|||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id BLOB PRIMARY KEY NOT NULL,
|
||||
description TEXT,
|
||||
project text,
|
||||
tags text,
|
||||
due text,
|
||||
wait text,
|
||||
priority text,
|
||||
urgency real,
|
||||
content TEXT,
|
||||
completed INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
SELECT crsql_as_crr('todos');
|
||||
|
@ -94,37 +87,12 @@
|
|||
todos = result;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// This is fucking sucks
|
||||
// make the stupid parser do this 😩
|
||||
async function Marshall(parsed) {
|
||||
let project = "";
|
||||
const tags = [];
|
||||
console.log(parsed);
|
||||
for (const attr of parsed.attributes) {
|
||||
console.log(attr);
|
||||
switch (attr.type) {
|
||||
case 'project':
|
||||
console.log("project" + attr.value);
|
||||
project = attr.value;
|
||||
break;
|
||||
case 'tag':
|
||||
console.log("tag" + attr.value);
|
||||
tags.push(attr.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {description: parsed.description, tags: tags, project: project};
|
||||
}
|
||||
|
||||
async function addTodo() {
|
||||
if (newTodo.trim()) {
|
||||
const parsed = await Marshall(addParser.parse(newTodo));
|
||||
console.log(parsed);
|
||||
await db.exec(`
|
||||
INSERT INTO todos (id, description, tags, project, completed)
|
||||
VALUES (lower(hex(randomblob(16))), ?, ?, ?, 0)
|
||||
`, [parsed.description, JSON.stringify(parsed.tags), parsed.project]);
|
||||
INSERT INTO todos (id, content, completed)
|
||||
VALUES (lower(hex(randomblob(16))), ?, 0)
|
||||
`, [newTodo.trim()]);
|
||||
newTodo = '';
|
||||
loadTodos();
|
||||
}
|
||||
|
@ -141,7 +109,7 @@
|
|||
</script>
|
||||
|
||||
<main>
|
||||
<h1>Mast</h1>
|
||||
<h1>Svelte Todo with cr-sqlite</h1>
|
||||
<input
|
||||
bind:value={newTodo}
|
||||
on:keypress={(e) => e.key === 'Enter' && addTodo()}
|
||||
|
@ -156,7 +124,7 @@
|
|||
checked={todo.completed}
|
||||
on:change={() => toggleTodo(todo.id)}
|
||||
/>
|
||||
<span class:completed={todo.completed}>{todo.description} pro:{todo.project} {todo.tags}</span>
|
||||
<span class:completed={todo.completed}>{todo.content}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
24
mast-react-vite/.gitignore
vendored
24
mast-react-vite/.gitignore
vendored
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -1,50 +0,0 @@
|
|||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react'
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
})
|
||||
```
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
|
@ -1,13 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
4087
mast-react-vite/package-lock.json
generated
4087
mast-react-vite/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "mast-react",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@vlcn.io/react": "^3.1.0",
|
||||
"@vlcn.io/rx-tbl": "^0.15.0",
|
||||
"@vlcn.io/ws-browserdb": "^0.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.13.0",
|
||||
"@types/node": "^22.8.2",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.13",
|
||||
"globals": "^15.11.0",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"vite": "^5.4.9"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,42 +0,0 @@
|
|||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import { useQuery } from "@vlcn.io/react";
|
||||
import {
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table"
|
||||
import { DataTable } from "@/components/ui/data-table"
|
||||
import * as commandParser from '@/lib/command_parser.js';
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
interface MarshalledCommand {
|
||||
description: string;
|
||||
tags: string;
|
||||
project: string;
|
||||
}
|
||||
|
||||
function Marshall(parsed): MarshalledCommand {
|
||||
let project = "";
|
||||
const tags = [];
|
||||
for (const attr of parsed.attributes) {
|
||||
switch (attr.type) {
|
||||
case 'project':
|
||||
project = attr.value;
|
||||
break;
|
||||
case 'tag':
|
||||
tags.push(attr.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { description: parsed.description, tags: JSON.stringify(tags), project: project };
|
||||
}
|
||||
|
||||
type Todo = {
|
||||
id: string
|
||||
description: string
|
||||
status: 0 | 1
|
||||
tags: string[]
|
||||
project: string
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<Todo>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<p>0</p>
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row, table }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<p>{row.index+1}</p>
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => {
|
||||
row.toggleSelected(!!value);
|
||||
// Get setNewText from table meta
|
||||
const setNewText = table.options.meta?.setNewText;
|
||||
const NewText = table.options.meta?.newText;
|
||||
if (setNewText && value === true) {
|
||||
const calculatedRowId = row.index+1
|
||||
setNewText(NewText+calculatedRowId+",");
|
||||
} else {
|
||||
// TODO:
|
||||
// We need to _remove_ the value here
|
||||
// That probably means taking the NewText
|
||||
// Removing the first instance of "row.index,"
|
||||
// And then setting that with setNewText
|
||||
}
|
||||
}}
|
||||
aria-label="Select row"
|
||||
className="translate-y-[2px]"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
},
|
||||
{
|
||||
accessorKey: "project",
|
||||
header: "Project",
|
||||
},
|
||||
{
|
||||
accessorKey: "tags",
|
||||
header: "Tags",
|
||||
cell: ({ row }) => {
|
||||
const formatted = JSON.parse(row.getValue("tags"))
|
||||
return <div className="font-medium">{formatted.join(", ")}</div>
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
function App({ ctx }) {
|
||||
const todos = useQuery(ctx, "SELECT * FROM todos where completed = 0").data;
|
||||
const [newText, setNewText] = useState("");
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
|
||||
const addTodo = (e) => {
|
||||
if (e.key === "Enter" && e.target.value.trim() !== "") {
|
||||
const parsed = Marshall(addParser.parse(e.target.value));
|
||||
ctx.db.exec(`INSERT INTO todos (id, description, tags, project, completed)
|
||||
VALUES (lower(hex(randomblob(16))), ?, ?, ?, 0)`,
|
||||
[
|
||||
parsed.description,
|
||||
parsed.tags,
|
||||
parsed.project
|
||||
]);
|
||||
setNewText("");
|
||||
}
|
||||
}
|
||||
|
||||
const parseTodos = (e) => {
|
||||
// On enter execute the command
|
||||
if (e.key === "Enter") {
|
||||
console.log("enter confirmed")
|
||||
try {
|
||||
const parsed = commandParser.parse(e.target.value);
|
||||
// TODO:
|
||||
// We need to identify the type of the command first
|
||||
// Currently assumes that it is done
|
||||
switch (parsed.type) {
|
||||
case "done":
|
||||
if (parsed.filters && parsed.filters.length > 0) {
|
||||
const idFilters = parsed.filters.filter(f => f.type === "id");
|
||||
|
||||
// Get the row IDs from the table
|
||||
const rowIds = idFilters.flatMap(filter =>
|
||||
filter.ids.map(id => todos[id - 1]?.id)
|
||||
).filter(Boolean);
|
||||
|
||||
// Execute SQL update for each row
|
||||
if (rowIds.length > 0) {
|
||||
rowIds.forEach(id => {
|
||||
ctx.db.exec(
|
||||
`UPDATE todos SET completed = 1 WHERE id = ?`,
|
||||
[id]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setNewText("");
|
||||
setRowSelection({});
|
||||
}
|
||||
case "add":
|
||||
ctx.db.exec(`INSERT INTO todos (id, description, tags, project, completed)
|
||||
VALUES (lower(hex(randomblob(16))), ?, ?, ?, 0)`,
|
||||
[
|
||||
parsed.description,
|
||||
parsed.tags,
|
||||
parsed.project
|
||||
]);
|
||||
setNewText("");
|
||||
}
|
||||
} catch (error) {
|
||||
// TODO:
|
||||
// This is actually bad
|
||||
// We want to throw an error to the user here
|
||||
console.log("Unable to parse field");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// React to key presses for selection
|
||||
// TODO:
|
||||
// We should use the react way
|
||||
// Where we update based on changes to newText
|
||||
// Rather than changes to the input value
|
||||
else if (e.target.value.trim() !== "") {
|
||||
try {
|
||||
const parsed = commandParser.parse(e.target.value);
|
||||
// TODO:
|
||||
// We want to reactively update based on command still
|
||||
// Right now this assumes the command is filter
|
||||
|
||||
if (parsed.filters && parsed.filters.length > 0) {
|
||||
const idFilters = parsed.filters.filter(f => f.type === "id");
|
||||
|
||||
// TODO:
|
||||
// We should probably have selection state react to changes on NewText
|
||||
// Rather than manually updating here
|
||||
|
||||
// Create new selection state
|
||||
const newSelection = {};
|
||||
idFilters.forEach(filter => {
|
||||
filter.ids.forEach(id => {
|
||||
newSelection[id-1] = true;
|
||||
})
|
||||
});
|
||||
|
||||
setRowSelection(newSelection);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Unable to parse field")
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setRowSelection({});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="pt-8 container">
|
||||
<Input type="text"
|
||||
className="bg-background p-4"
|
||||
placeholder="..."
|
||||
value={newText}
|
||||
onKeyUp={parseTodos}
|
||||
onChange={(e) => setNewText(e.target.value)}
|
||||
/>
|
||||
<div className="p-2"/>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={todos}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
setNewText={setNewText}
|
||||
newText={newText}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,78 +0,0 @@
|
|||
import App from "./App.tsx";
|
||||
import schemaContent from "./schemas/main2.sql?raw";
|
||||
import { DBProvider } from "@vlcn.io/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Generates a random room name to sync with or pulls one from local storage.
|
||||
*/
|
||||
function getRoom(hash: HashBag): string {
|
||||
return hash.room || localStorage.getItem("room") || newRoom();
|
||||
}
|
||||
|
||||
function hashChanged() {
|
||||
const hash = parseHash();
|
||||
const room = getRoom(hash);
|
||||
if (room != hash.room) {
|
||||
hash.room = room;
|
||||
window.location.hash = writeHash(hash);
|
||||
}
|
||||
localStorage.setItem("room", room);
|
||||
return room;
|
||||
}
|
||||
const room = hashChanged();
|
||||
|
||||
export default function Root() {
|
||||
const [theRoom, setTheRoom] = useState(room);
|
||||
useEffect(() => {
|
||||
const cb = () => {
|
||||
const room = hashChanged();
|
||||
if (room != theRoom) {
|
||||
setTheRoom(room);
|
||||
}
|
||||
};
|
||||
addEventListener("hashchange", cb);
|
||||
return () => {
|
||||
removeEventListener("hashchange", cb);
|
||||
};
|
||||
}, []); // ignore -- theRoom is managed by the effect
|
||||
|
||||
return (
|
||||
<DBProvider
|
||||
dbname={theRoom}
|
||||
schema={{
|
||||
name: "main2.sql",
|
||||
content: schemaContent,
|
||||
}}
|
||||
Render={() => <App dbname={theRoom} />}
|
||||
></DBProvider>
|
||||
);
|
||||
}
|
||||
|
||||
type HashBag = { [key: string]: string };
|
||||
function parseHash(): HashBag {
|
||||
const hash = window.location.hash;
|
||||
const ret: { [key: string]: string } = {};
|
||||
if (hash.length > 1) {
|
||||
const substr = hash.substring(1);
|
||||
const parts = substr.split(",");
|
||||
for (const part of parts) {
|
||||
const [key, value] = part.split("=");
|
||||
ret[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function writeHash(hash: HashBag) {
|
||||
const parts = [];
|
||||
for (const key in hash) {
|
||||
parts.push(`${key}=${hash[key]}`);
|
||||
}
|
||||
return parts.join(",");
|
||||
}
|
||||
|
||||
function newRoom() {
|
||||
return crypto.randomUUID().replaceAll("-", "");
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4 KiB |
|
@ -1,27 +0,0 @@
|
|||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CheckIcon } from "@radix-ui/react-icons"
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-4 w-4 shrink-0 items-center justify-center text-current rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<CheckIcon className="h-3 w-3" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
|
@ -1,109 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnSizingState,
|
||||
RowSelectionState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
rowSelection: {}
|
||||
setRowSelection: (value: {}) => void
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
rowSelection,
|
||||
setNewText,
|
||||
newText,
|
||||
setRowSelection,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [colSizing, setColSizing] = useState<ColumnSizingState>({});
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
enableColumnResizing: true,
|
||||
columnResizeMode: "onChange",
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onColumnSizingChange: setColSizing,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
state: {
|
||||
columnSizing: colSizing,
|
||||
rowSelection: rowSelection,
|
||||
},
|
||||
meta: {
|
||||
setNewText,
|
||||
newText,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div >
|
||||
<Table >
|
||||
<TableHeader className="bg-accent">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}
|
||||
className="relative text-foreground"
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
}}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
|
@ -1,118 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
};
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0.5rem;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +0,0 @@
|
|||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from "./App.tsx";
|
||||
import Root from "./Root.tsx";
|
||||
import { start } from "@vlcn.io/ws-client/worker.js";
|
||||
import { createDbProvider } from "@vlcn.io/ws-browserdb";
|
||||
|
||||
import initWasm from '@vlcn.io/crsqlite-wasm';
|
||||
import wasmUrl from '@vlcn.io/crsqlite-wasm/crsqlite.wasm?url';
|
||||
import tblrx from '@vlcn.io/rx-tbl';
|
||||
|
||||
async function initializeDatabase() {
|
||||
const sqlite = await initWasm(() => wasmUrl);
|
||||
const db = await sqlite.open("todo.db");
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id BLOB PRIMARY KEY NOT NULL,
|
||||
description TEXT,
|
||||
project text,
|
||||
tags text,
|
||||
due text,
|
||||
wait text,
|
||||
priority text,
|
||||
urgency real,
|
||||
completed INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
SELECT crsql_as_crr('todos');
|
||||
`);
|
||||
db.onUpdate(async () => {
|
||||
const changes = await db.execA(`SELECT * FROM crsql_changes WHERE db_version > 0 AND site_id = crsql_site_id()`);
|
||||
|
||||
sendChangesToServer(changes);
|
||||
});
|
||||
return db;
|
||||
}
|
||||
|
||||
const config = {
|
||||
dbProvider: createDbProvider(),
|
||||
transportProvider: (dbName) => new WebSocket(`ws://localhost:8080/sync/${dbName}`),
|
||||
};
|
||||
|
||||
start(config);
|
||||
const ws = new WebSocket('ws://localhost:8080/sync');
|
||||
|
||||
ws.onmessage = async function(event) {
|
||||
const { type, data } = JSON.parse(event.data);
|
||||
if (type === 'changes') {
|
||||
await applyChanges(data);
|
||||
}
|
||||
};
|
||||
|
||||
function sendChangesToServer(changes) {
|
||||
const formattedChanges = changes.map(change => ({
|
||||
TableName: change[0],
|
||||
PK: Array.isArray(change[1]) ? btoa(String.fromCharCode.apply(null, change[1])) : btoa(change[1]),
|
||||
ColumnName: change[2],
|
||||
Value: Array.isArray(change[3]) ? btoa(String.fromCharCode.apply(null, change[3])) : change[3],
|
||||
ColVersion: Number(change[4]),
|
||||
DBVersion: Number(change[5]),
|
||||
SiteID: Array.isArray(change[6]) ? btoa(new TextDecoder('utf8').decode(change[6])) : btoa(change[6]),
|
||||
CL: Number(change[7]),
|
||||
Seq: Number(change[8])
|
||||
}));
|
||||
console.log(formattedChanges)
|
||||
|
||||
const message = {
|
||||
type: "changes",
|
||||
data: formattedChanges
|
||||
};
|
||||
|
||||
ws.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
async function applyChanges(changes) {
|
||||
const db = initializeDatabase();
|
||||
const rx = tblrx(db);
|
||||
const ctx = { db, rx };
|
||||
await db.tx(async (tx) => {
|
||||
for (const change of changes) {
|
||||
await tx.exec(
|
||||
`INSERT INTO crsql_changes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
change
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("root")!.classList.add("dark");
|
||||
|
||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<main className="min-h-screen flex flex-col py-12 bg-background text-gray-200">
|
||||
<App ctx={ctx} />
|
||||
</main>
|
||||
);
|
1
mast-react-vite/src/vite-env.d.ts
vendored
1
mast-react-vite/src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -1,78 +0,0 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
/* shadcn */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmitOnError": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import path from "path"
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
})
|
85
package-lock.json
generated
85
package-lock.json
generated
|
@ -1,85 +0,0 @@
|
|||
{
|
||||
"name": "mast",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"peggy": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@peggyjs/from-mem": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@peggyjs/from-mem/-/from-mem-1.3.0.tgz",
|
||||
"integrity": "sha512-kzGoIRJjkg3KuGI4bopz9UvF3KguzfxalHRDEIdqEZUe45xezsQ6cx30e0RKuxPUexojQRBfu89Okn7f4/QXsw==",
|
||||
"dependencies": {
|
||||
"semver": "7.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/peggy": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/peggy/-/peggy-4.0.3.tgz",
|
||||
"integrity": "sha512-v7/Pt6kGYsfXsCrfb52q7/yg5jaAwiVaUMAPLPvy4DJJU6Wwr72t6nDIqIDkGfzd1B4zeVuTnQT0RGeOhe/uSA==",
|
||||
"dependencies": {
|
||||
"@peggyjs/from-mem": "1.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"source-map-generator": "0.8.0"
|
||||
},
|
||||
"bin": {
|
||||
"peggy": "bin/peggy.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-generator": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz",
|
||||
"integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"peggy": "^4.0.3"
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
function MakeAdd(description, attributes) {
|
||||
return {
|
||||
description: description,
|
||||
attributes: attributes || []
|
||||
};
|
||||
}
|
||||
|
||||
function MakeAttributes(attributes) {
|
||||
return attributes.flatMap(attr => attr[1]);
|
||||
}
|
||||
}
|
||||
|
||||
Add = desc:Description attributes:Attributes? {
|
||||
return MakeAdd(desc, attributes);
|
||||
}
|
||||
|
||||
Description = chars:(!Attribute .)+ {
|
||||
return chars.map(c => c[1]).join('');
|
||||
}
|
||||
|
||||
Attributes = attributes:(_ Attribute)* {
|
||||
return MakeAttributes(attributes);
|
||||
}
|
||||
|
||||
Attribute = Project / Priority / Tag
|
||||
|
||||
Project = ("pro:" / "project:" / "+") value:Word {
|
||||
return { type: "project", value: value };
|
||||
}
|
||||
|
||||
Priority = "priority:" value:[HML] {
|
||||
return { type: "priority", value: value };
|
||||
}
|
||||
|
||||
Tag = "#" value:Word {
|
||||
return { type: "tag", value: value };
|
||||
}
|
||||
|
||||
Word = chars:[a-zA-Z0-9_-]+ {
|
||||
return chars.join('');
|
||||
}
|
||||
|
||||
_ = [ \\t]*
|
||||
|
||||
EOF = !.
|
|
@ -1,767 +0,0 @@
|
|||
// @generated by Peggy 4.1.1.
|
||||
//
|
||||
// https://peggyjs.org/
|
||||
|
||||
|
||||
function peg$subclass(child, parent) {
|
||||
function C() { this.constructor = child; }
|
||||
C.prototype = parent.prototype;
|
||||
child.prototype = new C();
|
||||
}
|
||||
|
||||
function peg$SyntaxError(message, expected, found, location) {
|
||||
var self = Error.call(this, message);
|
||||
// istanbul ignore next Check is a necessary evil to support older environments
|
||||
if (Object.setPrototypeOf) {
|
||||
Object.setPrototypeOf(self, peg$SyntaxError.prototype);
|
||||
}
|
||||
self.expected = expected;
|
||||
self.found = found;
|
||||
self.location = location;
|
||||
self.name = "SyntaxError";
|
||||
return self;
|
||||
}
|
||||
|
||||
peg$subclass(peg$SyntaxError, Error);
|
||||
|
||||
function peg$padEnd(str, targetLength, padString) {
|
||||
padString = padString || " ";
|
||||
if (str.length > targetLength) { return str; }
|
||||
targetLength -= str.length;
|
||||
padString += padString.repeat(targetLength);
|
||||
return str + padString.slice(0, targetLength);
|
||||
}
|
||||
|
||||
peg$SyntaxError.prototype.format = function(sources) {
|
||||
var str = "Error: " + this.message;
|
||||
if (this.location) {
|
||||
var src = null;
|
||||
var k;
|
||||
for (k = 0; k < sources.length; k++) {
|
||||
if (sources[k].source === this.location.source) {
|
||||
src = sources[k].text.split(/\r\n|\n|\r/g);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var s = this.location.start;
|
||||
var offset_s = (this.location.source && (typeof this.location.source.offset === "function"))
|
||||
? this.location.source.offset(s)
|
||||
: s;
|
||||
var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
|
||||
if (src) {
|
||||
var e = this.location.end;
|
||||
var filler = peg$padEnd("", offset_s.line.toString().length, ' ');
|
||||
var line = src[s.line - 1];
|
||||
var last = s.line === e.line ? e.column : line.length + 1;
|
||||
var hatLen = (last - s.column) || 1;
|
||||
str += "\n --> " + loc + "\n"
|
||||
+ filler + " |\n"
|
||||
+ offset_s.line + " | " + line + "\n"
|
||||
+ filler + " | " + peg$padEnd("", s.column - 1, ' ')
|
||||
+ peg$padEnd("", hatLen, "^");
|
||||
} else {
|
||||
str += "\n at " + loc;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
peg$SyntaxError.buildMessage = function(expected, found) {
|
||||
var DESCRIBE_EXPECTATION_FNS = {
|
||||
literal: function(expectation) {
|
||||
return "\"" + literalEscape(expectation.text) + "\"";
|
||||
},
|
||||
|
||||
class: function(expectation) {
|
||||
var escapedParts = expectation.parts.map(function(part) {
|
||||
return Array.isArray(part)
|
||||
? classEscape(part[0]) + "-" + classEscape(part[1])
|
||||
: classEscape(part);
|
||||
});
|
||||
|
||||
return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]";
|
||||
},
|
||||
|
||||
any: function() {
|
||||
return "any character";
|
||||
},
|
||||
|
||||
end: function() {
|
||||
return "end of input";
|
||||
},
|
||||
|
||||
other: function(expectation) {
|
||||
return expectation.description;
|
||||
}
|
||||
};
|
||||
|
||||
function hex(ch) {
|
||||
return ch.charCodeAt(0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
function literalEscape(s) {
|
||||
return s
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, "\\\"")
|
||||
.replace(/\0/g, "\\0")
|
||||
.replace(/\t/g, "\\t")
|
||||
.replace(/\n/g, "\\n")
|
||||
.replace(/\r/g, "\\r")
|
||||
.replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
|
||||
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
|
||||
}
|
||||
|
||||
function classEscape(s) {
|
||||
return s
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/\]/g, "\\]")
|
||||
.replace(/\^/g, "\\^")
|
||||
.replace(/-/g, "\\-")
|
||||
.replace(/\0/g, "\\0")
|
||||
.replace(/\t/g, "\\t")
|
||||
.replace(/\n/g, "\\n")
|
||||
.replace(/\r/g, "\\r")
|
||||
.replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
|
||||
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
|
||||
}
|
||||
|
||||
function describeExpectation(expectation) {
|
||||
return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
|
||||
}
|
||||
|
||||
function describeExpected(expected) {
|
||||
var descriptions = expected.map(describeExpectation);
|
||||
var i, j;
|
||||
|
||||
descriptions.sort();
|
||||
|
||||
if (descriptions.length > 0) {
|
||||
for (i = 1, j = 1; i < descriptions.length; i++) {
|
||||
if (descriptions[i - 1] !== descriptions[i]) {
|
||||
descriptions[j] = descriptions[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
descriptions.length = j;
|
||||
}
|
||||
|
||||
switch (descriptions.length) {
|
||||
case 1:
|
||||
return descriptions[0];
|
||||
|
||||
case 2:
|
||||
return descriptions[0] + " or " + descriptions[1];
|
||||
|
||||
default:
|
||||
return descriptions.slice(0, -1).join(", ")
|
||||
+ ", or "
|
||||
+ descriptions[descriptions.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
function describeFound(found) {
|
||||
return found ? "\"" + literalEscape(found) + "\"" : "end of input";
|
||||
}
|
||||
|
||||
return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
|
||||
};
|
||||
|
||||
function peg$parse(input, options) {
|
||||
options = options !== undefined ? options : {};
|
||||
|
||||
var peg$FAILED = {};
|
||||
var peg$source = options.grammarSource;
|
||||
|
||||
var peg$startRuleFunctions = { Add: peg$parseAdd };
|
||||
var peg$startRuleFunction = peg$parseAdd;
|
||||
|
||||
var peg$c0 = "pro:";
|
||||
var peg$c1 = "project:";
|
||||
var peg$c2 = "+";
|
||||
var peg$c3 = "priority:";
|
||||
var peg$c4 = "#";
|
||||
|
||||
var peg$r0 = /^[HML]/;
|
||||
var peg$r1 = /^[a-zA-Z0-9_\-]/;
|
||||
var peg$r2 = /^[ \\t]/;
|
||||
|
||||
var peg$e0 = peg$anyExpectation();
|
||||
var peg$e1 = peg$literalExpectation("pro:", false);
|
||||
var peg$e2 = peg$literalExpectation("project:", false);
|
||||
var peg$e3 = peg$literalExpectation("+", false);
|
||||
var peg$e4 = peg$literalExpectation("priority:", false);
|
||||
var peg$e5 = peg$classExpectation(["H", "M", "L"], false, false);
|
||||
var peg$e6 = peg$literalExpectation("#", false);
|
||||
var peg$e7 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false);
|
||||
var peg$e8 = peg$classExpectation([" ", "\\", "t"], false, false);
|
||||
|
||||
var peg$f0 = function(desc, attributes) {
|
||||
return MakeAdd(desc, attributes);
|
||||
};
|
||||
var peg$f1 = function(chars) {
|
||||
return chars.map(c => c[1]).join('');
|
||||
};
|
||||
var peg$f2 = function(attributes) {
|
||||
return MakeAttributes(attributes);
|
||||
};
|
||||
var peg$f3 = function(value) {
|
||||
return { type: "project", value: value };
|
||||
};
|
||||
var peg$f4 = function(value) {
|
||||
return { type: "priority", value: value };
|
||||
};
|
||||
var peg$f5 = function(value) {
|
||||
return { type: "tag", value: value };
|
||||
};
|
||||
var peg$f6 = function(chars) {
|
||||
return chars.join('');
|
||||
};
|
||||
var peg$currPos = options.peg$currPos | 0;
|
||||
var peg$savedPos = peg$currPos;
|
||||
var peg$posDetailsCache = [{ line: 1, column: 1 }];
|
||||
var peg$maxFailPos = peg$currPos;
|
||||
var peg$maxFailExpected = options.peg$maxFailExpected || [];
|
||||
var peg$silentFails = options.peg$silentFails | 0;
|
||||
|
||||
var peg$result;
|
||||
|
||||
if (options.startRule) {
|
||||
if (!(options.startRule in peg$startRuleFunctions)) {
|
||||
throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
|
||||
}
|
||||
|
||||
peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
|
||||
}
|
||||
|
||||
function text() {
|
||||
return input.substring(peg$savedPos, peg$currPos);
|
||||
}
|
||||
|
||||
function offset() {
|
||||
return peg$savedPos;
|
||||
}
|
||||
|
||||
function range() {
|
||||
return {
|
||||
source: peg$source,
|
||||
start: peg$savedPos,
|
||||
end: peg$currPos
|
||||
};
|
||||
}
|
||||
|
||||
function location() {
|
||||
return peg$computeLocation(peg$savedPos, peg$currPos);
|
||||
}
|
||||
|
||||
function expected(description, location) {
|
||||
location = location !== undefined
|
||||
? location
|
||||
: peg$computeLocation(peg$savedPos, peg$currPos);
|
||||
|
||||
throw peg$buildStructuredError(
|
||||
[peg$otherExpectation(description)],
|
||||
input.substring(peg$savedPos, peg$currPos),
|
||||
location
|
||||
);
|
||||
}
|
||||
|
||||
function error(message, location) {
|
||||
location = location !== undefined
|
||||
? location
|
||||
: peg$computeLocation(peg$savedPos, peg$currPos);
|
||||
|
||||
throw peg$buildSimpleError(message, location);
|
||||
}
|
||||
|
||||
function peg$literalExpectation(text, ignoreCase) {
|
||||
return { type: "literal", text: text, ignoreCase: ignoreCase };
|
||||
}
|
||||
|
||||
function peg$classExpectation(parts, inverted, ignoreCase) {
|
||||
return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
|
||||
}
|
||||
|
||||
function peg$anyExpectation() {
|
||||
return { type: "any" };
|
||||
}
|
||||
|
||||
function peg$endExpectation() {
|
||||
return { type: "end" };
|
||||
}
|
||||
|
||||
function peg$otherExpectation(description) {
|
||||
return { type: "other", description: description };
|
||||
}
|
||||
|
||||
function peg$computePosDetails(pos) {
|
||||
var details = peg$posDetailsCache[pos];
|
||||
var p;
|
||||
|
||||
if (details) {
|
||||
return details;
|
||||
} else {
|
||||
if (pos >= peg$posDetailsCache.length) {
|
||||
p = peg$posDetailsCache.length - 1;
|
||||
} else {
|
||||
p = pos;
|
||||
while (!peg$posDetailsCache[--p]) {}
|
||||
}
|
||||
|
||||
details = peg$posDetailsCache[p];
|
||||
details = {
|
||||
line: details.line,
|
||||
column: details.column
|
||||
};
|
||||
|
||||
while (p < pos) {
|
||||
if (input.charCodeAt(p) === 10) {
|
||||
details.line++;
|
||||
details.column = 1;
|
||||
} else {
|
||||
details.column++;
|
||||
}
|
||||
|
||||
p++;
|
||||
}
|
||||
|
||||
peg$posDetailsCache[pos] = details;
|
||||
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
function peg$computeLocation(startPos, endPos, offset) {
|
||||
var startPosDetails = peg$computePosDetails(startPos);
|
||||
var endPosDetails = peg$computePosDetails(endPos);
|
||||
|
||||
var res = {
|
||||
source: peg$source,
|
||||
start: {
|
||||
offset: startPos,
|
||||
line: startPosDetails.line,
|
||||
column: startPosDetails.column
|
||||
},
|
||||
end: {
|
||||
offset: endPos,
|
||||
line: endPosDetails.line,
|
||||
column: endPosDetails.column
|
||||
}
|
||||
};
|
||||
if (offset && peg$source && (typeof peg$source.offset === "function")) {
|
||||
res.start = peg$source.offset(res.start);
|
||||
res.end = peg$source.offset(res.end);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function peg$fail(expected) {
|
||||
if (peg$currPos < peg$maxFailPos) { return; }
|
||||
|
||||
if (peg$currPos > peg$maxFailPos) {
|
||||
peg$maxFailPos = peg$currPos;
|
||||
peg$maxFailExpected = [];
|
||||
}
|
||||
|
||||
peg$maxFailExpected.push(expected);
|
||||
}
|
||||
|
||||
function peg$buildSimpleError(message, location) {
|
||||
return new peg$SyntaxError(message, null, null, location);
|
||||
}
|
||||
|
||||
function peg$buildStructuredError(expected, found, location) {
|
||||
return new peg$SyntaxError(
|
||||
peg$SyntaxError.buildMessage(expected, found),
|
||||
expected,
|
||||
found,
|
||||
location
|
||||
);
|
||||
}
|
||||
|
||||
function peg$parseAdd() {
|
||||
var s0, s1, s2;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parseDescription();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseAttributes();
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f0(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseDescription() {
|
||||
var s0, s1, s2, s3, s4;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$currPos;
|
||||
peg$silentFails++;
|
||||
s4 = peg$parseAttribute();
|
||||
peg$silentFails--;
|
||||
if (s4 === peg$FAILED) {
|
||||
s3 = undefined;
|
||||
} else {
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
if (input.length > peg$currPos) {
|
||||
s4 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s4 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e0); }
|
||||
}
|
||||
if (s4 !== peg$FAILED) {
|
||||
s3 = [s3, s4];
|
||||
s2 = s3;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$currPos;
|
||||
peg$silentFails++;
|
||||
s4 = peg$parseAttribute();
|
||||
peg$silentFails--;
|
||||
if (s4 === peg$FAILED) {
|
||||
s3 = undefined;
|
||||
} else {
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
if (input.length > peg$currPos) {
|
||||
s4 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s4 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e0); }
|
||||
}
|
||||
if (s4 !== peg$FAILED) {
|
||||
s3 = [s3, s4];
|
||||
s2 = s3;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f1(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseAttributes() {
|
||||
var s0, s1, s2, s3, s4;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$parse_();
|
||||
s4 = peg$parseAttribute();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s3 = [s3, s4];
|
||||
s2 = s3;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$parse_();
|
||||
s4 = peg$parseAttribute();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s3 = [s3, s4];
|
||||
s2 = s3;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f2(s1);
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseAttribute() {
|
||||
var s0;
|
||||
|
||||
s0 = peg$parseProject();
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parsePriority();
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parseTag();
|
||||
}
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseProject() {
|
||||
var s0, s1, s2;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 4) === peg$c0) {
|
||||
s1 = peg$c0;
|
||||
peg$currPos += 4;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e1); }
|
||||
}
|
||||
if (s1 === peg$FAILED) {
|
||||
if (input.substr(peg$currPos, 8) === peg$c1) {
|
||||
s1 = peg$c1;
|
||||
peg$currPos += 8;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e2); }
|
||||
}
|
||||
if (s1 === peg$FAILED) {
|
||||
if (input.charCodeAt(peg$currPos) === 43) {
|
||||
s1 = peg$c2;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseWord();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f3(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsePriority() {
|
||||
var s0, s1, s2;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 9) === peg$c3) {
|
||||
s1 = peg$c3;
|
||||
peg$currPos += 9;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = input.charAt(peg$currPos);
|
||||
if (peg$r0.test(s2)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e5); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f4(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseTag() {
|
||||
var s0, s1, s2;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 35) {
|
||||
s1 = peg$c4;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parseWord();
|
||||
if (s2 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f5(s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseWord() {
|
||||
var s0, s1, s2;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = [];
|
||||
s2 = input.charAt(peg$currPos);
|
||||
if (peg$r1.test(s2)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
s1.push(s2);
|
||||
s2 = input.charAt(peg$currPos);
|
||||
if (peg$r1.test(s2)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f6(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parse_() {
|
||||
var s0, s1;
|
||||
|
||||
s0 = [];
|
||||
s1 = input.charAt(peg$currPos);
|
||||
if (peg$r2.test(s1)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
}
|
||||
while (s1 !== peg$FAILED) {
|
||||
s0.push(s1);
|
||||
s1 = input.charAt(peg$currPos);
|
||||
if (peg$r2.test(s1)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
}
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseEOF() {
|
||||
var s0, s1;
|
||||
|
||||
s0 = peg$currPos;
|
||||
peg$silentFails++;
|
||||
if (input.length > peg$currPos) {
|
||||
s1 = input.charAt(peg$currPos);
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e0); }
|
||||
}
|
||||
peg$silentFails--;
|
||||
if (s1 === peg$FAILED) {
|
||||
s0 = undefined;
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
|
||||
function MakeAdd(description, attributes) {
|
||||
return {
|
||||
description: description,
|
||||
attributes: attributes || []
|
||||
};
|
||||
}
|
||||
|
||||
function MakeAttributes(attributes) {
|
||||
return attributes.flatMap(attr => attr[1]);
|
||||
}
|
||||
|
||||
peg$result = peg$startRuleFunction();
|
||||
|
||||
if (options.peg$library) {
|
||||
return /** @type {any} */ ({
|
||||
peg$result,
|
||||
peg$currPos,
|
||||
peg$FAILED,
|
||||
peg$maxFailExpected,
|
||||
peg$maxFailPos
|
||||
});
|
||||
}
|
||||
if (peg$result !== peg$FAILED && peg$currPos === input.length) {
|
||||
return peg$result;
|
||||
} else {
|
||||
if (peg$result !== peg$FAILED && peg$currPos < input.length) {
|
||||
peg$fail(peg$endExpectation());
|
||||
}
|
||||
|
||||
throw peg$buildStructuredError(
|
||||
peg$maxFailExpected,
|
||||
peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
|
||||
peg$maxFailPos < input.length
|
||||
? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
|
||||
: peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const peg$allowedStartRules = [
|
||||
"Add"
|
||||
];
|
||||
|
||||
export {
|
||||
peg$allowedStartRules as StartRules,
|
||||
peg$SyntaxError as SyntaxError,
|
||||
peg$parse as parse
|
||||
};
|
|
@ -1,162 +0,0 @@
|
|||
{
|
||||
function makeCommand(type, filters, parts) {
|
||||
|
||||
const validParts = parts || [];
|
||||
// Extract pure description (without attributes)
|
||||
const description = validParts
|
||||
.filter(part => part.type === 'text')
|
||||
.map(part => part.value)
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
// Collect all attributes
|
||||
const attributes = validParts
|
||||
.filter(part => part.type !== 'text')
|
||||
.filter(part => part !== null);
|
||||
|
||||
const project = validParts
|
||||
.filter(part => part.type === 'project')
|
||||
.map(part => part.value)
|
||||
.join('')
|
||||
.trim();
|
||||
|
||||
const tags = validParts
|
||||
.filter(part => part.type === 'tag')
|
||||
.map(part => part.value);
|
||||
|
||||
|
||||
// Store original parts for reconstruction
|
||||
const originalParts = validParts.filter(part => part !== null);
|
||||
let validFilters;
|
||||
if (!!filters) {
|
||||
validFilters = filters.flat();
|
||||
} else {
|
||||
validFilters = [];
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
description: description || [],
|
||||
attributes: attributes || [],
|
||||
filters: validFilters,
|
||||
project: project,
|
||||
tags: JSON.stringify(tags),
|
||||
parts: parts || [],
|
||||
reconstruct: function() {
|
||||
const filterStr = this.filters.map(f => f.reconstruct()).join(',');
|
||||
const partsStr = this.parts.map(p => p.reconstruct()).join(' ');
|
||||
return [filterStr, this.type, partsStr].filter(Boolean).join(' ');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Start = AddCommand / DoneCommand / ExplicitFilterCommand / ImplicitFilterCommand
|
||||
|
||||
// ADD COMMAND
|
||||
AddCommand = "add" _ parts:(Part / _)+ EOF {
|
||||
return makeCommand('add', null, parts.filter(p => p !== null));
|
||||
}
|
||||
|
||||
// DONE COMMAND
|
||||
DoneCommand = filters:Filters _ "done" EOF {
|
||||
return makeCommand('done', filters, null);
|
||||
}
|
||||
|
||||
ExplicitFilterCommand = filters:Filters _ "filter" _* moreFilters:Filters* EOF {
|
||||
return makeCommand('filter', [...filters, ...moreFilters], null);
|
||||
}
|
||||
|
||||
ImplicitFilterCommand = filters:Filters+ EOF {
|
||||
return makeCommand('filter', filters, null);
|
||||
}
|
||||
|
||||
Filters = first:Filter rest:(_ Filter)* {
|
||||
return [first, ...rest.map(r => r[1])];
|
||||
}
|
||||
|
||||
|
||||
IdRange = start:Integer "-" end:Integer {
|
||||
const ids = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
ids.push(i);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
SingleId = id:Integer {
|
||||
return [id];
|
||||
}
|
||||
|
||||
Filter = IdFilter / Tag / Project / Priority / Due
|
||||
|
||||
IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? {
|
||||
const ids = [first, ...rest.map(r => r[1])].flat();
|
||||
return {
|
||||
type: 'id',
|
||||
ids: ids,
|
||||
reconstruct: function() { return this.ids.join(','); }
|
||||
};
|
||||
}
|
||||
|
||||
Part = Attribute / TextPart
|
||||
|
||||
TextPart = chars:Word {
|
||||
return {
|
||||
type: "text",
|
||||
value: chars,
|
||||
reconstruct: function() { return this.value; }
|
||||
};
|
||||
}
|
||||
|
||||
Attribute = Due / Tag / Project / Priority
|
||||
|
||||
Due = "@" value:TimeValue {
|
||||
return {
|
||||
type: "due",
|
||||
value: value,
|
||||
reconstruct: function() { return `@${this.value}`; }
|
||||
};
|
||||
}
|
||||
|
||||
Project = ("pro:" / "project:" / "+") value:Word {
|
||||
return {
|
||||
type: "project",
|
||||
value: value,
|
||||
reconstruct: function() { return `+${this.value}`; }
|
||||
};
|
||||
}
|
||||
|
||||
Priority = "priority:" value:[HML] {
|
||||
return {
|
||||
type: "priority",
|
||||
value: value,
|
||||
reconstruct: function() { return `priority:${this.value}`; }
|
||||
};
|
||||
}
|
||||
|
||||
Tag = "#" value:Word {
|
||||
return {
|
||||
type: "tag",
|
||||
value: value,
|
||||
reconstruct: function() { return `#${this.value}`; }
|
||||
};
|
||||
}
|
||||
|
||||
Integer = digits:[0-9]+ {
|
||||
return parseInt(digits.join(''), 10);
|
||||
}
|
||||
|
||||
TimeValue = chars:[0-9:]+ ("am" / "pm")? {
|
||||
return chars.join('') + (text() || '');
|
||||
}
|
||||
|
||||
Word = chars:[a-zA-Z0-9_-]+ {
|
||||
return chars.join('');
|
||||
}
|
||||
|
||||
_ = [ \t]+ {
|
||||
return null;
|
||||
}
|
||||
|
||||
EOF = !.
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue