diff --git a/.github/workers/github-login/github-oauth.js b/.github/workers/github-login/github-oauth.js deleted file mode 100644 index c6455a5..0000000 --- a/.github/workers/github-login/github-oauth.js +++ /dev/null @@ -1,63 +0,0 @@ -addEventListener("fetch", (event) => { - event.respondWith(handle(event.request)); -}); - -// use secrets -const client_id = CLIENT_ID; -const client_secret = CLIENT_SECRET; - -async function handle(request) { - // handle CORS pre-flight request - if (request.method === "OPTIONS") { - return new Response(null, { - headers: { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - "Access-Control-Allow-Headers": "Content-Type", - }, - }); - } - - // redirect GET requests to the OAuth login page on github.com - if (request.method === "GET") { - const scope = new URL(request.url).searchParams.get("scope"); - return Response.redirect( - `https://github.com/login/oauth/authorize?client_id=${client_id}&scope=${scope}`, - 302 - ); - } - - try { - const { code } = await request.json(); - - const response = await fetch( - "https://github.com/login/oauth/access_token", - { - method: "POST", - headers: { - "content-type": "application/json", - accept: "application/json", - }, - body: JSON.stringify({ client_id, client_secret, code }), - } - ); - const result = await response.json(); - const headers = { - "Access-Control-Allow-Origin": "*", - }; - - if (result.error) { - return new Response(JSON.stringify(result), { status: 401, headers }); - } - - return new Response(JSON.stringify({ token: result.access_token }), { - status: 201, - headers, - }); - } catch (error) { - console.error(error); - return new Response(error.message, { - status: 500, - }); - } -} \ No newline at end of file diff --git a/.github/workers/github-login/wrangler.toml b/.github/workers/github-login/wrangler.toml deleted file mode 100644 index 9a41642..0000000 --- a/.github/workers/github-login/wrangler.toml +++ /dev/null @@ -1,4 +0,0 @@ -name = "treehouse-demo-login" -workers_dev = true -compatibility_date = "2023-02-08" -main = "github-oauth.js" \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index b9cfe92..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Website Build and Deploy -on: - push: - branches: - - main -permissions: - contents: write -jobs: - build-and-deploy: - concurrency: ci-${{ github.ref }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup Deno - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Build - run: | - deno task bundle - deno task build - env: - BACKEND: github - BACKEND_URL: https://treehouse-demo-login.proteco.workers.dev - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: web/_out \ No newline at end of file diff --git a/.github/workflows/workers.yml b/.github/workflows/workers.yml deleted file mode 100644 index c079b64..0000000 --- a/.github/workflows/workers.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Worker Deploys -on: - push: - branches: - - main - paths: - - '.github/workers/**' - - '.github/workflows/workers.yml' -jobs: - github-login: - concurrency: ci-${{ github.ref }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Publish - uses: cloudflare/wrangler-action@2.0.0 - with: - command: publish - apiToken: ${{ secrets.CF_API_TOKEN }} - accountId: ${{ secrets.CF_ACCOUNT_ID }} - workingDirectory: '.github/workers/github-login' - secrets: | - CLIENT_ID - CLIENT_SECRET - env: - CLIENT_ID: ${{ secrets.DEMO_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.DEMO_CLIENT_SECRET }} \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 98ad90e..0000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -/web/_out -/web/static/lib/treehouse.js -/web/static/lib/treehouse.js.map -/web/static/lib/treehouse.min.js -/web/static/lib/treehouse.min.js.map -/web/_vendor/**/node_modules -/web/_vendor/**/package-lock.json -/web/_vendor/octokit/package.json -/local -.DS_Store -/NOTES \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4b9fb22..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "deno.enable": true -} \ No newline at end of file diff --git a/web/static/CNAME b/CNAME similarity index 100% rename from web/static/CNAME rename to CNAME diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3af2053..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Jeff Lindsay - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index f524d5d..0000000 --- a/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# treehouse - -Bring your own backend Tana-style frontend. Actively being developed. - -![Screenshot](https://treehouse.sh/photos/screenshot-small.png) - -* Outline editor and repurposable workbench shell. -* Built with [Deno](https://deno.land/) toolchain. Zero Node.js utilization. -* Minimal dependencies, mainly [Mithril.js](https://mithril.js.org/). - -## Demo - -This project is meant as a frontend toolkit for your own note-taking app, -but we're maintaining a usable [live demo](https://treehouse.sh/demo/) -based on the current state of the main branch. - -## Development - -Contribute by [installing Deno](https://deno.land/manual@v1.30.0/getting_started/installation), -cloning the repo, and running: - -``` -deno task serve -``` - -This will run a development server for the project site that includes the -demo, which you can use for development. - -## Community - -* [Devstream](https://www.twitch.tv/progrium) - Majority of development is streamed live on Twitch -* [Devlog](https://github.com/treehousedev/treehouse/discussions/categories/devlog) - Devlog updates about development -* [Forums](https://github.com/treehousedev/treehouse/discussions) - Basic discussion on GitHub - -## License - -MIT diff --git a/_config.js b/_config.js deleted file mode 100644 index ded037b..0000000 --- a/_config.js +++ /dev/null @@ -1,86 +0,0 @@ -import lume from "lume/mod.ts"; -import attrs from "npm:markdown-it-attrs"; -import jsx from "lume/plugins/jsx_preact.ts"; -import nav from "lume/plugins/nav.ts"; -import feed from "lume/plugins/feed.ts"; -import codeHighlight from "lume/plugins/code_highlight.ts"; -import lang_javascript from "https://unpkg.com/@highlightjs/cdn-assets@11.6.0/es/languages/javascript.min.js"; -import lang_bash from "https://unpkg.com/@highlightjs/cdn-assets@11.6.0/es/languages/bash.min.js"; -import toc from "https://deno.land/x/lume_markdown_plugins@v0.5.1/toc.ts"; - -import * as esbuild from "https://deno.land/x/esbuild@v0.17.2/mod.js"; - -let lastBuild = 0; - -const site = lume({ - location: new URL("https://treehouse.sh"), - src: "./web", - dest: "./web/_out", - server: { - middlewares: [ - async (request, next) => { - const url = new URL(request.url); - if (url.pathname === "/lib/treehouse.min.js") { - if (lastBuild < Date.now()-1000) { - await esbuild.build({ - entryPoints: ["lib/mod.ts"], - bundle: true, - outfile: "web/_out/lib/treehouse.js", - jsxFactory: "m", - sourcemap: true, - format: "esm", - keepNames: true, - // minify: true - }); - lastBuild = Date.now(); - } - Object.defineProperty(request, "url", {value: request.url.replace(".min", "")}); - } - return await next(request); - } - ] - } -}, { - markdown: { - plugins: [attrs], - keepDefaultPlugins: true, - } -}); -site.copy("static", "."); - -site.use(feed({ - output: ["/blog/feed.rss", "/blog/feed.json"], - query: "layout=layouts/blog.tsx", - info: { - title: "=site.title", - description: "=site.description", - }, - items: { - title: "=title", - }, -})); -site.use(toc({ - level: 2, - anchor: false -})); -site.use(nav()); -site.use(jsx()); -site.use(codeHighlight({ - languages: { - javascript: lang_javascript, - bash: lang_bash, - }, -})); - -site.filter("formatDate", (value) => new Date(value).toLocaleDateString('en-us', { year:"numeric", month:"short", day:"numeric"})); - -site.data("site", { - title: "Treehouse", - description: "An open source note-taking frontend to extend and customize." -}); -site.data("backend", JSON.stringify({ - name: Deno.env.get("BACKEND") || "browser", - url: Deno.env.get("BACKEND_URL") -})); - -export default site; diff --git a/web/static/analytics.js b/analytics.js similarity index 100% rename from web/static/analytics.js rename to analytics.js diff --git a/web/static/app/main.css b/app/main.css similarity index 100% rename from web/static/app/main.css rename to app/main.css diff --git a/web/static/app/main.js b/app/main.js similarity index 100% rename from web/static/app/main.js rename to app/main.js diff --git a/web/static/app/main.webmanifest b/app/main.webmanifest similarity index 100% rename from web/static/app/main.webmanifest rename to app/main.webmanifest diff --git a/blog/feed.json b/blog/feed.json new file mode 100644 index 0000000..5ba2fc1 --- /dev/null +++ b/blog/feed.json @@ -0,0 +1 @@ +{"version":"https://jsonfeed.org/version/1","title":"Treehouse","home_page_url":"https://treehouse.sh/","feed_url":"https://treehouse.sh/blog/feed.json","description":"An open source note-taking frontend to extend and customize.","items":[{"id":"https://treehouse.sh/blog/v0-7-0/","url":"https://treehouse.sh/blog/v0-7-0/","title":"Release 0.7.0","content_html":"

Descriptions, better paste, shortcut drawer

\n

We're excited to be adding a handful of minor enhancements in this release. The first is node descriptions, which allow you to add small text below your node content, as part of the same node. Use it to add context or any kind of secondary information. To use, select your node, open the Command Palette with Command/Control + K, and select "Add Description".

\n

We've made several improvements related to cut/copy/pasting text. Now if you paste text containing new lines, we'll convert each newline to a separate node. We've also fixed some bugs and UX flows.

\n

Lastly, check out the keyboard shortcut drawer! We want it to be super easy to learn and use keyboard shortcuts and hope this helps.

\n

Enhancements and Chores

\n\n

Bugs

\n\n
\n

Discuss on GitHub

\n","date_published":"Fri, 18 Aug 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-6-0/","url":"https://treehouse.sh/blog/v0-6-0/","title":"Release 0.6.0","content_html":"

New Document and Tab Views

\n

In this release we're introducing two new views: Document view, and Tab view. Views are a powerful idea in Treehouse that can either be used to create embedded mini-apps or act as building blocks for you to make your own. These new views are an example of each.

\n

Document view gives you a more conventional note-taking experience. In Document view each child node is rendered as a paragraph in the center of the panel. Documents support simple Markdown formatting like bold, italic, strikethrough, and ordered and unordered lists. Turn a node into a document by choosing "Make Document" from the Command Palette. Unlike other views, changing to Document view is a one-way action and nodes can't be converted back to other views.

\n

Tab view renders each child node as a tab. Their children (grandchild nodes) are shown beneath when the tab is selected. These nodes will retain their view, so you can combine Tab view with others to create a more complex larger view of your data. In general, Tab view is useful for saving vertical space across several categories. Turn nodes into tabs by selecting the parent node and choose "View as Tabs" from the Command Palette. Since Tab view is read-only, you can modify tabs by switching back to List view.

\n

We've also renamed Live Search Nodes to Smart Nodes, which can now be named separate from their query. This also resolves an issue where previously you couldn't use them for tag searches.

\n

\"Watch\nDemo video

\n

Enhancements and Chores

\n\n

Bugfixes

\n\n
\n

Discuss on GitHub

\n","date_published":"Thu, 20 Jul 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-5-0/","url":"https://treehouse.sh/blog/v0-5-0/","title":"Release 0.5.0","content_html":"

Tags, templates, and table view

\n

Treehouse now supports tags and templates! For example, you can now create a template node with children and fields, name it "book", then any node tagged with #book will automatically get those fields and children. To turn a node into a template, select "Make Template" from the Command Palette. You can also tag nodes without an equivalent template. Tagging is done by simply adding hashtags to a node's text.

\n

You can search for tagged nodes in the search bar using the hashtag notation (#book), however there is a known issue preventing you from searching for nodes by tag in Smart Nodes. This will be resolved in the next release, and in main much sooner.

\n

You can now take a list of nodes with fields and turn them into an easy-to-scan table. Simply go to the parent node of the rows, open the Command Palette with Command+K, and choose "View as Table". We'll be adding more features to the table view in coming releases.

\n

Double-quoted search terms now allow spaces and search results are a little tighter now. Last but not least we've added cut, copy, and paste commands for nodes, making it easier to duplicate and move nodes around.

\n

\"Watch\nDemo video

\n

Enhancements and Chores

\n\n

Bugfixes

\n\n
\n

Discuss on GitHub

\n","date_published":"Thu, 06 Jul 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-4-0/","url":"https://treehouse.sh/blog/v0-4-0/","title":"Release 0.4.0","content_html":"

Mobile support and CSS themes

\n

In this release we focused on making Treehouse more powerful and enjoyable to use from more places. Editing text on mobile is always a tricky prospect, but we've taken our first step to being mobile-friendly with customized navigation and easier touch targets.

\n

White background a bit too bright? We've added several built-in themes (including sepia and dark mode) and we've made it super easy to create your own CSS themes using our component variables.

\n

Otherwise, lots of quality of life improvements as usual, especially for fields and Smart Nodes.

\n

\"Watch\nDemo video

\n

Enhancements and Chores

\n\n

Bugfixes

\n\n
\n

Discuss on GitHub

\n","date_published":"Mon, 19 Jun 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-3-0/","url":"https://treehouse.sh/blog/v0-3-0/","title":"Release 0.3.0","content_html":"

New Node Types: Fields, Live Search, and References

\n

This release we added some exciting new features: Fields, Live Search, and References. These new node types represent our first step into becoming much more than a simple outliner. Quality of live improvements include making the node menu easier to click, initial work towards a mobile view, and improving interactivity of the command palette. We also cleaned up all our UI elements to match our design system, allowing for better custom CSS support.

\n

Update 7/10/2023: Live Search is now called Smart Nodes

\n

\"Watch\nDemo video

\n

New Feature: Fields

\n

A field is a node that can store a key-value pair. This introduces structured data to your nodes, letting you create nodes as data records. You can also search for nodes by field. This initial pass supports text values, but we'll soon provide more value types.

\n

To turn a node into a field:

\n
    \n
  1. Indent underneath the node you want to contain the field, and type the field name
  2. \n
  3. Command/Control + K to open the command palette, and choose "Create Field"
  4. \n
  5. Add your field value in the value section
  6. \n
\n

New Feature: Live Search

\n

Live Search allows you to create search nodes where the children are auto-updating search results. Simply type a keyword, or use the format "fieldname:value" to filter by fields. The Live Search nodes will update automatically as your workspace content changes. This is a powerful way to view your data in new configurations.

\n

To create a Live Search:

\n
    \n
  1. Create a new node where you want your search node, and type your search value
  2. \n
  3. Command/Control + K to open the command palette, and choose "Create Search Node"
  4. \n
\n

Tips: Search terms are case-insensitive, and you can filter on multiple fields (uses AND, not OR) like so: "fieldname:value fieldname:value".

\n

New Feature: References

\n

References are nodes that refer to another node and its children inline. They're sort of like symlinks on the filesystem. This lets you have a node exist in multiple places at once. References are differentiated from normal nodes with a dashed outline around their outline bullet. Deleting a reference node does not delete the node it points to. You may notice that Live Search results are reference nodes!

\n

To create a reference node:

\n
    \n
  1. Select the node you want to make a reference to
  2. \n
  3. Command/Control + K to open the command palette, and choose "Create Reference"
  4. \n
\n

Bugfixes

\n\n

Enhancements and Chores

\n\n
\n

Discuss on GitHub

\n","date_published":"Wed, 17 May 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/influences/","url":"https://treehouse.sh/blog/influences/","title":"Treehouse Influences","content_html":"

Over the past few months, we've built a frontend framework for an elegant, quality outliner that's open source, extensible, and gives you control of your data. I’d like to share some of the design influences for the Treehouse frontend, which should give a sense of the unique direction Treehouse is going from here.

\n

The biggest influences for Treehouse are Tana, Notion, and Obsidian. These three represent the state of the art of personal and collaborative information management, sometimes simplified as note-taking tools. However, "note-taking tools" sells them short as they go beyond note-taking and information management. For lack of a better descriptor, many consider them tools for thought.

\n

From Notes to Tools for Thought

\n

For most, note-taking brings to mind simple apps like Apple Notes and Google Keep, or even just a text file editor. These work well for people because they're already there and focus on quick and easy plain text capture. We could call this casual note-taking.

\n

Back in the 2000s, hosted and self-hosted wikis became popular for easy, collaborative web publishing and knowledge management. Like Wikipedia, they could be used to build out hyperlinked knowledge repositories. Many wiki-based tools focused on their use as personal notebooks, one of the most influential examples being TiddlyWiki. The simple versatility of the wiki laid the groundwork for what we call "tools for thought" today.

\n

\"TiddlyWiki

\n

When Notion appeared in the mid-2010s, it built on the idea of the wiki and introduced structured data management with flexible views that effectively gave you integrated, customizable versions of other productivity tools. Notion, Airtable, and others helped bring in the age of no-code and low-code tools, allowing knowledge workers and entrepreneurs to build their own "apps" or solutions to problems without traditionally building software. Notion brought it all together in a simple, user-friendly experience based around the core idea of wiki-like information management.

\n

\"Notion

\n

Meanwhile, a separate paradigm of note-taking tools emerged, focusing on the nested, tree-like structure of the outline. Perhaps inspired by tools like OmniOutliner and Org Mode for Emacs of the 2000s, Workflowy appeared in 2010 as a no-frills web-based outliner.

\n

\"Workflowy

\n

Obsidian arrived in 2020 and is a local app focusing on Markdown files stored on your filesystem. Obsidian has a large plugin ecosystem giving it a wide breadth of features, but it’s especially appealing to those that want to own their data. If you strip away the plugins, Obsidian is a pretty simple hyperlinked Markdown editor.

\n

\"Obsidian

\n

Most recently, a tool in early access called Tana caught my attention. Their key innovation is taking the linked outline model of Workflowy and introducing schemas for nodes, making them into structured data. This gives Tana the embedded database functionality of Notion and Airtable, a step towards bringing the two paradigms of note-taking software together towards powerful, malleable tools for thought.

\n

\"Tana

\n

How Treehouse Fits In

\n

By now there's no shortage of options in this space, both as SaaS and open source. Take a look at this growing encyclopedia of note-taking tools. Like Notion and Tana, many of the apps listed are much more than note-taking tools. Some lean into the framing of "collaborative documents", and some are just categorized more generally as "productivity tools". Tana goes so far as to say "the everything OS".

\n

Note-taking is just the beginning. It's a tangible gateway for something more powerful inherent to computing. Ever since Engelbart's mother of all demos, the computing revolution seems to start with powerful tools for thought, which are, at minimum, good note-taking tools.

\n

\"Engelbart

\n

Treehouse is a frontend and starter kit for anybody else that wants to explore this space with us. We will release a standalone product based on it soon, but most of the user-facing development will be done in the open source Treehouse project.

\n

\"Treehouse

\n

Today with Treehouse you can build your own Workflowy equivalent, but soon it will become more comparable to Tana and Notion with the open extensibility of Obsidian. That alone is pretty exciting to have in a minimal open source project, but I can't wait to show you what will come next.

\n

Coming Soon

\n

In the next post, I'll start getting technical and share details on the Treehouse project stack and architecture. If you can't wait, we do have documentation for you to check out.

\n

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work. Share your thoughts and favorite note-taking tools in the discussion thread for this post.

\n","date_published":"Thu, 20 Apr 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-2-0/","url":"https://treehouse.sh/blog/v0-2-0/","title":"Release 0.2.0","content_html":"

New design system, GitHub session locking, and documentation

\n

This release is a refinement of our initial release, fixing a number of bugs and adding interaction improvements. The look and feel of the UI was also updated with the start of a new CSS design system based on custom properties. Session locking was added for the live demo and GitHub backend so multiple devices/browsers/tabs don't clobber changes of each other. Documentation also got an upgrade with the start of a full guide on the website.

\n

\"Watch\nDemo video

\n

Bugfixes

\n\n

Enhancements and Chores

\n\n
\n

Discuss on GitHub

\n","date_published":"Mon, 27 Mar 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/v0-1-0/","url":"https://treehouse.sh/blog/v0-1-0/","title":"Release 0.1.0","content_html":"

It begins...

\n

This first development release captures core functionality of the frontend to the point of being internally usable in the demo deployment. For developers, the project source layout, architecture, and backend API is defined in broad strokes in the right direction but is by no means stable.

\n

\"Watch\nDemo video

\n

Initial Functionality

\n\n

Enhancements

\n\n

Bugfixes

\n\n","date_published":"Fri, 03 Mar 2023 00:00:00 GMT"},{"id":"https://treehouse.sh/blog/welcome/","url":"https://treehouse.sh/blog/welcome/","title":"Welcome to Treehouse","content_html":"

I'm excited to announce the start of a new major project that I'll be sharing the journey of on this fancy new blog. The project is called Treehouse, which is starting as an open source note-taking frontend and will evolve into something much more.

\n

\"Treehouse

\n

We're creating a simple, hackable kernel of a note-taking tool as a web frontend that can be extended and customized by developers. Then we'll use that frontend to build a new kind of note-taking product. Today we have an early preview of our first minimum viable prototype of the frontend, and we'll be finishing up this release in the open on GitHub.

\n

The open source project is a functional app frontend that you can deploy and back in various ways. For developers, this "bring your own backend" approach makes Treehouse a great starting point to build your own note-taking tool, just as we intend to do. We also have pre-made backends for those who want to use it with little-to-no programming. Either way, you'll always own your data, and now the system presenting it to you as well.

\n

In this post, I'm going to talk about our minimum viable prototype and the approach we're taking with Treehouse as an open source project.

\n

Out of the Box

\n

While inspired by powerful tools like Notion, Tana, and Obsidian, we wanted to boil our initial goal down to the essentials while still being usable enough to use ourselves. As we continue to plan our prototype release milestone, we'd love to hear what you think is essential for you to have in a tool and platform like this, but here is what to expect as a baseline.

\n

Outliner with Markdown Pages

\n

At the heart of Treehouse is a graph-like system I've been developing called Manifold. This will play a big part in the long-term extensibility of Treehouse, which I'll talk more about in the future. For now, this maps very cleanly to the outliner model popularized by Workflowy, Tana, and others.

\n

However, I'm also a fan of Notion-style pages and Obsidian's commitment to working with plain Markdown files. The Manifold system allows us to make certain nodes into Markdown pages. This hybrid model gets us the best of both worlds with room for even more possibilities later on.

\n

Quick Add and Daily Notes

\n

Being able to quickly get thoughts and information into the system has been a key aspect of every good note-taking system. This can be exposed a number of ways from browser extensions to desktop shortcuts, but what makes any "quick add" functionality quick is not having to think about where it will be organized.

\n

Luckily, "daily notes" has become such a common pattern that many recent tools have managed daily notes built-in. This typical gives you a "today" note that is automatically organized into a calendar-like structure. This conveniently becomes the perfect destination for "quick add" notes.

\n

Full-text Search

\n

Whether you are an obsessive organizer or are too busy to take the time, solid full-text search is basically a necessity. It's how we quickly get to our data.

\n

Out of the box we have a full-text search that doesn't require a backend. However, our pluggable backend will allow you to power search in a number of ways, and also opens up the ability to search into external systems that are important to you.

\n

Built-in Backends

\n

"Bring your own backend" is a critical part of the design of this project, but Treehouse also needs to be usable by people who prefer not to build their own backend. Our prototype release is planned to include a handful of built-in backends to get started with.

\n

Our preview demo is using a localStorage backend, which works for development and special use cases. Of course, we will have a simple local filesystem backend for desktop scenarios, similar to Obsidian.

\n

However, we're most excited for the GitHub backend. This functions similarly to the local filesystem backend, but it would be versioned in a central repository on your GitHub account and be accessible on any online platform.

\n

Backends will not only let you extend storage, search, and authentication, but other aspects in the future.

\n

Project Overview

\n

The Treehouse codebase and open source project is as much the product as its features. The entire user and developer experience is being designed around simplicity and human ergonomics.

\n

The Treehouse frontend is built with web technologies intended for use across web, mobile, and desktop platforms. The project is written in TypeScript using Deno as its JavaScript toolchain.

\n

The JavaScript ecosystem is a mess, but Deno provides a forward-looking, self-contained toolchain that's easy to love. We actually have a zero Node.js policy and avoid NPM modules as much as possible.

\n

With minimal dependencies and an ongoing effort to keep the codebase small, it will always be easy to understand the entire Treehouse system. This will be further supported with a focus on documentation, both API docs and guides.

\n

We're using a permissive open source license, allowing you to use or change our code as you see fit. Contribution and participation is welcome, but so is simply consuming downstream.

\n

The project is actively being developed but we keep a preview demo running off the main branch that should always be in working order.

\n

Coming Soon

\n

I'm excited to give you a deeper look at the project stack in a future post. For now, keep an eye out for the next post digging into the influences for Treehouse. We'll also try to answer "why another note-taking tool", and tease some long-term ideas for the future.

\n

Lastly, I want to mention the project is being developed in the open, not just with the code on GitHub, but most of my work is streamed and archived on Twitch. Feel free to come and co-work with me.

\n

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work.

\n","date_published":"Thu, 23 Feb 2023 00:00:00 GMT"}]} \ No newline at end of file diff --git a/blog/feed.rss b/blog/feed.rss new file mode 100644 index 0000000..abccf5c --- /dev/null +++ b/blog/feed.rss @@ -0,0 +1,385 @@ + + + + Treehouse + https://treehouse.sh/ + + An open source note-taking frontend to extend and customize. + Tue, 26 Nov 2024 18:57:49 GMT + en + Lume v1.17.5 + + Release 0.7.0 + https://treehouse.sh/blog/v0-7-0/ + https://treehouse.sh/blog/v0-7-0/ + + + Descriptions, better paste, shortcut drawer +

We're excited to be adding a handful of minor enhancements in this release. The first is node descriptions, which allow you to add small text below your node content, as part of the same node. Use it to add context or any kind of secondary information. To use, select your node, open the Command Palette with Command/Control + K, and select "Add Description".

+

We've made several improvements related to cut/copy/pasting text. Now if you paste text containing new lines, we'll convert each newline to a separate node. We've also fixed some bugs and UX flows.

+

Lastly, check out the keyboard shortcut drawer! We want it to be super easy to learn and use keyboard shortcuts and hope this helps.

+

Enhancements and Chores

+
    +
  • Node descriptions #295
  • +
  • Support searching for field keys containing spaces #277
  • +
  • Newlines should be translated as new nodes when pasting from outside sources #250
  • +
  • When multiple panels, all panels should be equal width #259
  • +
  • Keyboard shortcut reference drawer #264
  • +
  • Sublime theme - adjust pink to improve contrast #284
  • +
  • Use real italics/bold font variant for Codemirror (treehouse) #272
  • +
+

Bugs

+
    +
  • Can't paste into (+) node #275
  • +
  • Prevent multiple checkboxes #299
  • +
  • Cut node pastes when you select cut, not paste #293
  • +
  • "Back" link should not require two clicks #257
  • +
  • Cutting the node should remove it from view #274
  • +
  • TypeError when smart node returns results #269
  • +
  • TypeError related to command palette + tags #258
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Fri, 18 Aug 2023 00:00:00 GMT +
+ + Release 0.6.0 + https://treehouse.sh/blog/v0-6-0/ + https://treehouse.sh/blog/v0-6-0/ + + + New Document and Tab Views +

In this release we're introducing two new views: Document view, and Tab view. Views are a powerful idea in Treehouse that can either be used to create embedded mini-apps or act as building blocks for you to make your own. These new views are an example of each.

+

Document view gives you a more conventional note-taking experience. In Document view each child node is rendered as a paragraph in the center of the panel. Documents support simple Markdown formatting like bold, italic, strikethrough, and ordered and unordered lists. Turn a node into a document by choosing "Make Document" from the Command Palette. Unlike other views, changing to Document view is a one-way action and nodes can't be converted back to other views.

+

Tab view renders each child node as a tab. Their children (grandchild nodes) are shown beneath when the tab is selected. These nodes will retain their view, so you can combine Tab view with others to create a more complex larger view of your data. In general, Tab view is useful for saving vertical space across several categories. Turn nodes into tabs by selecting the parent node and choose "View as Tabs" from the Command Palette. Since Tab view is read-only, you can modify tabs by switching back to List view.

+

We've also renamed Live Search Nodes to Smart Nodes, which can now be named separate from their query. This also resolves an issue where previously you couldn't use them for tag searches.

+

Watch Demo + Demo video

+

Enhancements and Chores

+
    +
  • Tabs view #126
  • +
  • Document view #246
  • +
  • Support setting a name for a Smart Node #235
  • +
  • Clean up command palette commands #242
  • +
  • Rename live search/search node to Smart Node #248
  • +
+

Bugfixes

+
    +
  • Range error when turning a tag into a search node #236
  • +
  • Don't allow outdenting a top-level node #234
  • +
  • Can't interact with an empty node in Quick Add (mobile) #204
  • +
  • Capital letters in GitHub username prevent login #245
  • +
  • Can't select a theme in Chrome #266
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Thu, 20 Jul 2023 00:00:00 GMT +
+ + Release 0.5.0 + https://treehouse.sh/blog/v0-5-0/ + https://treehouse.sh/blog/v0-5-0/ + + + Tags, templates, and table view +

Treehouse now supports tags and templates! For example, you can now create a template node with children and fields, name it "book", then any node tagged with #book will automatically get those fields and children. To turn a node into a template, select "Make Template" from the Command Palette. You can also tag nodes without an equivalent template. Tagging is done by simply adding hashtags to a node's text.

+

You can search for tagged nodes in the search bar using the hashtag notation (#book), however there is a known issue preventing you from searching for nodes by tag in Smart Nodes. This will be resolved in the next release, and in main much sooner.

+

You can now take a list of nodes with fields and turn them into an easy-to-scan table. Simply go to the parent node of the rows, open the Command Palette with Command+K, and choose "View as Table". We'll be adding more features to the table view in coming releases.

+

Double-quoted search terms now allow spaces and search results are a little tighter now. Last but not least we've added cut, copy, and paste commands for nodes, making it easier to duplicate and move nodes around.

+

Watch Demo + Demo video

+

Enhancements and Chores

+
    +
  • Tags and templates #206
  • +
  • Cut/copy/paste nodes #194
  • +
  • Table view for nodes #106, #120
  • +
  • Support field search terms containing spaces #153
  • +
  • Link to documentation from within Treehouse #175
  • +
+

Bugfixes

+
    +
  • Search bug when capitalizing search term #217
  • +
  • Search is behaving too broadly when matching #197
  • +
  • Support multiple concurrent dialogs #203
  • +
  • Support editing "Calendar" node without Today making a new one #232
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Thu, 06 Jul 2023 00:00:00 GMT +
+ + Release 0.4.0 + https://treehouse.sh/blog/v0-4-0/ + https://treehouse.sh/blog/v0-4-0/ + + + Mobile support and CSS themes +

In this release we focused on making Treehouse more powerful and enjoyable to use from more places. Editing text on mobile is always a tricky prospect, but we've taken our first step to being mobile-friendly with customized navigation and easier touch targets.

+

White background a bit too bright? We've added several built-in themes (including sepia and dark mode) and we've made it super easy to create your own CSS themes using our component variables.

+

Otherwise, lots of quality of life improvements as usual, especially for fields and Smart Nodes.

+

Watch Demo + Demo video

+

Enhancements and Chores

+
    +
  • Support setting a theme in the UI, and custom CSS themes #168, #122
  • +
  • Mobile improvements: new navigation and improved interactivity #114, #115
  • +
  • Allow partial phrase match for command palette search #148
  • +
  • UX improvements for search nodes #134
  • +
  • Only show new-node plus sign if a node is expanded and has no children #149
  • +
  • Add keyboard shortcuts to command palette #129
  • +
  • Prevent turning nodes with children into search nodes #161
  • +
  • Support multiple workflows for creating fields #152
  • +
  • Show visual indicator when search node is collapsed #150
  • +
  • Consolidate search logic #159
  • +
  • Consolidate overlays with dialog element #158
  • +
  • Support multiple workflows for creating fields#152
  • +
+

Bugfixes

+
    +
  • Backspace behavior on fields should be predictable #130
  • +
  • Don't collapse above node when outdenting #131
  • +
  • Backspacing from beginning of node deletes children #151
  • +
  • Prevent edit interactions on search node #105
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Mon, 19 Jun 2023 00:00:00 GMT +
+ + Release 0.3.0 + https://treehouse.sh/blog/v0-3-0/ + https://treehouse.sh/blog/v0-3-0/ + + + New Node Types: Fields, Live Search, and References +

This release we added some exciting new features: Fields, Live Search, and References. These new node types represent our first step into becoming much more than a simple outliner. Quality of live improvements include making the node menu easier to click, initial work towards a mobile view, and improving interactivity of the command palette. We also cleaned up all our UI elements to match our design system, allowing for better custom CSS support.

+

Update 7/10/2023: Live Search is now called Smart Nodes

+

Watch Demo + Demo video

+

New Feature: Fields

+

A field is a node that can store a key-value pair. This introduces structured data to your nodes, letting you create nodes as data records. You can also search for nodes by field. This initial pass supports text values, but we'll soon provide more value types.

+

To turn a node into a field:

+
    +
  1. Indent underneath the node you want to contain the field, and type the field name
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Field"
  4. +
  5. Add your field value in the value section
  6. +
+ +

Live Search allows you to create search nodes where the children are auto-updating search results. Simply type a keyword, or use the format "fieldname:value" to filter by fields. The Live Search nodes will update automatically as your workspace content changes. This is a powerful way to view your data in new configurations.

+

To create a Live Search:

+
    +
  1. Create a new node where you want your search node, and type your search value
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Search Node"
  4. +
+

Tips: Search terms are case-insensitive, and you can filter on multiple fields (uses AND, not OR) like so: "fieldname:value fieldname:value".

+

New Feature: References

+

References are nodes that refer to another node and its children inline. They're sort of like symlinks on the filesystem. This lets you have a node exist in multiple places at once. References are differentiated from normal nodes with a dashed outline around their outline bullet. Deleting a reference node does not delete the node it points to. You may notice that Live Search results are reference nodes!

+

To create a reference node:

+
    +
  1. Select the node you want to make a reference to
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Reference"
  4. +
+

Bugfixes

+
    +
  • Command palette now uses normalized title #117
  • +
  • Command palette supports mouse interactivity #102
  • +
  • Components no longer fail with bundled library #119
  • +
  • Ensure Node IDs are unique #104
  • +
  • Reference sub-nodes now include fields #121
  • +
  • Search nodes no longer production workspace #118
  • +
  • Prevent indenting/outdenting a field node #116
  • +
+

Enhancements and Chores

+
    +
  • New feature: Field nodes
  • +
  • New feature: Live Search nodes
  • +
  • New feature: Reference nodes
  • +
  • Clear out non-existent node keys in workspace document under expanded #120
  • +
  • Don't show bullet for empty nodes #31
  • +
  • Allow hovering over menu area to show menu #76
  • +
  • Clean up styles on search bar, palette, quick add, menu, buttons #81, #85, #96, #108, #110
  • +
  • Show placeholder text for empty field key/value inputs #135
  • +
  • Implement initial version of mobile web app #103
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Wed, 17 May 2023 00:00:00 GMT +
+ + Treehouse Influences + https://treehouse.sh/blog/influences/ + https://treehouse.sh/blog/influences/ + + + Over the past few months, we've built a frontend framework for an elegant, quality outliner that's open source, extensible, and gives you control of your data. I’d like to share some of the design influences for the Treehouse frontend, which should give a sense of the unique direction Treehouse is going from here.

+

The biggest influences for Treehouse are Tana, Notion, and Obsidian. These three represent the state of the art of personal and collaborative information management, sometimes simplified as note-taking tools. However, "note-taking tools" sells them short as they go beyond note-taking and information management. For lack of a better descriptor, many consider them tools for thought.

+

From Notes to Tools for Thought

+

For most, note-taking brings to mind simple apps like Apple Notes and Google Keep, or even just a text file editor. These work well for people because they're already there and focus on quick and easy plain text capture. We could call this casual note-taking.

+

Back in the 2000s, hosted and self-hosted wikis became popular for easy, collaborative web publishing and knowledge management. Like Wikipedia, they could be used to build out hyperlinked knowledge repositories. Many wiki-based tools focused on their use as personal notebooks, one of the most influential examples being TiddlyWiki. The simple versatility of the wiki laid the groundwork for what we call "tools for thought" today.

+

TiddlyWiki screenshot

+

When Notion appeared in the mid-2010s, it built on the idea of the wiki and introduced structured data management with flexible views that effectively gave you integrated, customizable versions of other productivity tools. Notion, Airtable, and others helped bring in the age of no-code and low-code tools, allowing knowledge workers and entrepreneurs to build their own "apps" or solutions to problems without traditionally building software. Notion brought it all together in a simple, user-friendly experience based around the core idea of wiki-like information management.

+

Notion screenshot

+

Meanwhile, a separate paradigm of note-taking tools emerged, focusing on the nested, tree-like structure of the outline. Perhaps inspired by tools like OmniOutliner and Org Mode for Emacs of the 2000s, Workflowy appeared in 2010 as a no-frills web-based outliner.

+

Workflowy screenshot

+

Obsidian arrived in 2020 and is a local app focusing on Markdown files stored on your filesystem. Obsidian has a large plugin ecosystem giving it a wide breadth of features, but it’s especially appealing to those that want to own their data. If you strip away the plugins, Obsidian is a pretty simple hyperlinked Markdown editor.

+

Obsidian screenshot

+

Most recently, a tool in early access called Tana caught my attention. Their key innovation is taking the linked outline model of Workflowy and introducing schemas for nodes, making them into structured data. This gives Tana the embedded database functionality of Notion and Airtable, a step towards bringing the two paradigms of note-taking software together towards powerful, malleable tools for thought.

+

Tana screenshot

+

How Treehouse Fits In

+

By now there's no shortage of options in this space, both as SaaS and open source. Take a look at this growing encyclopedia of note-taking tools. Like Notion and Tana, many of the apps listed are much more than note-taking tools. Some lean into the framing of "collaborative documents", and some are just categorized more generally as "productivity tools". Tana goes so far as to say "the everything OS".

+

Note-taking is just the beginning. It's a tangible gateway for something more powerful inherent to computing. Ever since Engelbart's mother of all demos, the computing revolution seems to start with powerful tools for thought, which are, at minimum, good note-taking tools.

+

Engelbart using NLS

+

Treehouse is a frontend and starter kit for anybody else that wants to explore this space with us. We will release a standalone product based on it soon, but most of the user-facing development will be done in the open source Treehouse project.

+

Treehouse screenshot

+

Today with Treehouse you can build your own Workflowy equivalent, but soon it will become more comparable to Tana and Notion with the open extensibility of Obsidian. That alone is pretty exciting to have in a minimal open source project, but I can't wait to show you what will come next.

+

Coming Soon

+

In the next post, I'll start getting technical and share details on the Treehouse project stack and architecture. If you can't wait, we do have documentation for you to check out.

+

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work. Share your thoughts and favorite note-taking tools in the discussion thread for this post.

+ ]]> +
+ Thu, 20 Apr 2023 00:00:00 GMT +
+ + Release 0.2.0 + https://treehouse.sh/blog/v0-2-0/ + https://treehouse.sh/blog/v0-2-0/ + + + New design system, GitHub session locking, and documentation +

This release is a refinement of our initial release, fixing a number of bugs and adding interaction improvements. The look and feel of the UI was also updated with the start of a new CSS design system based on custom properties. Session locking was added for the live demo and GitHub backend so multiple devices/browsers/tabs don't clobber changes of each other. Documentation also got an upgrade with the start of a full guide on the website.

+

Watch Demo + Demo video

+

Bugfixes

+
    +
  • Autosave error when switching between devices #32
  • +
  • Deleting a node doesn't delete child nodes #25
  • +
  • Hitting return should produce a new node directly below the above node #29
  • +
  • TypeError exception when switching back from new panel #65
  • +
  • Support emojis #52
  • +
+

Enhancements and Chores

+
    +
  • Typography and layout improvements to application #37
  • +
  • Add keyboard shortcut to move nodes up or down #28
  • +
  • Prevent backspace to delete if there are child nodes #15
  • +
  • Allow renaming the workspace #23
  • +
  • Clicking outside of the search bar/command palette should close it #48
  • +
  • Workspace/workbench separation #39
  • +
  • Add API docs #34
  • +
  • Set up versioned library bundle #41
  • +
  • Allow backspace to delete an empty child node #53
  • +
  • Save last location on reloads #54
  • +
  • Hide buttons to move a panel up/down #49
  • +
+
+

Discuss on GitHub

+ ]]> +
+ Mon, 27 Mar 2023 00:00:00 GMT +
+ + Release 0.1.0 + https://treehouse.sh/blog/v0-1-0/ + https://treehouse.sh/blog/v0-1-0/ + + + It begins... +

This first development release captures core functionality of the frontend to the point of being internally usable in the demo deployment. For developers, the project source layout, architecture, and backend API is defined in broad strokes in the right direction but is by no means stable.

+

Watch Demo + Demo video

+

Initial Functionality

+
    +
  • Basic outliner
  • +
  • Commands, menus, keybindings
  • +
  • Workspace model
  • +
  • Navigation tree
  • +
  • Command palette
  • +
  • Multi-view panels
  • +
  • Calendar/today notes
  • +
  • Quick add notes
  • +
  • Full-text search using Minisearch
  • +
  • Localstorage backend
  • +
  • GitHub backend
  • +
+

Enhancements

+
    +
  • Implemented OS detection for registering different keybindings #1
  • +
  • Don't collapse new nested node #3
  • +
  • Hitting tab on plus sign should create an indented node #4
  • +
  • Improve cursor location when manually deleting a node #5
  • +
  • Improve backspace behavior when cursor is at the beginning of a node #6
  • +
  • Add keyboard shortcut to zoom #8
  • +
  • Allow editing title node #9
  • +
  • Hide root node #10
  • +
  • Clean up sidebar text #11
  • +
  • Remember node expansion state #19
  • +
  • Typography updates #21
  • +
+

Bugfixes

+
    +
  • Enable text wrapping for nodes #2
  • +
  • Highlight to delete for nested node gives a TypeError #12
  • +
  • Hitting return mid-node copies contents instead of moving #17
  • +
  • Quick Add formatting issue #18
  • +
  • Error logging in with GitHub #22
  • +
  • Node content disappears when it loses focus #24
  • +
  • Display issue w/ icons overlapping search bar #26
  • +
+ ]]> +
+ Fri, 03 Mar 2023 00:00:00 GMT +
+ + Welcome to Treehouse + https://treehouse.sh/blog/welcome/ + https://treehouse.sh/blog/welcome/ + + + I'm excited to announce the start of a new major project that I'll be sharing the journey of on this fancy new blog. The project is called Treehouse, which is starting as an open source note-taking frontend and will evolve into something much more.

+

Treehouse screenshot

+

We're creating a simple, hackable kernel of a note-taking tool as a web frontend that can be extended and customized by developers. Then we'll use that frontend to build a new kind of note-taking product. Today we have an early preview of our first minimum viable prototype of the frontend, and we'll be finishing up this release in the open on GitHub.

+

The open source project is a functional app frontend that you can deploy and back in various ways. For developers, this "bring your own backend" approach makes Treehouse a great starting point to build your own note-taking tool, just as we intend to do. We also have pre-made backends for those who want to use it with little-to-no programming. Either way, you'll always own your data, and now the system presenting it to you as well.

+

In this post, I'm going to talk about our minimum viable prototype and the approach we're taking with Treehouse as an open source project.

+

Out of the Box

+

While inspired by powerful tools like Notion, Tana, and Obsidian, we wanted to boil our initial goal down to the essentials while still being usable enough to use ourselves. As we continue to plan our prototype release milestone, we'd love to hear what you think is essential for you to have in a tool and platform like this, but here is what to expect as a baseline.

+

Outliner with Markdown Pages

+

At the heart of Treehouse is a graph-like system I've been developing called Manifold. This will play a big part in the long-term extensibility of Treehouse, which I'll talk more about in the future. For now, this maps very cleanly to the outliner model popularized by Workflowy, Tana, and others.

+

However, I'm also a fan of Notion-style pages and Obsidian's commitment to working with plain Markdown files. The Manifold system allows us to make certain nodes into Markdown pages. This hybrid model gets us the best of both worlds with room for even more possibilities later on.

+

Quick Add and Daily Notes

+

Being able to quickly get thoughts and information into the system has been a key aspect of every good note-taking system. This can be exposed a number of ways from browser extensions to desktop shortcuts, but what makes any "quick add" functionality quick is not having to think about where it will be organized.

+

Luckily, "daily notes" has become such a common pattern that many recent tools have managed daily notes built-in. This typical gives you a "today" note that is automatically organized into a calendar-like structure. This conveniently becomes the perfect destination for "quick add" notes.

+ +

Whether you are an obsessive organizer or are too busy to take the time, solid full-text search is basically a necessity. It's how we quickly get to our data.

+

Out of the box we have a full-text search that doesn't require a backend. However, our pluggable backend will allow you to power search in a number of ways, and also opens up the ability to search into external systems that are important to you.

+

Built-in Backends

+

"Bring your own backend" is a critical part of the design of this project, but Treehouse also needs to be usable by people who prefer not to build their own backend. Our prototype release is planned to include a handful of built-in backends to get started with.

+

Our preview demo is using a localStorage backend, which works for development and special use cases. Of course, we will have a simple local filesystem backend for desktop scenarios, similar to Obsidian.

+

However, we're most excited for the GitHub backend. This functions similarly to the local filesystem backend, but it would be versioned in a central repository on your GitHub account and be accessible on any online platform.

+

Backends will not only let you extend storage, search, and authentication, but other aspects in the future.

+

Project Overview

+

The Treehouse codebase and open source project is as much the product as its features. The entire user and developer experience is being designed around simplicity and human ergonomics.

+

The Treehouse frontend is built with web technologies intended for use across web, mobile, and desktop platforms. The project is written in TypeScript using Deno as its JavaScript toolchain.

+

The JavaScript ecosystem is a mess, but Deno provides a forward-looking, self-contained toolchain that's easy to love. We actually have a zero Node.js policy and avoid NPM modules as much as possible.

+

With minimal dependencies and an ongoing effort to keep the codebase small, it will always be easy to understand the entire Treehouse system. This will be further supported with a focus on documentation, both API docs and guides.

+

We're using a permissive open source license, allowing you to use or change our code as you see fit. Contribution and participation is welcome, but so is simply consuming downstream.

+

The project is actively being developed but we keep a preview demo running off the main branch that should always be in working order.

+

Coming Soon

+

I'm excited to give you a deeper look at the project stack in a future post. For now, keep an eye out for the next post digging into the influences for Treehouse. We'll also try to answer "why another note-taking tool", and tease some long-term ideas for the future.

+

Lastly, I want to mention the project is being developed in the open, not just with the code on GitHub, but most of my work is streamed and archived on Twitch. Feel free to come and co-work with me.

+

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work.

+ ]]> +
+ Thu, 23 Feb 2023 00:00:00 GMT +
+
+
\ No newline at end of file diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 0000000..17fa897 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/blog/influences/index.html b/blog/influences/index.html new file mode 100644 index 0000000..1500804 --- /dev/null +++ b/blog/influences/index.html @@ -0,0 +1,73 @@ + +Treehouse Influences - Treehouse

Branching Out

Notes from the Treehouse

Treehouse Influences

Jeff Lindsay
April 20, 2023

Over the past few months, we've built a frontend framework for an elegant, quality outliner that's open source, extensible, and gives you control of your data. I’d like to share some of the design influences for the Treehouse frontend, which should give a sense of the unique direction Treehouse is going from here.

+

The biggest influences for Treehouse are Tana, Notion, and Obsidian. These three represent the state of the art of personal and collaborative information management, sometimes simplified as note-taking tools. However, "note-taking tools" sells them short as they go beyond note-taking and information management. For lack of a better descriptor, many consider them tools for thought.

+

From Notes to Tools for Thought

+

For most, note-taking brings to mind simple apps like Apple Notes and Google Keep, or even just a text file editor. These work well for people because they're already there and focus on quick and easy plain text capture. We could call this casual note-taking.

+

Back in the 2000s, hosted and self-hosted wikis became popular for easy, collaborative web publishing and knowledge management. Like Wikipedia, they could be used to build out hyperlinked knowledge repositories. Many wiki-based tools focused on their use as personal notebooks, one of the most influential examples being TiddlyWiki. The simple versatility of the wiki laid the groundwork for what we call "tools for thought" today.

+

TiddlyWiki screenshot

+

When Notion appeared in the mid-2010s, it built on the idea of the wiki and introduced structured data management with flexible views that effectively gave you integrated, customizable versions of other productivity tools. Notion, Airtable, and others helped bring in the age of no-code and low-code tools, allowing knowledge workers and entrepreneurs to build their own "apps" or solutions to problems without traditionally building software. Notion brought it all together in a simple, user-friendly experience based around the core idea of wiki-like information management.

+

Notion screenshot

+

Meanwhile, a separate paradigm of note-taking tools emerged, focusing on the nested, tree-like structure of the outline. Perhaps inspired by tools like OmniOutliner and Org Mode for Emacs of the 2000s, Workflowy appeared in 2010 as a no-frills web-based outliner.

+

Workflowy screenshot

+

Obsidian arrived in 2020 and is a local app focusing on Markdown files stored on your filesystem. Obsidian has a large plugin ecosystem giving it a wide breadth of features, but it’s especially appealing to those that want to own their data. If you strip away the plugins, Obsidian is a pretty simple hyperlinked Markdown editor.

+

Obsidian screenshot

+

Most recently, a tool in early access called Tana caught my attention. Their key innovation is taking the linked outline model of Workflowy and introducing schemas for nodes, making them into structured data. This gives Tana the embedded database functionality of Notion and Airtable, a step towards bringing the two paradigms of note-taking software together towards powerful, malleable tools for thought.

+

Tana screenshot

+

How Treehouse Fits In

+

By now there's no shortage of options in this space, both as SaaS and open source. Take a look at this growing encyclopedia of note-taking tools. Like Notion and Tana, many of the apps listed are much more than note-taking tools. Some lean into the framing of "collaborative documents", and some are just categorized more generally as "productivity tools". Tana goes so far as to say "the everything OS".

+

Note-taking is just the beginning. It's a tangible gateway for something more powerful inherent to computing. Ever since Engelbart's mother of all demos, the computing revolution seems to start with powerful tools for thought, which are, at minimum, good note-taking tools.

+

Engelbart using NLS

+

Treehouse is a frontend and starter kit for anybody else that wants to explore this space with us. We will release a standalone product based on it soon, but most of the user-facing development will be done in the open source Treehouse project.

+

Treehouse screenshot

+

Today with Treehouse you can build your own Workflowy equivalent, but soon it will become more comparable to Tana and Notion with the open extensibility of Obsidian. That alone is pretty exciting to have in a minimal open source project, but I can't wait to show you what will come next.

+

Coming Soon

+

In the next post, I'll start getting technical and share details on the Treehouse project stack and architecture. If you can't wait, we do have documentation for you to check out.

+

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work. Share your thoughts and favorite note-taking tools in the discussion thread for this post.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-1-0/index.html b/blog/v0-1-0/index.html new file mode 100644 index 0000000..1b109c9 --- /dev/null +++ b/blog/v0-1-0/index.html @@ -0,0 +1,91 @@ + +Release 0.1.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.1.0

Jeff Lindsay
March 3, 2023

It begins...

+

This first development release captures core functionality of the frontend to the point of being internally usable in the demo deployment. For developers, the project source layout, architecture, and backend API is defined in broad strokes in the right direction but is by no means stable.

+

Watch Demo +Demo video

+

Initial Functionality

+
    +
  • Basic outliner
  • +
  • Commands, menus, keybindings
  • +
  • Workspace model
  • +
  • Navigation tree
  • +
  • Command palette
  • +
  • Multi-view panels
  • +
  • Calendar/today notes
  • +
  • Quick add notes
  • +
  • Full-text search using Minisearch
  • +
  • Localstorage backend
  • +
  • GitHub backend
  • +
+

Enhancements

+
    +
  • Implemented OS detection for registering different keybindings #1
  • +
  • Don't collapse new nested node #3
  • +
  • Hitting tab on plus sign should create an indented node #4
  • +
  • Improve cursor location when manually deleting a node #5
  • +
  • Improve backspace behavior when cursor is at the beginning of a node #6
  • +
  • Add keyboard shortcut to zoom #8
  • +
  • Allow editing title node #9
  • +
  • Hide root node #10
  • +
  • Clean up sidebar text #11
  • +
  • Remember node expansion state #19
  • +
  • Typography updates #21
  • +
+

Bugfixes

+
    +
  • Enable text wrapping for nodes #2
  • +
  • Highlight to delete for nested node gives a TypeError #12
  • +
  • Hitting return mid-node copies contents instead of moving #17
  • +
  • Quick Add formatting issue #18
  • +
  • Error logging in with GitHub #22
  • +
  • Node content disappears when it loses focus #24
  • +
  • Display issue w/ icons overlapping search bar #26
  • +
+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-2-0/index.html b/blog/v0-2-0/index.html new file mode 100644 index 0000000..c85ea97 --- /dev/null +++ b/blog/v0-2-0/index.html @@ -0,0 +1,77 @@ + +Release 0.2.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.2.0

Jeff Lindsay
March 27, 2023

New design system, GitHub session locking, and documentation

+

This release is a refinement of our initial release, fixing a number of bugs and adding interaction improvements. The look and feel of the UI was also updated with the start of a new CSS design system based on custom properties. Session locking was added for the live demo and GitHub backend so multiple devices/browsers/tabs don't clobber changes of each other. Documentation also got an upgrade with the start of a full guide on the website.

+

Watch Demo +Demo video

+

Bugfixes

+
    +
  • Autosave error when switching between devices #32
  • +
  • Deleting a node doesn't delete child nodes #25
  • +
  • Hitting return should produce a new node directly below the above node #29
  • +
  • TypeError exception when switching back from new panel #65
  • +
  • Support emojis #52
  • +
+

Enhancements and Chores

+
    +
  • Typography and layout improvements to application #37
  • +
  • Add keyboard shortcut to move nodes up or down #28
  • +
  • Prevent backspace to delete if there are child nodes #15
  • +
  • Allow renaming the workspace #23
  • +
  • Clicking outside of the search bar/command palette should close it #48
  • +
  • Workspace/workbench separation #39
  • +
  • Add API docs #34
  • +
  • Set up versioned library bundle #41
  • +
  • Allow backspace to delete an empty child node #53
  • +
  • Save last location on reloads #54
  • +
  • Hide buttons to move a panel up/down #49
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-3-0/index.html b/blog/v0-3-0/index.html new file mode 100644 index 0000000..e952882 --- /dev/null +++ b/blog/v0-3-0/index.html @@ -0,0 +1,101 @@ + +Release 0.3.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.3.0

Jeff Lindsay
May 17, 2023

New Node Types: Fields, Live Search, and References

+

This release we added some exciting new features: Fields, Live Search, and References. These new node types represent our first step into becoming much more than a simple outliner. Quality of live improvements include making the node menu easier to click, initial work towards a mobile view, and improving interactivity of the command palette. We also cleaned up all our UI elements to match our design system, allowing for better custom CSS support.

+

Update 7/10/2023: Live Search is now called Smart Nodes

+

Watch Demo +Demo video

+

New Feature: Fields

+

A field is a node that can store a key-value pair. This introduces structured data to your nodes, letting you create nodes as data records. You can also search for nodes by field. This initial pass supports text values, but we'll soon provide more value types.

+

To turn a node into a field:

+
    +
  1. Indent underneath the node you want to contain the field, and type the field name
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Field"
  4. +
  5. Add your field value in the value section
  6. +
+ +

Live Search allows you to create search nodes where the children are auto-updating search results. Simply type a keyword, or use the format "fieldname:value" to filter by fields. The Live Search nodes will update automatically as your workspace content changes. This is a powerful way to view your data in new configurations.

+

To create a Live Search:

+
    +
  1. Create a new node where you want your search node, and type your search value
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Search Node"
  4. +
+

Tips: Search terms are case-insensitive, and you can filter on multiple fields (uses AND, not OR) like so: "fieldname:value fieldname:value".

+

New Feature: References

+

References are nodes that refer to another node and its children inline. They're sort of like symlinks on the filesystem. This lets you have a node exist in multiple places at once. References are differentiated from normal nodes with a dashed outline around their outline bullet. Deleting a reference node does not delete the node it points to. You may notice that Live Search results are reference nodes!

+

To create a reference node:

+
    +
  1. Select the node you want to make a reference to
  2. +
  3. Command/Control + K to open the command palette, and choose "Create Reference"
  4. +
+

Bugfixes

+
    +
  • Command palette now uses normalized title #117
  • +
  • Command palette supports mouse interactivity #102
  • +
  • Components no longer fail with bundled library #119
  • +
  • Ensure Node IDs are unique #104
  • +
  • Reference sub-nodes now include fields #121
  • +
  • Search nodes no longer production workspace #118
  • +
  • Prevent indenting/outdenting a field node #116
  • +
+

Enhancements and Chores

+
    +
  • New feature: Field nodes
  • +
  • New feature: Live Search nodes
  • +
  • New feature: Reference nodes
  • +
  • Clear out non-existent node keys in workspace document under expanded #120
  • +
  • Don't show bullet for empty nodes #31
  • +
  • Allow hovering over menu area to show menu #76
  • +
  • Clean up styles on search bar, palette, quick add, menu, buttons #81, #85, #96, #108, #110
  • +
  • Show placeholder text for empty field key/value inputs #135
  • +
  • Implement initial version of mobile web app #103
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-4-0/index.html b/blog/v0-4-0/index.html new file mode 100644 index 0000000..b6a3eef --- /dev/null +++ b/blog/v0-4-0/index.html @@ -0,0 +1,79 @@ + +Release 0.4.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.4.0

Jeff Lindsay
June 19, 2023

Mobile support and CSS themes

+

In this release we focused on making Treehouse more powerful and enjoyable to use from more places. Editing text on mobile is always a tricky prospect, but we've taken our first step to being mobile-friendly with customized navigation and easier touch targets.

+

White background a bit too bright? We've added several built-in themes (including sepia and dark mode) and we've made it super easy to create your own CSS themes using our component variables.

+

Otherwise, lots of quality of life improvements as usual, especially for fields and Smart Nodes.

+

Watch Demo +Demo video

+

Enhancements and Chores

+
    +
  • Support setting a theme in the UI, and custom CSS themes #168, #122
  • +
  • Mobile improvements: new navigation and improved interactivity #114, #115
  • +
  • Allow partial phrase match for command palette search #148
  • +
  • UX improvements for search nodes #134
  • +
  • Only show new-node plus sign if a node is expanded and has no children #149
  • +
  • Add keyboard shortcuts to command palette #129
  • +
  • Prevent turning nodes with children into search nodes #161
  • +
  • Support multiple workflows for creating fields #152
  • +
  • Show visual indicator when search node is collapsed #150
  • +
  • Consolidate search logic #159
  • +
  • Consolidate overlays with dialog element #158
  • +
  • Support multiple workflows for creating fields#152
  • +
+

Bugfixes

+
    +
  • Backspace behavior on fields should be predictable #130
  • +
  • Don't collapse above node when outdenting #131
  • +
  • Backspacing from beginning of node deletes children #151
  • +
  • Prevent edit interactions on search node #105
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-5-0/index.html b/blog/v0-5-0/index.html new file mode 100644 index 0000000..e4d9cd4 --- /dev/null +++ b/blog/v0-5-0/index.html @@ -0,0 +1,73 @@ + +Release 0.5.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.5.0

Jeff Lindsay
July 6, 2023

Tags, templates, and table view

+

Treehouse now supports tags and templates! For example, you can now create a template node with children and fields, name it "book", then any node tagged with #book will automatically get those fields and children. To turn a node into a template, select "Make Template" from the Command Palette. You can also tag nodes without an equivalent template. Tagging is done by simply adding hashtags to a node's text.

+

You can search for tagged nodes in the search bar using the hashtag notation (#book), however there is a known issue preventing you from searching for nodes by tag in Smart Nodes. This will be resolved in the next release, and in main much sooner.

+

You can now take a list of nodes with fields and turn them into an easy-to-scan table. Simply go to the parent node of the rows, open the Command Palette with Command+K, and choose "View as Table". We'll be adding more features to the table view in coming releases.

+

Double-quoted search terms now allow spaces and search results are a little tighter now. Last but not least we've added cut, copy, and paste commands for nodes, making it easier to duplicate and move nodes around.

+

Watch Demo +Demo video

+

Enhancements and Chores

+
    +
  • Tags and templates #206
  • +
  • Cut/copy/paste nodes #194
  • +
  • Table view for nodes #106, #120
  • +
  • Support field search terms containing spaces #153
  • +
  • Link to documentation from within Treehouse #175
  • +
+

Bugfixes

+
    +
  • Search bug when capitalizing search term #217
  • +
  • Search is behaving too broadly when matching #197
  • +
  • Support multiple concurrent dialogs #203
  • +
  • Support editing "Calendar" node without Today making a new one #232
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-6-0/index.html b/blog/v0-6-0/index.html new file mode 100644 index 0000000..739afe5 --- /dev/null +++ b/blog/v0-6-0/index.html @@ -0,0 +1,74 @@ + +Release 0.6.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.6.0

Jeff Lindsay
July 20, 2023

New Document and Tab Views

+

In this release we're introducing two new views: Document view, and Tab view. Views are a powerful idea in Treehouse that can either be used to create embedded mini-apps or act as building blocks for you to make your own. These new views are an example of each.

+

Document view gives you a more conventional note-taking experience. In Document view each child node is rendered as a paragraph in the center of the panel. Documents support simple Markdown formatting like bold, italic, strikethrough, and ordered and unordered lists. Turn a node into a document by choosing "Make Document" from the Command Palette. Unlike other views, changing to Document view is a one-way action and nodes can't be converted back to other views.

+

Tab view renders each child node as a tab. Their children (grandchild nodes) are shown beneath when the tab is selected. These nodes will retain their view, so you can combine Tab view with others to create a more complex larger view of your data. In general, Tab view is useful for saving vertical space across several categories. Turn nodes into tabs by selecting the parent node and choose "View as Tabs" from the Command Palette. Since Tab view is read-only, you can modify tabs by switching back to List view.

+

We've also renamed Live Search Nodes to Smart Nodes, which can now be named separate from their query. This also resolves an issue where previously you couldn't use them for tag searches.

+

Watch Demo +Demo video

+

Enhancements and Chores

+
    +
  • Tabs view #126
  • +
  • Document view #246
  • +
  • Support setting a name for a Smart Node #235
  • +
  • Clean up command palette commands #242
  • +
  • Rename live search/search node to Smart Node #248
  • +
+

Bugfixes

+
    +
  • Range error when turning a tag into a search node #236
  • +
  • Don't allow outdenting a top-level node #234
  • +
  • Can't interact with an empty node in Quick Add (mobile) #204
  • +
  • Capital letters in GitHub username prevent login #245
  • +
  • Can't select a theme in Chrome #266
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/blog/v0-7-0/index.html b/blog/v0-7-0/index.html new file mode 100644 index 0000000..0d08712 --- /dev/null +++ b/blog/v0-7-0/index.html @@ -0,0 +1,75 @@ + +Release 0.7.0 - Treehouse

Branching Out

Notes from the Treehouse

Release 0.7.0

Jeff Lindsay
August 18, 2023

Descriptions, better paste, shortcut drawer

+

We're excited to be adding a handful of minor enhancements in this release. The first is node descriptions, which allow you to add small text below your node content, as part of the same node. Use it to add context or any kind of secondary information. To use, select your node, open the Command Palette with Command/Control + K, and select "Add Description".

+

We've made several improvements related to cut/copy/pasting text. Now if you paste text containing new lines, we'll convert each newline to a separate node. We've also fixed some bugs and UX flows.

+

Lastly, check out the keyboard shortcut drawer! We want it to be super easy to learn and use keyboard shortcuts and hope this helps.

+

Enhancements and Chores

+
    +
  • Node descriptions #295
  • +
  • Support searching for field keys containing spaces #277
  • +
  • Newlines should be translated as new nodes when pasting from outside sources #250
  • +
  • When multiple panels, all panels should be equal width #259
  • +
  • Keyboard shortcut reference drawer #264
  • +
  • Sublime theme - adjust pink to improve contrast #284
  • +
  • Use real italics/bold font variant for Codemirror (treehouse) #272
  • +
+

Bugs

+
    +
  • Can't paste into (+) node #275
  • +
  • Prevent multiple checkboxes #299
  • +
  • Cut node pastes when you select cut, not paste #293
  • +
  • "Back" link should not require two clicks #257
  • +
  • Cutting the node should remove it from view #274
  • +
  • TypeError when smart node returns results #269
  • +
  • TypeError related to command palette + tags #258
  • +
+
+

Discuss on GitHub

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/web/static/blog/v0.1.0/index.html b/blog/v0.1.0/index.html similarity index 100% rename from web/static/blog/v0.1.0/index.html rename to blog/v0.1.0/index.html diff --git a/web/static/blog/v0.2.0/index.html b/blog/v0.2.0/index.html similarity index 100% rename from web/static/blog/v0.2.0/index.html rename to blog/v0.2.0/index.html diff --git a/web/static/blog/v0.3.0/index.html b/blog/v0.3.0/index.html similarity index 100% rename from web/static/blog/v0.3.0/index.html rename to blog/v0.3.0/index.html diff --git a/web/static/blog/v0.4.0/index.html b/blog/v0.4.0/index.html similarity index 100% rename from web/static/blog/v0.4.0/index.html rename to blog/v0.4.0/index.html diff --git a/blog/welcome/index.html b/blog/welcome/index.html new file mode 100644 index 0000000..171a022 --- /dev/null +++ b/blog/welcome/index.html @@ -0,0 +1,81 @@ + +Welcome to Treehouse - Treehouse

Branching Out

Notes from the Treehouse

Welcome to Treehouse

Jeff Lindsay
February 23, 2023

I'm excited to announce the start of a new major project that I'll be sharing the journey of on this fancy new blog. The project is called Treehouse, which is starting as an open source note-taking frontend and will evolve into something much more.

+

Treehouse screenshot

+

We're creating a simple, hackable kernel of a note-taking tool as a web frontend that can be extended and customized by developers. Then we'll use that frontend to build a new kind of note-taking product. Today we have an early preview of our first minimum viable prototype of the frontend, and we'll be finishing up this release in the open on GitHub.

+

The open source project is a functional app frontend that you can deploy and back in various ways. For developers, this "bring your own backend" approach makes Treehouse a great starting point to build your own note-taking tool, just as we intend to do. We also have pre-made backends for those who want to use it with little-to-no programming. Either way, you'll always own your data, and now the system presenting it to you as well.

+

In this post, I'm going to talk about our minimum viable prototype and the approach we're taking with Treehouse as an open source project.

+

Out of the Box

+

While inspired by powerful tools like Notion, Tana, and Obsidian, we wanted to boil our initial goal down to the essentials while still being usable enough to use ourselves. As we continue to plan our prototype release milestone, we'd love to hear what you think is essential for you to have in a tool and platform like this, but here is what to expect as a baseline.

+

Outliner with Markdown Pages

+

At the heart of Treehouse is a graph-like system I've been developing called Manifold. This will play a big part in the long-term extensibility of Treehouse, which I'll talk more about in the future. For now, this maps very cleanly to the outliner model popularized by Workflowy, Tana, and others.

+

However, I'm also a fan of Notion-style pages and Obsidian's commitment to working with plain Markdown files. The Manifold system allows us to make certain nodes into Markdown pages. This hybrid model gets us the best of both worlds with room for even more possibilities later on.

+

Quick Add and Daily Notes

+

Being able to quickly get thoughts and information into the system has been a key aspect of every good note-taking system. This can be exposed a number of ways from browser extensions to desktop shortcuts, but what makes any "quick add" functionality quick is not having to think about where it will be organized.

+

Luckily, "daily notes" has become such a common pattern that many recent tools have managed daily notes built-in. This typical gives you a "today" note that is automatically organized into a calendar-like structure. This conveniently becomes the perfect destination for "quick add" notes.

+ +

Whether you are an obsessive organizer or are too busy to take the time, solid full-text search is basically a necessity. It's how we quickly get to our data.

+

Out of the box we have a full-text search that doesn't require a backend. However, our pluggable backend will allow you to power search in a number of ways, and also opens up the ability to search into external systems that are important to you.

+

Built-in Backends

+

"Bring your own backend" is a critical part of the design of this project, but Treehouse also needs to be usable by people who prefer not to build their own backend. Our prototype release is planned to include a handful of built-in backends to get started with.

+

Our preview demo is using a localStorage backend, which works for development and special use cases. Of course, we will have a simple local filesystem backend for desktop scenarios, similar to Obsidian.

+

However, we're most excited for the GitHub backend. This functions similarly to the local filesystem backend, but it would be versioned in a central repository on your GitHub account and be accessible on any online platform.

+

Backends will not only let you extend storage, search, and authentication, but other aspects in the future.

+

Project Overview

+

The Treehouse codebase and open source project is as much the product as its features. The entire user and developer experience is being designed around simplicity and human ergonomics.

+

The Treehouse frontend is built with web technologies intended for use across web, mobile, and desktop platforms. The project is written in TypeScript using Deno as its JavaScript toolchain.

+

The JavaScript ecosystem is a mess, but Deno provides a forward-looking, self-contained toolchain that's easy to love. We actually have a zero Node.js policy and avoid NPM modules as much as possible.

+

With minimal dependencies and an ongoing effort to keep the codebase small, it will always be easy to understand the entire Treehouse system. This will be further supported with a focus on documentation, both API docs and guides.

+

We're using a permissive open source license, allowing you to use or change our code as you see fit. Contribution and participation is welcome, but so is simply consuming downstream.

+

The project is actively being developed but we keep a preview demo running off the main branch that should always be in working order.

+

Coming Soon

+

I'm excited to give you a deeper look at the project stack in a future post. For now, keep an eye out for the next post digging into the influences for Treehouse. We'll also try to answer "why another note-taking tool", and tease some long-term ideas for the future.

+

Lastly, I want to mention the project is being developed in the open, not just with the code on GitHub, but most of my work is streamed and archived on Twitch. Feel free to come and co-work with me.

+

Thanks for reading, and a big thanks to my sponsors for supporting this kind of open source work.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/bundle.ts b/bundle.ts deleted file mode 100644 index 07e5686..0000000 --- a/bundle.ts +++ /dev/null @@ -1,18 +0,0 @@ - -import * as esbuild from "https://deno.land/x/esbuild@v0.17.2/mod.js"; - -var outfile = "web/static/lib/treehouse.min.js"; - -console.log(`Creating bundle at ${outfile} ...`); -await esbuild.build({ - entryPoints: ["lib/mod.ts"], - bundle: true, - outfile: outfile, - jsxFactory: "m", - sourcemap: true, - format: "esm", - keepNames: true, - minify: true -}); -esbuild.stop(); -console.log("Finished!"); diff --git a/web/demo/index.njk b/demo/index.html similarity index 64% rename from web/demo/index.njk rename to demo/index.html index d426229..db19a0b 100644 --- a/web/demo/index.njk +++ b/demo/index.html @@ -1,14 +1,14 @@ - - - - - - - - - + + + + + + + + + - + @@ -25,9 +25,10 @@ import { CodeJar } from "https://cdn.jsdelivr.net/npm/codejar@4.2.0/+esm"; window.CodeJar = CodeJar; - window.backend = {{ backend | safe }}; + window.backend = {"name":"github","url":"https://treehouse-demo-login.proteco.workers.dev"}; - - + + + \ No newline at end of file diff --git a/deno.json b/deno.json deleted file mode 100644 index 871d67c..0000000 --- a/deno.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "npm:preact" - }, - "tasks": { - "bundle": "deno run --unstable -A ./bundle.ts", - "lume": "echo \"import 'lume/cli.ts'\" | deno run --unstable -A -", - "build": "deno task lume", - "serve": "deno task lume -s" - }, - "imports": { - "lume/": "https://deno.land/x/lume@v1.17.5/", - "https://deno.land/std/": "https://deno.land/std@0.177.0/" - } -} \ No newline at end of file diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 85eb088..0000000 --- a/deno.lock +++ /dev/null @@ -1,561 +0,0 @@ -{ - "version": "3", - "packages": { - "specifiers": { - "npm:date-fns@2.30.0": "npm:date-fns@2.30.0", - "npm:highlight.js@11.8.0": "npm:highlight.js@11.8.0", - "npm:markdown-it-attrs": "npm:markdown-it-attrs@4.1.6_markdown-it@13.0.1", - "npm:markdown-it-attrs@4.1.6": "npm:markdown-it-attrs@4.1.6_markdown-it@13.0.1", - "npm:markdown-it-deflist@2.1.0": "npm:markdown-it-deflist@2.1.0", - "npm:markdown-it@13.0.1": "npm:markdown-it@13.0.1", - "npm:nunjucks@3.2.4": "npm:nunjucks@3.2.4", - "npm:preact": "npm:preact@10.15.1", - "npm:preact-render-to-string@6.0.3": "npm:preact-render-to-string@6.0.3_preact@10.15.1", - "npm:preact@10.15.1": "npm:preact@10.15.1", - "npm:react-dom@18.2.0": "npm:react-dom@18.2.0_react@18.2.0", - "npm:react@18.2.0": "npm:react@18.2.0" - }, - "npm": { - "@babel/runtime@7.22.5": { - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dependencies": { - "regenerator-runtime": "regenerator-runtime@0.13.11" - } - }, - "a-sync-waterfall@1.0.1": { - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "dependencies": {} - }, - "argparse@2.0.1": { - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dependencies": {} - }, - "asap@2.0.6": { - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dependencies": {} - }, - "commander@5.1.0": { - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dependencies": {} - }, - "date-fns@2.30.0": { - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "@babel/runtime@7.22.5" - } - }, - "entities@3.0.1": { - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dependencies": {} - }, - "highlight.js@11.8.0": { - "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==", - "dependencies": {} - }, - "js-tokens@4.0.0": { - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dependencies": {} - }, - "linkify-it@4.0.1": { - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dependencies": { - "uc.micro": "uc.micro@1.0.6" - } - }, - "loose-envify@1.4.0": { - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "js-tokens@4.0.0" - } - }, - "markdown-it-attrs@4.1.6_markdown-it@13.0.1": { - "integrity": "sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA==", - "dependencies": { - "markdown-it": "markdown-it@13.0.1" - } - }, - "markdown-it-deflist@2.1.0": { - "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", - "dependencies": {} - }, - "markdown-it@13.0.1": { - "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", - "dependencies": { - "argparse": "argparse@2.0.1", - "entities": "entities@3.0.1", - "linkify-it": "linkify-it@4.0.1", - "mdurl": "mdurl@1.0.1", - "uc.micro": "uc.micro@1.0.6" - } - }, - "mdurl@1.0.1": { - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dependencies": {} - }, - "nunjucks@3.2.4": { - "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "dependencies": { - "a-sync-waterfall": "a-sync-waterfall@1.0.1", - "asap": "asap@2.0.6", - "commander": "commander@5.1.0" - } - }, - "preact-render-to-string@6.0.3_preact@10.15.1": { - "integrity": "sha512-UUP+EtmLw5ns0fT9C7+CTdLawm1wLmlrZ6WKzJ4Jwhb4EBu4vy5ufIZKlrfvWNnPl1JFoJzZwzfKs97H4N0Vug==", - "dependencies": { - "preact": "preact@10.15.1", - "pretty-format": "pretty-format@3.8.0" - } - }, - "preact@10.15.1": { - "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==", - "dependencies": {} - }, - "pretty-format@3.8.0": { - "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", - "dependencies": {} - }, - "react-dom@18.2.0_react@18.2.0": { - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0", - "react": "react@18.2.0", - "scheduler": "scheduler@0.23.0" - } - }, - "react@18.2.0": { - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0" - } - }, - "regenerator-runtime@0.13.11": { - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dependencies": {} - }, - "scheduler@0.23.0": { - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "loose-envify@1.4.0" - } - }, - "uc.micro@1.0.6": { - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dependencies": {} - } - } - }, - "remote": { - "https://deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", - "https://deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", - "https://deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", - "https://deno.land/std@0.170.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", - "https://deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", - "https://deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", - "https://deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", - "https://deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", - "https://deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", - "https://deno.land/std@0.190.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", - "https://deno.land/std@0.190.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", - "https://deno.land/std@0.190.0/async/abortable.ts": "fd682fa46f3b7b16b4606a5ab52a7ce309434b76f820d3221bdfb862719a15d7", - "https://deno.land/std@0.190.0/async/deadline.ts": "58f72a3cc0fcb731b2cc055ba046f4b5be3349ff6bf98f2e793c3b969354aab2", - "https://deno.land/std@0.190.0/async/debounce.ts": "adab11d04ca38d699444ac8a9d9856b4155e8dda2afd07ce78276c01ea5a4332", - "https://deno.land/std@0.190.0/async/deferred.ts": "42790112f36a75a57db4a96d33974a936deb7b04d25c6084a9fa8a49f135def8", - "https://deno.land/std@0.190.0/async/delay.ts": "73aa04cec034c84fc748c7be49bb15cac3dd43a57174bfdb7a4aec22c248f0dd", - "https://deno.land/std@0.190.0/async/mod.ts": "f04344fa21738e5ad6bea37a6bfffd57c617c2d372bb9f9dcfd118a1b622e576", - "https://deno.land/std@0.190.0/async/mux_async_iterator.ts": "70c7f2ee4e9466161350473ad61cac0b9f115cff4c552eaa7ef9d50c4cbb4cc9", - "https://deno.land/std@0.190.0/async/pool.ts": "f1b8d3df4d7fd3c73f8cbc91cc2e8b8e950910f1eab94230b443944d7584c657", - "https://deno.land/std@0.190.0/async/retry.ts": "c9248325ec08cc2cceb7618472e77589a51a25cee460e07b6096be34966c2ead", - "https://deno.land/std@0.190.0/async/tee.ts": "47e42d35f622650b02234d43803d0383a89eb4387e1b83b5a40106d18ae36757", - "https://deno.land/std@0.190.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", - "https://deno.land/std@0.190.0/collections/_utils.ts": "5114abc026ddef71207a79609b984614e66a63a4bda17d819d56b0e72c51527e", - "https://deno.land/std@0.190.0/collections/deep_merge.ts": "5a8ed29030f4471a5272785c57c3455fa79697b9a8f306013a8feae12bafc99a", - "https://deno.land/std@0.190.0/crypto/_fnv/fnv32.ts": "e4649dfdefc5c987ed53c3c25db62db771a06d9d1b9c36d2b5cf0853b8e82153", - "https://deno.land/std@0.190.0/crypto/_fnv/fnv64.ts": "bfa0e4702061fdb490a14e6bf5f9168a22fb022b307c5723499469bfefca555e", - "https://deno.land/std@0.190.0/crypto/_fnv/mod.ts": "f956a95f58910f223e420340b7404702ecd429603acd4491fa77af84f746040c", - "https://deno.land/std@0.190.0/crypto/_fnv/util.ts": "accba12bfd80a352e32a872f87df2a195e75561f1b1304a4cb4f5a4648d288f9", - "https://deno.land/std@0.190.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "bdd70a6183c6bdabc086ec2a5f828c86711b4201f1ba7954fc78385a664e8fae", - "https://deno.land/std@0.190.0/crypto/_wasm/mod.ts": "e2df88236fc061eac7a89e8cb0b97843f5280b08b2a990e473b7397a3e566003", - "https://deno.land/std@0.190.0/crypto/crypto.ts": "9895c267ed0107ef613244f6a88cef40a21da159d52b5d90aceca46fd2684bb1", - "https://deno.land/std@0.190.0/crypto/keystack.ts": "877ab0f19eb7d37ad6495190d3c3e39f58e9c52e0b6a966f82fd6df67ca55f90", - "https://deno.land/std@0.190.0/crypto/mod.ts": "ae384519e85eca9aeff4e7111ed153df8f3dbda7b35b70850ed4b3e9c8cec4d5", - "https://deno.land/std@0.190.0/crypto/timing_safe_equal.ts": "0fae34ee02264f309ae0b6e54e9746a7aba3996e5454903ed106967a7a9ef665", - "https://deno.land/std@0.190.0/crypto/to_hash_string.ts": "6927c768f3e373a1be4a31555a45ccecf7bd413105455cc334ad3f908cfa986f", - "https://deno.land/std@0.190.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d", - "https://deno.land/std@0.190.0/encoding/base64url.ts": "2ed4ba122b20fedf226c5d337cf22ee2024fa73a8f85d915d442af7e9ce1fae1", - "https://deno.land/std@0.190.0/encoding/hex.ts": "b4b1a7cb678745b0bf181ed8cf2498c7be00d121a7de244b752fbf9c7d9c48cd", - "https://deno.land/std@0.190.0/flags/mod.ts": "17f444ddbee43c5487568de0c6a076c7729cfe90d96d2ffcd2b8f8adadafb6e8", - "https://deno.land/std@0.190.0/fmt/bytes.ts": "f29cf69e0791d375f9f5d94ae1f0641e5a03b975f32ddf86d70f70fdf37e7b6a", - "https://deno.land/std@0.190.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", - "https://deno.land/std@0.190.0/front_matter/mod.ts": "f817a339f595482cd2c98d47e8009fbc82a965247495dc4114e680ed75bcb782", - "https://deno.land/std@0.190.0/front_matter/yaml.ts": "a681fbff79b9641379b1ceda27308b6e83ca5d26a1bcfe669cd1289fc3692ac7", - "https://deno.land/std@0.190.0/fs/_util.ts": "579038bebc3bd35c43a6a7766f7d91fbacdf44bc03468e9d3134297bb99ed4f9", - "https://deno.land/std@0.190.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8", - "https://deno.land/std@0.190.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", - "https://deno.land/std@0.190.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.190.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9", - "https://deno.land/std@0.190.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4", - "https://deno.land/std@0.190.0/fs/ensure_symlink.ts": "5006ab2f458159c56d689b53b1e48d57e05eeb1eaf64e677f7f76a30bc4fdba1", - "https://deno.land/std@0.190.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842", - "https://deno.land/std@0.190.0/fs/exists.ts": "29c26bca8584a22876be7cb8844f1b6c8fc35e9af514576b78f5c6884d7ed02d", - "https://deno.land/std@0.190.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", - "https://deno.land/std@0.190.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", - "https://deno.land/std@0.190.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", - "https://deno.land/std@0.190.0/fs/walk.ts": "920be35a7376db6c0b5b1caf1486fb962925e38c9825f90367f8f26b5e5d0897", - "https://deno.land/std@0.190.0/http/etag.ts": "6ad8abbbb1045aabf2307959a2c5565054a8bf01c9824ddee836b1ff22706a58", - "https://deno.land/std@0.190.0/http/file_server.ts": "fd99a7a0e1ac6f80873b1f812aa30ac64f96663971d061ced3a4d10b65048efe", - "https://deno.land/std@0.190.0/http/http_status.ts": "8a7bcfe3ac025199ad804075385e57f63d055b2aed539d943ccc277616d6f932", - "https://deno.land/std@0.190.0/http/server.ts": "1b23463b5b36e4eebc495417f6af47a6f7d52e3294827a1226d2a1aab23d9d20", - "https://deno.land/std@0.190.0/http/util.ts": "57bf151c8d4388bbd56ed01ece1720934252adc6f859f782392927f0f78964c0", - "https://deno.land/std@0.190.0/io/buffer.ts": "17f4410eaaa60a8a85733e8891349a619eadfbbe42e2f319283ce2b8f29723ab", - "https://deno.land/std@0.190.0/json/common.ts": "ecd5e87d45b5f0df33238ed8b1746e1444da7f5c86ae53d0f0b04280f41a25bb", - "https://deno.land/std@0.190.0/jsonc/mod.ts": "b88dce28eb3645667caa856538ae2fe87af51410822544a0b45a4177ef3bd7dd", - "https://deno.land/std@0.190.0/jsonc/parse.ts": "2910e33bc7c3b243e3b6f3a39ce4d6ca84337b277a8df6f2ad2d9e4adbcddc08", - "https://deno.land/std@0.190.0/media_types/_db.ts": "7606d83e31f23ce1a7968cbaee852810c2cf477903a095696cdc62eaab7ce570", - "https://deno.land/std@0.190.0/media_types/_util.ts": "916efbd30b6148a716f110e67a4db29d6949bf4048997b754415dd7e42c52378", - "https://deno.land/std@0.190.0/media_types/content_type.ts": "ad98a5aa2d95f5965b2796072284258710a25e520952376ed432b0937ce743bc", - "https://deno.land/std@0.190.0/media_types/format_media_type.ts": "f5e1073c05526a6f5a516ac5c5587a1abd043bf1039c71cde1166aa4328c8baf", - "https://deno.land/std@0.190.0/media_types/get_charset.ts": "18b88274796fda5d353806bf409eb1d2ddb3f004eb4bd311662c4cdd8ac173db", - "https://deno.land/std@0.190.0/media_types/parse_media_type.ts": "835c4112e1357e95b4f10d7cdea5ae1801967e444f48673ff8f1cb4d32af9920", - "https://deno.land/std@0.190.0/media_types/type_by_extension.ts": "daa801eb0f11cdf199445d0f1b656cf116d47dcf9e5b85cc1e6b4469f5ee0432", - "https://deno.land/std@0.190.0/media_types/vendor/mime-db.v1.52.0.ts": "6925bbcae81ca37241e3f55908d0505724358cda3384eaea707773b2c7e99586", - "https://deno.land/std@0.190.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.190.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.190.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", - "https://deno.land/std@0.190.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", - "https://deno.land/std@0.190.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", - "https://deno.land/std@0.190.0/path/mod.ts": "ee161baec5ded6510ee1d1fb6a75a0f5e4b41f3f3301c92c716ecbdf7dae910d", - "https://deno.land/std@0.190.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", - "https://deno.land/std@0.190.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", - "https://deno.land/std@0.190.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", - "https://deno.land/std@0.190.0/streams/byte_slice_stream.ts": "225d57263a34325d7c96cb3dafeb478eec0e6fd05cd0458d678752eadd132bb4", - "https://deno.land/std@0.190.0/types.d.ts": "dbaeb2c4d7c526db9828fc8df89d8aecf53b9ced72e0c4568f97ddd8cda616a4", - "https://deno.land/std@0.190.0/version.ts": "c3fdbeb886289441c29bb900612bb35815659717391f631be2c14a34fca40c52", - "https://deno.land/std@0.190.0/yaml/_dumper/dumper.ts": "a2c937a53a2b0473125a31a330334cc3f30e98fd82f8143bc225583d1260890b", - "https://deno.land/std@0.190.0/yaml/_dumper/dumper_state.ts": "f0d0673ceea288334061ca34b63954c2bb5feb5bf6de5e4cfe9a942cdf6e5efe", - "https://deno.land/std@0.190.0/yaml/_error.ts": "b59e2c76ce5a47b1b9fa0ff9f96c1dd92ea1e1b17ce4347ece5944a95c3c1a84", - "https://deno.land/std@0.190.0/yaml/_loader/loader.ts": "04cf748a736a9b3a29bd3d4b3c77d81489f82cfe8391627fd6ba8327e1e8cec2", - "https://deno.land/std@0.190.0/yaml/_loader/loader_state.ts": "0841870b467169269d7c2dfa75cd288c319bc06f65edd9e42c29e5fced91c7a4", - "https://deno.land/std@0.190.0/yaml/_mark.ts": "dcd8585dee585e024475e9f3fe27d29740670fb64ebb970388094cad0fc11d5d", - "https://deno.land/std@0.190.0/yaml/_state.ts": "ef03d55ec235d48dcfbecc0ab3ade90bfae69a61094846e08003421c2cf5cfc6", - "https://deno.land/std@0.190.0/yaml/_type/binary.ts": "d34d8c8d8ed521e270cfede3401c425b971af4f6c69da1e2cb32b172d42c7da7", - "https://deno.land/std@0.190.0/yaml/_type/bool.ts": "5bfa75da84343d45347b521ba4e5aeace9fe6f53447405290d53315a3fc20e66", - "https://deno.land/std@0.190.0/yaml/_type/float.ts": "056bd3cb9c5586238b20517511014fb24b0e36f98f9f6073e12da308b6b9808a", - "https://deno.land/std@0.190.0/yaml/_type/function.ts": "ff574fe84a750695302864e1c31b93f12d14ada4bde79a5f93197fc33ad17471", - "https://deno.land/std@0.190.0/yaml/_type/int.ts": "563ad074f0fa7aecf6b6c3d84135bcc95a8269dcc15de878de20ce868fd773fa", - "https://deno.land/std@0.190.0/yaml/_type/map.ts": "7b105e4ab03a361c61e7e335a0baf4d40f06460b13920e5af3fb2783a1464000", - "https://deno.land/std@0.190.0/yaml/_type/merge.ts": "8192bf3e4d637f32567917f48bb276043da9cf729cf594e5ec191f7cd229337e", - "https://deno.land/std@0.190.0/yaml/_type/mod.ts": "060e2b3d38725094b77ea3a3f05fc7e671fced8e67ca18e525be98c4aa8f4bbb", - "https://deno.land/std@0.190.0/yaml/_type/nil.ts": "606e8f0c44d73117c81abec822f89ef81e40f712258c74f186baa1af659b8887", - "https://deno.land/std@0.190.0/yaml/_type/omap.ts": "cfe59a294726f5cea705c39a61fd2b08199cf48f4ccd6b040cb550ec0f38d0a1", - "https://deno.land/std@0.190.0/yaml/_type/pairs.ts": "0032fdfe57558d21696a4f8cf5b5cfd1f698743177080affc18629685c905666", - "https://deno.land/std@0.190.0/yaml/_type/regexp.ts": "1ce118de15b2da43b4bd8e4395f42d448b731acf3bdaf7c888f40789f9a95f8b", - "https://deno.land/std@0.190.0/yaml/_type/seq.ts": "95333abeec8a7e4d967b8c8328b269e342a4bbdd2585395549b9c4f58c8533a2", - "https://deno.land/std@0.190.0/yaml/_type/set.ts": "f28ba44e632ef2a6eb580486fd47a460445eeddbdf1dbc739c3e62486f566092", - "https://deno.land/std@0.190.0/yaml/_type/str.ts": "a67a3c6e429d95041399e964015511779b1130ea5889fa257c48457bd3446e31", - "https://deno.land/std@0.190.0/yaml/_type/timestamp.ts": "706ea80a76a73e48efaeb400ace087da1f927647b53ad6f754f4e06d51af087f", - "https://deno.land/std@0.190.0/yaml/_type/undefined.ts": "94a316ca450597ccbc6750cbd79097ad0d5f3a019797eed3c841a040c29540ba", - "https://deno.land/std@0.190.0/yaml/_utils.ts": "26b311f0d42a7ce025060bd6320a68b50e52fd24a839581eb31734cd48e20393", - "https://deno.land/std@0.190.0/yaml/mod.ts": "28ecda6652f3e7a7735ee29c247bfbd32a2e2fc5724068e9fd173ec4e59f66f7", - "https://deno.land/std@0.190.0/yaml/parse.ts": "1fbbda572bf3fff578b6482c0d8b85097a38de3176bf3ab2ca70c25fb0c960ef", - "https://deno.land/std@0.190.0/yaml/schema.ts": "96908b78dc50c340074b93fc1598d5e7e2fe59103f89ff81e5a49b2dedf77a67", - "https://deno.land/std@0.190.0/yaml/schema/core.ts": "fa406f18ceedc87a50e28bb90ec7a4c09eebb337f94ef17468349794fa828639", - "https://deno.land/std@0.190.0/yaml/schema/default.ts": "0047e80ae8a4a93293bc4c557ae8a546aabd46bb7165b9d9b940d57b4d88bde9", - "https://deno.land/std@0.190.0/yaml/schema/extended.ts": "0784416bf062d20a1626b53c03380e265b3e39b9409afb9f4cb7d659fd71e60d", - "https://deno.land/std@0.190.0/yaml/schema/failsafe.ts": "d219ab5febc43f770917d8ec37735a4b1ad671149846cbdcade767832b42b92b", - "https://deno.land/std@0.190.0/yaml/schema/json.ts": "5f41dd7c2f1ad545ef6238633ce9ee3d444dfc5a18101e1768bd5504bf90e5e5", - "https://deno.land/std@0.190.0/yaml/schema/mod.ts": "4472e827bab5025e92bc2eb2eeefa70ecbefc64b2799b765c69af84822efef32", - "https://deno.land/std@0.190.0/yaml/stringify.ts": "fffc09c65c68d3d63f8159e8cbaa3f489bc20a8e55b4fbb61a8c2e9f914d1d02", - "https://deno.land/std@0.190.0/yaml/type.ts": "1aabb8e0a3f4229ce0a3526256f68826d9bdf65a36c8a3890ead8011fcba7670", - "https://deno.land/x/cliffy@v0.25.7/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", - "https://deno.land/x/cliffy@v0.25.7/ansi/ansi.ts": "7f43d07d31dd7c24b721bb434c39cbb5132029fa4be3dd8938873065f65e5810", - "https://deno.land/x/cliffy@v0.25.7/ansi/ansi_escapes.ts": "885f61f343223f27b8ec69cc138a54bea30542924eacd0f290cd84edcf691387", - "https://deno.land/x/cliffy@v0.25.7/ansi/chain.ts": "31fb9fcbf72fed9f3eb9b9487270d2042ccd46a612d07dd5271b1a80ae2140a0", - "https://deno.land/x/cliffy@v0.25.7/ansi/colors.ts": "5f71993af5bd1aa0a795b15f41692d556d7c89584a601fed75997df844b832c9", - "https://deno.land/x/cliffy@v0.25.7/ansi/cursor_position.ts": "d537491e31d9c254b208277448eff92ff7f55978c4928dea363df92c0df0813f", - "https://deno.land/x/cliffy@v0.25.7/ansi/deps.ts": "0f35cb7e91868ce81561f6a77426ea8bc55dc15e13f84c7352f211023af79053", - "https://deno.land/x/cliffy@v0.25.7/ansi/mod.ts": "bb4e6588e6704949766205709463c8c33b30fec66c0b1846bc84a3db04a4e075", - "https://deno.land/x/cliffy@v0.25.7/ansi/tty.ts": "8fb064c17ead6cdf00c2d3bc87a9fd17b1167f2daa575c42b516f38bdb604673", - "https://deno.land/x/cliffy@v0.25.7/command/_errors.ts": "a9bd23dc816b32ec96c9b8f3057218241778d8c40333b43341138191450965e5", - "https://deno.land/x/cliffy@v0.25.7/command/_utils.ts": "9ab3d69fabab6c335b881b8a5229cbd5db0c68f630a1c307aff988b6396d9baf", - "https://deno.land/x/cliffy@v0.25.7/command/command.ts": "a2b83c612acd65c69116f70dec872f6da383699b83874b70fcf38cddf790443f", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_bash_completions_generator.ts": "43b4abb543d4dc60233620d51e69d82d3b7c44e274e723681e0dce2a124f69f9", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_fish_completions_generator.ts": "d0289985f5cf0bd288c05273bfa286b24c27feb40822eb7fd9d7fee64e6580e8", - "https://deno.land/x/cliffy@v0.25.7/command/completions/_zsh_completions_generator.ts": "14461eb274954fea4953ee75938821f721da7da607dc49bcc7db1e3f33a207bd", - "https://deno.land/x/cliffy@v0.25.7/command/completions/bash.ts": "053aa2006ec327ccecacb00ba28e5eb836300e5c1bec1b3cfaee9ddcf8189756", - "https://deno.land/x/cliffy@v0.25.7/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v0.25.7/command/completions/fish.ts": "9938beaa6458c6cf9e2eeda46a09e8cd362d4f8c6c9efe87d3cd8ca7477402a5", - "https://deno.land/x/cliffy@v0.25.7/command/completions/mod.ts": "aeef7ec8e319bb157c39a4bab8030c9fe8fa327b4c1e94c9c1025077b45b40c0", - "https://deno.land/x/cliffy@v0.25.7/command/completions/zsh.ts": "8b04ab244a0b582f7927d405e17b38602428eeb347a9968a657e7ea9f40e721a", - "https://deno.land/x/cliffy@v0.25.7/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v0.25.7/command/deps.ts": "275b964ce173770bae65f6b8ebe9d2fd557dc10292cdd1ed3db1735f0d77fa1d", - "https://deno.land/x/cliffy@v0.25.7/command/help/_help_generator.ts": "f7c349cb2ddb737e70dc1f89bcb1943ca9017a53506be0d4138e0aadb9970a49", - "https://deno.land/x/cliffy@v0.25.7/command/help/mod.ts": "09d74d3eb42d21285407cda688074c29595d9c927b69aedf9d05ff3f215820d3", - "https://deno.land/x/cliffy@v0.25.7/command/mod.ts": "d0a32df6b14028e43bb2d41fa87d24bc00f9662a44e5a177b3db02f93e473209", - "https://deno.land/x/cliffy@v0.25.7/command/type.ts": "24e88e3085e1574662b856ccce70d589959648817135d4469fab67b9cce1b364", - "https://deno.land/x/cliffy@v0.25.7/command/types.ts": "ae02eec0ed7a769f7dba2dd5d3a931a61724b3021271b1b565cf189d9adfd4a0", - "https://deno.land/x/cliffy@v0.25.7/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v0.25.7/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v0.25.7/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v0.25.7/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v0.25.7/command/types/enum.ts": "2178345972adf7129a47e5f02856ca3e6852a91442a1c78307dffb8a6a3c6c9f", - "https://deno.land/x/cliffy@v0.25.7/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v0.25.7/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v0.25.7/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v0.25.7/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts": "17e2df3b620905583256684415e6c4a31e8de5c59066eb6d6c9c133919292dc4", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider.ts": "d6fb846043232cbd23c57d257100c7fc92274984d75a5fead0f3e4266dc76ab8", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v0.25.7/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", - "https://deno.land/x/cliffy@v0.25.7/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v0.25.7/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", - "https://deno.land/x/cliffy@v0.25.7/flags/_validate_flags.ts": "16eb5837986c6f6f7620817820161a78d66ce92d690e3697068726bbef067452", - "https://deno.land/x/cliffy@v0.25.7/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v0.25.7/flags/flags.ts": "68a9dfcacc4983a84c07ba19b66e5e9fccd04389fad215210c60fb414cc62576", - "https://deno.land/x/cliffy@v0.25.7/flags/mod.ts": "b21c2c135cd2437cc16245c5f168a626091631d6d4907ad10db61c96c93bdb25", - "https://deno.land/x/cliffy@v0.25.7/flags/types.ts": "7452ea5296758fb7af89930349ce40d8eb9a43b24b3f5759283e1cb5113075fd", - "https://deno.land/x/cliffy@v0.25.7/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v0.25.7/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v0.25.7/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v0.25.7/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v0.25.7/keycode/key_code.ts": "c4ab0ffd102c2534962b765ded6d8d254631821bf568143d9352c1cdcf7a24be", - "https://deno.land/x/cliffy@v0.25.7/keycode/key_codes.ts": "917f0a2da0dbace08cf29bcfdaaa2257da9fe7e705fff8867d86ed69dfb08cfe", - "https://deno.land/x/cliffy@v0.25.7/keycode/mod.ts": "292d2f295316c6e0da6955042a7b31ab2968ff09f2300541d00f05ed6c2aa2d4", - "https://deno.land/x/cliffy@v0.25.7/mod.ts": "e3515ccf6bd4e4ac89322034e07e2332ed71901e4467ee5bc9d72851893e167b", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_input.ts": "737cff2de02c8ce35250f5dd79c67b5fc176423191a2abd1f471a90dd725659e", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_list.ts": "79b301bf09eb19f0d070d897f613f78d4e9f93100d7e9a26349ef0bfaa7408d2", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_prompt.ts": "8630ce89a66d83e695922df41721cada52900b515385d86def597dea35971bb2", - "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_suggestions.ts": "2a8b619f91e8f9a270811eff557f10f1343a444a527b5fc22c94de832939920c", - "https://deno.land/x/cliffy@v0.25.7/prompt/_utils.ts": "676cca30762656ed1a9bcb21a7254244278a23ffc591750e98a501644b6d2df3", - "https://deno.land/x/cliffy@v0.25.7/prompt/checkbox.ts": "e5a5a9adbb86835dffa2afbd23c6f7a8fe25a9d166485388ef25aba5dc3fbf9e", - "https://deno.land/x/cliffy@v0.25.7/prompt/confirm.ts": "94c8e55de3bbcd53732804420935c432eab29945497d1c47c357d236a89cb5f6", - "https://deno.land/x/cliffy@v0.25.7/prompt/deps.ts": "4c38ab18e55a792c9a136c1c29b2b6e21ea4820c45de7ef4cf517ce94012c57d", - "https://deno.land/x/cliffy@v0.25.7/prompt/figures.ts": "26af0fbfe21497220e4b887bb550fab997498cde14703b98e78faf370fbb4b94", - "https://deno.land/x/cliffy@v0.25.7/prompt/input.ts": "ee45532e0a30c2463e436e08ae291d79d1c2c40872e17364c96d2b97c279bf4d", - "https://deno.land/x/cliffy@v0.25.7/prompt/list.ts": "6780427ff2a932a48c9b882d173c64802081d6cdce9ff618d66ba6504b6abc50", - "https://deno.land/x/cliffy@v0.25.7/prompt/mod.ts": "195aed14d10d279914eaa28c696dec404d576ca424c097a5bc2b4a7a13b66c89", - "https://deno.land/x/cliffy@v0.25.7/prompt/number.ts": "015305a76b50138234dde4fd50eb886c6c7c0baa1b314caf811484644acdc2cf", - "https://deno.land/x/cliffy@v0.25.7/prompt/prompt.ts": "0e7f6a1d43475ee33fb25f7d50749b2f07fc0bcddd9579f3f9af12d05b4a4412", - "https://deno.land/x/cliffy@v0.25.7/prompt/secret.ts": "58745f5231fb2c44294c4acf2511f8c5bfddfa1e12f259580ff90dedea2703d6", - "https://deno.land/x/cliffy@v0.25.7/prompt/select.ts": "1e982eae85718e4e15a3ee10a5ae2233e532d7977d55888f3a309e8e3982b784", - "https://deno.land/x/cliffy@v0.25.7/prompt/toggle.ts": "842c3754a40732f2e80bcd4670098713e402e64bd930e6cab2b787f7ad4d931a", - "https://deno.land/x/cliffy@v0.25.7/table/border.ts": "2514abae4e4f51eda60a5f8c927ba24efd464a590027e900926b38f68e01253c", - "https://deno.land/x/cliffy@v0.25.7/table/cell.ts": "1d787d8006ac8302020d18ec39f8d7f1113612c20801b973e3839de9c3f8b7b3", - "https://deno.land/x/cliffy@v0.25.7/table/deps.ts": "5b05fa56c1a5e2af34f2103fd199e5f87f0507549963019563eae519271819d2", - "https://deno.land/x/cliffy@v0.25.7/table/layout.ts": "46bf10ae5430cf4fbb92f23d588230e9c6336edbdb154e5c9581290562b169f4", - "https://deno.land/x/cliffy@v0.25.7/table/mod.ts": "e74f69f38810ee6139a71132783765feb94436a6619c07474ada45b465189834", - "https://deno.land/x/cliffy@v0.25.7/table/row.ts": "5f519ba7488d2ef76cbbf50527f10f7957bfd668ce5b9169abbc44ec88302645", - "https://deno.land/x/cliffy@v0.25.7/table/table.ts": "ec204c9d08bb3ff1939c5ac7412a4c9ed7d00925d4fc92aff9bfe07bd269258d", - "https://deno.land/x/cliffy@v0.25.7/table/utils.ts": "187bb7dcbcfb16199a5d906113f584740901dfca1007400cba0df7dcd341bc29", - "https://deno.land/x/deno_dom@v0.1.38/build/deno-wasm/deno-wasm.js": "98b1ad24a1c13284557917659402202e5c5258ab1431b3f3a82434ad36ffa05a", - "https://deno.land/x/deno_dom@v0.1.38/deno-dom-wasm.ts": "bfd999a493a6974e9fca4d331bee03bfb68cfc600c662cd0b48b21d67a2a8ba0", - "https://deno.land/x/deno_dom@v0.1.38/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7", - "https://deno.land/x/deno_dom@v0.1.38/src/constructor-lock.ts": "59714df7e0571ec7bd338903b1f396202771a6d4d7f55a452936bd0de9deb186", - "https://deno.land/x/deno_dom@v0.1.38/src/deserialize.ts": "f4d34514ca00473ca428b69ad437ba345925744b5d791cb9552e2d7a0e7b0439", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/document-fragment.ts": "a40c6e18dd0efcf749a31552c1c9a6f7fa614452245e86ee38fc92ba0235e5ae", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/document.ts": "b8f4e4ccabaaa063d6562a0f2f8dea9c0419515d63d8bd79bfde95f7cd64bd93", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/dom-parser.ts": "609097b426f8c2358f3e5d2bca55ed026cf26cdf86562e94130dfdb0f2537f92", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/element.ts": "77c454e228dfeb5c570da5aa61d91850400116bfa0f5a85505acdd3c667171a4", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/elements/html-template-element.ts": "127bb291bb08afeb7e9a66294a5aa6ff2780f4eb4601fa6f7869fe8b70a81472", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/html-collection.ts": "ae90197f5270c32074926ad6cf30ee07d274d44596c7e413c354880cebce8565", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/node-list.ts": "4c6e4b4585301d4147addaccd90cb5f5a80e8d6290a1ba7058c5e3dfea16e15d", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/node.ts": "3069e6fc93ac4111a136ed68199d76673339842b9751610ba06f111ba7dc10a7", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/custom-api.ts": "852696bd58e534bc41bd3be9e2250b60b67cd95fd28ed16b1deff1d548531a71", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/nwsapi-types.ts": "c43b36c36acc5d32caabaa54fda8c9d239b2b0fcbce9a28efb93c84aa1021698", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/nwsapi.js": "985d7d8fc1eabbb88946b47a1c44c1b2d4aa79ff23c21424219f1528fa27a2ff", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/selectors.ts": "83eab57be2290fb48e3130533448c93c6c61239f2a2f3b85f1917f80ca0fdc75", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/sizzle-types.ts": "78149e2502409989ce861ed636b813b059e16bc267bb543e7c2b26ef43e4798b", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/selectors/sizzle.js": "c3aed60c1045a106d8e546ac2f85cc82e65f62d9af2f8f515210b9212286682a", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436", - "https://deno.land/x/deno_dom@v0.1.38/src/dom/utils.ts": "55f3e9dc71d6c4a54605888d3f99d26fb0cf9973924709f159252a6933ceeabe", - "https://deno.land/x/deno_dom@v0.1.38/src/parser.ts": "b65eb7e673fa7ca611de871de109655f0aa9fa35ddc1de73df1a5fc2baafc332", - "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", - "https://deno.land/x/esbuild@v0.17.2/mod.d.ts": "dc279a3a46f084484453e617c0cabcd5b8bd1920c0e562e4ea02dfc828c8f968", - "https://deno.land/x/esbuild@v0.17.2/mod.js": "96534dfc1d4c388af077246d85243a6a87b7fd6420d6f9498d135c109ee1a397", - "https://deno.land/x/imagemagick_deno@0.0.24/mod.ts": "85b2099e9ff5ea6e0ada441ce87c43bbd2555359248ae67a92acb0281d5908d5", - "https://deno.land/x/imagemagick_deno@0.0.24/src/alpha-option.ts": "749a9f3309e491ec09a1d6bc50ce95d9733887d9f57c6863c4ff1c7e9610227b", - "https://deno.land/x/imagemagick_deno@0.0.24/src/auto-threshold-method.ts": "bb08a00046137e441930e56190b6db10c5fe657cb0a6142cd565a40b1c4250a2", - "https://deno.land/x/imagemagick_deno@0.0.24/src/byte-array.ts": "bb00cd720dcea06b0d41a500a02e6ad6ace6d4e6fcab06dc943b85bfe0773493", - "https://deno.land/x/imagemagick_deno@0.0.24/src/channel-statistics.ts": "503871a48800436cbd02baee45f5e55aa7a0d95c3cd36e1111cf36dda946f43c", - "https://deno.land/x/imagemagick_deno@0.0.24/src/channels.ts": "03e46f10df374d002cc39d10a6bc93c68deb4e1158bef1107234e10a192b05c7", - "https://deno.land/x/imagemagick_deno@0.0.24/src/chromaticity-info.ts": "192bbfff68cafe77ec5b6439e67f00a96d292e1bd4f482e29f2a32e519121fb1", - "https://deno.land/x/imagemagick_deno@0.0.24/src/class-type.ts": "11970b07ecdd97798ea9ff45b080e77f6711595cef73c678e0393e21c765de05", - "https://deno.land/x/imagemagick_deno@0.0.24/src/color-space.ts": "3d9a60f3a8bfefea8d9525572d7bd6214530c69688e8799dceb492b7797d1d0a", - "https://deno.land/x/imagemagick_deno@0.0.24/src/color-type.ts": "d6a588272044ad171bfd98ea13fb510fce19b8afc4cafdbaadc2a562fbcc380e", - "https://deno.land/x/imagemagick_deno@0.0.24/src/composite-operator.ts": "f4b5046415c5965d53b17a9e441a42d87e8477b7c158704abd417d6ac10f3ea0", - "https://deno.land/x/imagemagick_deno@0.0.24/src/compression-method.ts": "5bb7b8884eadcd91e6ad6016d1a644bdb5022020cdc58df1be3d38f742054ae1", - "https://deno.land/x/imagemagick_deno@0.0.24/src/defines/define.ts": "645fb3a06424ed750250212ac8762ba2ea97c4e4fdbda8aedf21734cbaf4833c", - "https://deno.land/x/imagemagick_deno@0.0.24/src/defines/defines-creator.ts": "876b7215bb6523cee562bcdf95cdd20cce33dd118db9f61d3d9e6305d5cfa631", - "https://deno.land/x/imagemagick_deno@0.0.24/src/defines/defines.ts": "fc8e12475e11a30f9f6f9c2b5e2fba94b01d65135654b97694da915d40fae2fe", - "https://deno.land/x/imagemagick_deno@0.0.24/src/defines/magick-define.ts": "07e9e7fab4ea23f06f584163a99e0007ac1d1f3379ab4a872f1ddd555dd8ddd1", - "https://deno.land/x/imagemagick_deno@0.0.24/src/density-unit.ts": "4e5a451dde11303aaf99f7f5f9c1409f2d5399e0678a4ab589a33e9757d04491", - "https://deno.land/x/imagemagick_deno@0.0.24/src/density.ts": "b12b953f53900a2182563963fc08074453fb7bff387c06a7e04a8b26045be91e", - "https://deno.land/x/imagemagick_deno@0.0.24/src/disposable.ts": "4e30ceedea5c7829153ded68929cc206a303be220a560bb27319b607ee68c4a7", - "https://deno.land/x/imagemagick_deno@0.0.24/src/distort-method.ts": "13819e00ccb6a636af9ece5d11dfce9451e578d46c94e1f528b0ae5da7721985", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-color.ts": "fe3785761844f09a2c8bfda1821580c18706d986b1e7619dd984db6d33f3093a", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-fill-color.ts": "566325e1b1eee1e4d74a97c7f0cfbd5cc1c3dbab9a39e8820b3e2555280fe633", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-fill-opacity.ts": "a8bfce5d256ed1f296b4e57ae49035dad0baadec57b7ee68731494530b5df816", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-font-point-size.ts": "ca4f41670186b13c1195eef74764af1ea00592b9faad5970d7867b3cb47630c3", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-font.ts": "6a332ab99030ffe9fffd83704ba3e655649e48f026ef78dfb04d0bb1f99416bf", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-gravity.ts": "184729aecc6d700a81eec2b8157b27b77f28c4188eb0f0337446b933e3857dbe", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-rectangle.ts": "c6b1bb4b81a7e66e59a9338d45816a8471d951a6583567df68b31d3fde5ae6e0", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-round-rectangle.ts": "7cd7e90d6d5902abee81061c7ce5919a50ef340d67d984f1ab5eb15ba22683c8", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable-text.ts": "f13d014b6f8b06e666b701b3860864711d0a0f942c1a30940af939705e72e5ce", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawable.ts": "61b40233ea3c28664c2f8dfd8d794772d8a7a779f4228060efd41b0d44762521", - "https://deno.land/x/imagemagick_deno@0.0.24/src/drawables/drawing-wand.ts": "c6fe73c4f4d48b8802aa79ea636b8d1f16046f2eb516b84260ae446f392b1528", - "https://deno.land/x/imagemagick_deno@0.0.24/src/endian.ts": "5c36e4c104dd79760a96d1a7e759fbb04907b40d4cbfd30a25d3ef25bf7f63ed", - "https://deno.land/x/imagemagick_deno@0.0.24/src/error-metric.ts": "fafe44d95312b0e9dd6e5d6d3efd536764468a4b80e3dc3d7d7efc33a40fb871", - "https://deno.land/x/imagemagick_deno@0.0.24/src/evaluate-operator.ts": "c05d51cb193d95ce0432dee914465cbafc35026ea1102cc48f431571bfb67260", - "https://deno.land/x/imagemagick_deno@0.0.24/src/filter-type.ts": "face0109ae9e56125b778a8842384031d6e0bd688dfcf96c0861f2fd8bb27225", - "https://deno.land/x/imagemagick_deno@0.0.24/src/formats/dng/dng-output-color.ts": "13533caccea31a8e6cd960df47c1b608f89a786d51e9c4b3b869ca40eb01c5a8", - "https://deno.land/x/imagemagick_deno@0.0.24/src/formats/dng/dng-read-defines.ts": "73d3fdc79c0f37ea3b2681f4e159cd927c25cb0801cc43905a4331171062f6c8", - "https://deno.land/x/imagemagick_deno@0.0.24/src/gif-dispose-method.ts": "841f730e6c282d81296111c32208614adb9c96cd700c76df4eae71140458f9d5", - "https://deno.land/x/imagemagick_deno@0.0.24/src/gravity.ts": "ed99d33e3775c510c0a29fb330ca5ac9445e41dd3644507186c25cc32eb8634a", - "https://deno.land/x/imagemagick_deno@0.0.24/src/image-magick.ts": "a86eda53f0d5a9054883b63fe48957d01a385eda57370e3dc080a7cc5f127e70", - "https://deno.land/x/imagemagick_deno@0.0.24/src/index.ts": "11210e50a9d9a1b2fe8cd9f8ddfd699dbaa3f8b5b88da68104a68319a1c96e7d", - "https://deno.land/x/imagemagick_deno@0.0.24/src/interlace.ts": "7b75a93d79311bcb5fe6d749f91d2bf122f2443a771dff721c11756e823dc445", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/disposable-array.ts": "048f80ffee94ccb37437961e7bcaa57ee0b0bd95a64423257010a8eeb18e9fae", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/disposable.ts": "48ea6c820871b46b9c50f8343bb68765bb81c80bf8edefc50c2d27bf4526c618", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/exception/exception.ts": "2c1e1d5f6df4fcaef50403ed18f5ebdf560a5e764944d569db406e97f76f2aae", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/geometry-flags.ts": "56bbc3f668db2e67f607cd1c08e07f51ded80a8c402efb0b6cd4ad98d0f69d19", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/magick-rectangle.ts": "ffffcd9ebffe20f871396af22c9f5acb332b5d503a5b21200a94e1e61e4e68b3", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/native/array.ts": "99fa112bde643aceda481db90be21d6264c23074cf77ad4a7fee41208ba75dcd", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/native/string.ts": "a8edad35a243f76ef98cf387580bbf771790d1b1d8299d77ea1731aa75c2c158", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/pointer/pointer.ts": "d866febf67a2d72678e6bd0fd70f751622348c3c2c4ad0aba42dbd750c4f8526", - "https://deno.land/x/imagemagick_deno@0.0.24/src/internal/string-info.ts": "6121081f0382fdfe259bb6c95655b1626cc68af778ad91af437daa8c55965575", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-color.ts": "6e849e94f3183d86f44d55f4646af394d0d3573fbce8b26b6d6bfbda03dcaf5c", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-colors.ts": "c3a4cdbbca0ebce9386ae71f835118847d8770573efcb63a35c54242aa156f90", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-error-severity.ts": "160e5f07bad67542c9c95a8ec61e70f294333bf7f3c463419dc4fadfacdbdbf6", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-error.ts": "5a515e203373ef48903bda51635e04f232bf3144eaee48c66d65df1e705346d4", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-format-info.ts": "8c3ae6ca748d18ae69e33756d2cf64219971350a856407861a0bf7bc119909a6", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-format.ts": "3373328cacc0e6a5b1264a6bedceffe17f687f8d303c8c3d0ecd7a9102771580", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-geometry.ts": "2ce766f7d4338484fed3fd9b25d4ec1ccd77f89d299db8391e048f77708f8245", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-image-collection.ts": "aac08ac15396d6d371753989890125cd36d1e9c30e7676de1c2f08fdbe5d581a", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick-image.ts": "4ae0621e582ea42b0dbf5d9435b5ce5263616f37038df93a94567e9f795e51c4", - "https://deno.land/x/imagemagick_deno@0.0.24/src/magick.ts": "990bbb125a908afd71bba8b9601704f64abfe68b861e906ec7495f87b2f4c776", - "https://deno.land/x/imagemagick_deno@0.0.24/src/native-instance.ts": "791c1dc7459d0daf53fd385d255edb9d84869edc2943cd1ff3b4b7c34aeeffb0", - "https://deno.land/x/imagemagick_deno@0.0.24/src/orientation-type.ts": "a5c48feec25d432e5c3ad3ed76c929a7960836d3ab1012525c0f7883e4f46c30", - "https://deno.land/x/imagemagick_deno@0.0.24/src/paint-method.ts": "0178827b90549bf587e8ae9e2757cb96607b1fffa5c05d0534a8de136a346d29", - "https://deno.land/x/imagemagick_deno@0.0.24/src/percentage.ts": "00240337512949c97e407b006cdd025af5fc6db600adce9ca6193ab61e326291", - "https://deno.land/x/imagemagick_deno@0.0.24/src/pixel-channel.ts": "1a6943107a0c9b73757b08b4e41bd72227fec2dbb29f7fe5871257abcf541fa1", - "https://deno.land/x/imagemagick_deno@0.0.24/src/pixel-interpolate-method.ts": "d2c62675acb5d8fffca3e2c91c9a35bfebec62f2424268e5e240f9f17f57d356", - "https://deno.land/x/imagemagick_deno@0.0.24/src/pixels/pixel-collection.ts": "e44aa8ebff8635d3959a20ffa9a545ce5df2dc2a8806f6b0d1d0092e185577f7", - "https://deno.land/x/imagemagick_deno@0.0.24/src/point.ts": "f664938d0f39eadd41fe5eb8ca81c52b59a7f7138539afea3ddc863d25a4a935", - "https://deno.land/x/imagemagick_deno@0.0.24/src/primary-info.ts": "87d9588a4ba37d0399e5c80fa7cbd602ed86ee544f6aeeab2f6bbe1ac366acef", - "https://deno.land/x/imagemagick_deno@0.0.24/src/profiles/image-profile.ts": "ea1bb6406430a03cf9263a40260fcd8f99bcc14fa3629206fcbcd2679c94b4a2", - "https://deno.land/x/imagemagick_deno@0.0.24/src/quantum.ts": "7e92f9cf73fc6ec89df48ab4c462339fad580f0b6a1e1009d3c5a5cb599dc3ed", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/distort-settings.ts": "cdb352260b90a140191c222bafde0740114062822400bdf89709bef1c2f40563", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/drawing-settings.ts": "c5191eff30944c7a6047dfdd4d6a6714cbd3e0154c4942bb53bfa25acc3399b2", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/magick-read-settings.ts": "d5e83efd74ff563146c5c074cefeead82e07763c0e75d271c6f1ba9f1fca3b63", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/magick-settings.ts": "086a4f372d0e7f46db262035d9e015b8d92e9e16475880373792a530ae7384ab", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/montage-settings.ts": "db5dc688e2165b1a5501b6f2175c0453f9a1c435c6d1d20be657ddf26af2bb14", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/native-drawing-settings.ts": "a69579abe2600a8ae13484f0abee16ec17d4c31213e95fe0fb9b581ee434af64", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/native-magick-settings.ts": "4d0aba713c710314b802317c017aae17bbc87f3fa9cf2149691feb7e9987f618", - "https://deno.land/x/imagemagick_deno@0.0.24/src/settings/native-montage-settings.ts": "5b0c810053730ba63a6603fe4109c09ce02d3dd9f5a04ba1070094cb69a92433", - "https://deno.land/x/imagemagick_deno@0.0.24/src/statistics.ts": "a9122f555b565d9869dcc9070d77febc54f2e12324932c6b8619e1a94b599885", - "https://deno.land/x/imagemagick_deno@0.0.24/src/virtual-pixel-method.ts": "ae2f0520e05b382299e4d41f4d7e2c67baf727ef7c816037e601c978948b1451", - "https://deno.land/x/imagemagick_deno@0.0.24/src/wasm/magick.ts": "b5ec7d6c3c7379f8f9ba0c23238f7024aa35f3a15edb2d1cbca4ccc44a186ac9", - "https://deno.land/x/imagemagick_deno@0.0.24/src/wasm/magick_native.js": "ce63e9894f66443624d09f8d9524d3c985e4174d6848b0e0c77a09d5d5b4759e", - "https://deno.land/x/lume@v1.17.5/cli.ts": "0abaeb7a2fc59e1ec0f6742485a3df599e36263894cf622e3f606eb326e827bb", - "https://deno.land/x/lume@v1.17.5/cli/build.ts": "eda0271fc01685951c01961e19b5eca64f7fabfe6777a4caf670077a1481d44a", - "https://deno.land/x/lume@v1.17.5/cli/create.ts": "3b5ed82e4c81858b53f929502ab3dc7c3e2f63be80c1d41011bc566442bbb4e9", - "https://deno.land/x/lume@v1.17.5/cli/run.ts": "ee2813fe642f22f311f578ef0c6bc0f007e592f82be4d77d4c0b8923b8708d6c", - "https://deno.land/x/lume@v1.17.5/cli/upgrade.ts": "abb558253778b5fa75159eeeb69214fd1f51feabd1e9e12e8e9c1f5b934bf37d", - "https://deno.land/x/lume@v1.17.5/core.ts": "be7573baa55a0e34a0cbaf95405bcbeca95e2004d532b07052af51f466d6c143", - "https://deno.land/x/lume@v1.17.5/core/cache.ts": "6d770debcedbb7441c2a9d14096ed518907e6615e0d5d014b83d4f5be52a7b3a", - "https://deno.land/x/lume@v1.17.5/core/component_loader.ts": "b95db6b450fb71dc3fc0464e806515e4fde59a4d6341042e014b3199a2b3ac5d", - "https://deno.land/x/lume@v1.17.5/core/data_loader.ts": "539968d1eb2759e14be2842a71b94cc1f14c66e2e0f57bc2530ea5444e4d92fa", - "https://deno.land/x/lume@v1.17.5/core/errors.ts": "73901534cfb14808a0fc69fd4929f169604b85f7b94e494be53e101f956b315d", - "https://deno.land/x/lume@v1.17.5/core/events.ts": "f02a60b815325093b650bcf4104d023a079b990dfa29b103f2cf2062b3ffc1ce", - "https://deno.land/x/lume@v1.17.5/core/filesystem.ts": "c21444f2f3855d857bcee9e4a92470e1abfa875008fd8bb85ea4a3c4b0582ee3", - "https://deno.land/x/lume@v1.17.5/core/formats.ts": "0a0be3070e179b33122f064251bd5d75dd60ea633f038265c4ce1a42b4916985", - "https://deno.land/x/lume@v1.17.5/core/fs.ts": "2530fee3e8a95f484d2aed118fd39f4f8b48fb2b7c77ac784a9c535abb5b0b2a", - "https://deno.land/x/lume@v1.17.5/core/loaders/binary.ts": "cbbfe972103e2663adb2bcf350e2431a6510ef05ce2accc57cd59b09123a9f3d", - "https://deno.land/x/lume@v1.17.5/core/loaders/json.ts": "ef23666ff3a42d45389bfe9aff7056dffc86f09e75182c723941c37f326a3c63", - "https://deno.land/x/lume@v1.17.5/core/loaders/module.ts": "b6d0a1c8250f340054e13843966d371021db869a267c98df63aeda6ca14a9290", - "https://deno.land/x/lume@v1.17.5/core/loaders/text.ts": "4b1bc3bc64863dfac5cfd0a0acab0ea7b92cf04fffd5423b436dd9b138013a76", - "https://deno.land/x/lume@v1.17.5/core/loaders/yaml.ts": "025893f94aa9c56686832752892b97f029e93e4d58ca2236ea6f16fc7c01fd5a", - "https://deno.land/x/lume@v1.17.5/core/logger.ts": "6e1b68ac2bb7c052defb99bd925f7dd4522ed7c35ba2011bbfa84fbad9ee1d20", - "https://deno.land/x/lume@v1.17.5/core/processors.ts": "a94b05c794798e04ccdb2909a956d1ee239699e974c470b9e76eede41000acc5", - "https://deno.land/x/lume@v1.17.5/core/renderer.ts": "0685345b40ac6d0e2138624c5b04e6ff045ee6141e870a8aa5174ee352aad5a9", - "https://deno.land/x/lume@v1.17.5/core/scopes.ts": "e12866d3a7d904b8d0635deed1e9b4c8f591ed41f3f26bd7dc55c86fffd6af43", - "https://deno.land/x/lume@v1.17.5/core/scripts.ts": "085e221e6c57840e888442e690cc11c21ef44caad43fd9887fc9132984ee6305", - "https://deno.land/x/lume@v1.17.5/core/searcher.ts": "154b48a65e6a48be2596ac6af4b10bd6e63387b16d90a8bd40fde56d7e02cda7", - "https://deno.land/x/lume@v1.17.5/core/server.ts": "f7943448826e9ba2314923139ca00ecfe8b3b4e50676a132d1c3916a00ba76d3", - "https://deno.land/x/lume@v1.17.5/core/site.ts": "7c1604dd79eeea5ab25db032c4ec569799d13cb6c60137cbbe891c8c2285a7a9", - "https://deno.land/x/lume@v1.17.5/core/source.ts": "83ed798b3fcba9d24dd46156439a80d1fb999810de813dedbe7fa6e5c5e246b9", - "https://deno.land/x/lume@v1.17.5/core/utils.ts": "9efc99ceec39f1742caf0e974fcbac69c814a63008661d695536a04b5f5691ca", - "https://deno.land/x/lume@v1.17.5/core/watcher.ts": "a92ea8cebe0f93446a7a8d870f4d2c757d220b92aec898cf616fabe02d0d0fcb", - "https://deno.land/x/lume@v1.17.5/core/writer.ts": "e5f3f1659900c2500f1f25870e98ad8207f236bf350f83aa99acd74c84cf6291", - "https://deno.land/x/lume@v1.17.5/deps/base64.ts": "3125684e578c63cbb159bd16105dd5dbbb649d95a99f2b961f73bfdd12654b71", - "https://deno.land/x/lume@v1.17.5/deps/cliffy.ts": "faff0c2ca187ec9fd1ad8660141f85b9d05b5c36bab25b40eb5038c02590a310", - "https://deno.land/x/lume@v1.17.5/deps/colors.ts": "bd17f327a997ff3514da14cc59951fea1acfe3e87095ecb4fa69da10af701512", - "https://deno.land/x/lume@v1.17.5/deps/crypto.ts": "5343b6aa2496f5226112e8256ff320325e8f241684cf820bba26dd8726b0f94c", - "https://deno.land/x/lume@v1.17.5/deps/date.ts": "a9320999733700f106ddffa9b457e38e879cc86acfcd51a3d9bd2c6d847aef45", - "https://deno.land/x/lume@v1.17.5/deps/dom.ts": "c2859fc5e59466367883160b5eeb2e60b2bbe84e0e50edda0bb05111a4c67c51", - "https://deno.land/x/lume@v1.17.5/deps/flags.ts": "360b7b4cf2001bcdbddd5d58f803cc40760df246207c982dfbe76018fbcc315a", - "https://deno.land/x/lume@v1.17.5/deps/front_matter.ts": "19abe9da1f14d0e59bba3f584c1bb315d8242b4835d9b385ef55f8e84a3271ec", - "https://deno.land/x/lume@v1.17.5/deps/fs.ts": "c73ec0799e62b948cd11c83ac22f305b769ade21ea9c6d6c04cc5f36373f3163", - "https://deno.land/x/lume@v1.17.5/deps/hex.ts": "0792b8e3dd53e3bdf92112dc1e974e081cd6b0db9c1209ec002849599da8f54b", - "https://deno.land/x/lume@v1.17.5/deps/highlight.ts": "e949067730c5e9c57bc4c102ccafa3bec0ea1cf681c921fe8336b1a6bd0c4f33", - "https://deno.land/x/lume@v1.17.5/deps/http.ts": "548d5dc3d6aeb7be81c2a8e33b87e773b914cfb461b05a7b0eb1492fb2f5a64f", - "https://deno.land/x/lume@v1.17.5/deps/imagick.ts": "454f17dc4b73fd278647618cbabc981eccf6f83892f68bb88b4ed6017255f4fb", - "https://deno.land/x/lume@v1.17.5/deps/jsonc.ts": "23741df3999fa1748c57d9069cb8714b4b5ded59b0858e5255519b2348c987c9", - "https://deno.land/x/lume@v1.17.5/deps/markdown_it.ts": "e48e34de092bc3fc96617372f26190cf3bbf722f4206a9e9e64febbc8fb925c4", - "https://deno.land/x/lume@v1.17.5/deps/nunjucks.ts": "960bc1c481145ac600e811907bdc3c62de7387a4763a07c5f43e325b5fea9da2", - "https://deno.land/x/lume@v1.17.5/deps/path.ts": "c173a61845bbed17c7d85236e66d27a717b5d3b8b2c75e378b8e713b5482d9d9", - "https://deno.land/x/lume@v1.17.5/deps/preact.ts": "e7379cab39314f93cc18f9dd591cdd00a081c6b057eb9befc5b59b436daf6d73", - "https://deno.land/x/lume@v1.17.5/deps/react.ts": "6ce99e14ad6bd9ce09f69c90b5a5597e44be78eab7f334bcab31f4824b12536a", - "https://deno.land/x/lume@v1.17.5/deps/xml.ts": "0aeac39b8613c8d4e389277334c714937bb7c638ebd6ce1295d771dbedf44cbb", - "https://deno.land/x/lume@v1.17.5/deps/yaml.ts": "62a2798b96782084e0347cdc118d91ca6b44d486ffcc6f3f21aac73429bc2311", - "https://deno.land/x/lume@v1.17.5/middlewares/logger.ts": "69e69099a2e3a8c62d0bb62014381337f6e855c6b330e210e4a705fe8111e10a", - "https://deno.land/x/lume@v1.17.5/middlewares/no_cache.ts": "95fb11d820d931b6aca268fa30aee22e315c556bd214e135bb9b5ddbcbe039d6", - "https://deno.land/x/lume@v1.17.5/middlewares/not_found.ts": "8a2b7d78d0dd4f441cee909682faac9387ca3b801f8e4ae0f6695a007313732e", - "https://deno.land/x/lume@v1.17.5/middlewares/reload.ts": "1162ee5fc3c7e1f6b5fb3da73a648fc71f0a64d30ca48f05ed066c8cb80adbb6", - "https://deno.land/x/lume@v1.17.5/middlewares/reload_client.js": "34d75e01503fae8180796de882af42b1125fac88f22a010a99d5548de1ba7d72", - "https://deno.land/x/lume@v1.17.5/mod.ts": "829abdd9fe45f04a6db27caa9e3bcc7f72b65c3810b67ad498582bc05b5e743d", - "https://deno.land/x/lume@v1.17.5/plugins/code_highlight.ts": "5b177676c6c60ba10d676ac9fc8b8b3103920529bb115656adc406737f25c9ab", - "https://deno.land/x/lume@v1.17.5/plugins/feed.ts": "2a782784abb4a50c65817f6283e167853291f6df8744916c5d22e799e9360cb4", - "https://deno.land/x/lume@v1.17.5/plugins/imagick.ts": "68fd0fee4f6ff26d0494b1792311ad84a863618d3d3e5af2701b99abceccd1be", - "https://deno.land/x/lume@v1.17.5/plugins/json.ts": "60f3b8616c282a9c592e1974db143b3461999c55b20235eebced87482ebf16bd", - "https://deno.land/x/lume@v1.17.5/plugins/jsx.ts": "532a055f1c6c6f00d21d13cb658614d5f6fc56ea28266020d371ba5fb151461a", - "https://deno.land/x/lume@v1.17.5/plugins/jsx_preact.ts": "65d02bf551d7649317c754e3664a8f376f498461a2764114be8327ebfe98afd4", - "https://deno.land/x/lume@v1.17.5/plugins/markdown.ts": "31a27f73ed3c79c6c87808ef9e4343c0bbbf896220ee97e88dad4474f091fb39", - "https://deno.land/x/lume@v1.17.5/plugins/metas.ts": "8d8a32aafad87c9ef393e3dcf142bdedc8bd2ab530fb9751ddba30aa3853b83b", - "https://deno.land/x/lume@v1.17.5/plugins/modules.ts": "d31ababab5e35b47fc207685765c9431ddc7bec019061e18b1d36f527e13029d", - "https://deno.land/x/lume@v1.17.5/plugins/nav.ts": "81427c289a79ea73dd57d3f63ab2bcfbf900c7992ac2fb0e8631616247b77e88", - "https://deno.land/x/lume@v1.17.5/plugins/nunjucks.ts": "81055bcb1c599fdd52eee6c8cce48c2698441027c79ad85dacf6b6d8a871d211", - "https://deno.land/x/lume@v1.17.5/plugins/paginate.ts": "e997b33da12da9d68b72d0c7615ec49d0e187012e8ffc78dac2b558edf27f795", - "https://deno.land/x/lume@v1.17.5/plugins/search.ts": "af3ea229915f3580962c9da6be33ca8e639b8444ce8604769968afd731b9dd3c", - "https://deno.land/x/lume@v1.17.5/plugins/source_maps.ts": "6de5f075d02f56a715042f3ccceff97b35cbf525e4cdb0c51a71ca5e66e54531", - "https://deno.land/x/lume@v1.17.5/plugins/url.ts": "43d3d47896a7322a8dd34572dedb4baa6f73a382594a2ff7c34a3a064dcc6c9e", - "https://deno.land/x/lume@v1.17.5/plugins/utils.ts": "6435d164539d9e408e7e818b080cc1a96ff76ed3c376160577a7df751b57fa07", - "https://deno.land/x/lume@v1.17.5/plugins/yaml.ts": "df24aac4098dba258f1ac331a3b16ba488a336eb63c51afed8f59201228d583c", - "https://deno.land/x/lume_markdown_plugins@v0.5.0/toc.ts": "442aff9a6942d100ebfcac70af3ea8bd0456c0b7a64a0997852b74bad33a8473", - "https://deno.land/x/lume_markdown_plugins@v0.5.0/toc/anchors.ts": "8a4a1c6b2c63156622695ceba57fa7100a6e5f109c9a383a1dcaf755233c8184", - "https://deno.land/x/lume_markdown_plugins@v0.5.0/toc/mod.ts": "f949307ce92d59fb0bbfbeeca1575bf50a6226f2476d2bca8397e5ef7020986c", - "https://deno.land/x/lume_markdown_plugins@v0.5.0/utils.ts": "6e6c3c394709eff39080562732c2dafe404f225253aaded937133ea694c4b735", - "https://deno.land/x/lume_markdown_plugins@v0.5.1/toc.ts": "442aff9a6942d100ebfcac70af3ea8bd0456c0b7a64a0997852b74bad33a8473", - "https://deno.land/x/lume_markdown_plugins@v0.5.1/toc/anchors.ts": "8a4a1c6b2c63156622695ceba57fa7100a6e5f109c9a383a1dcaf755233c8184", - "https://deno.land/x/lume_markdown_plugins@v0.5.1/toc/mod.ts": "f949307ce92d59fb0bbfbeeca1575bf50a6226f2476d2bca8397e5ef7020986c", - "https://deno.land/x/lume_markdown_plugins@v0.5.1/utils.ts": "6e6c3c394709eff39080562732c2dafe404f225253aaded937133ea694c4b735", - "https://deno.land/x/xml@2.1.1/mod.ts": "4a314a7a28d1ec92f899ce4c6991f0356c77550a75955ec3f4a36733f08548e8", - "https://deno.land/x/xml@2.1.1/parse.ts": "614b8648345ae93c641368836947484d321c7ac9312ae12ec750434353cd7385", - "https://deno.land/x/xml@2.1.1/stringify.ts": "930d35431f153b29d36549cff08fcfbe978e52ccb56af1e3baa2e0760f418b04", - "https://deno.land/x/xml@2.1.1/utils/parser.ts": "7f3b00aefabdd6d47e17061cbc35c3c4d44cc1ed174180b7eb70184e56bb5ba6", - "https://deno.land/x/xml@2.1.1/utils/stream.ts": "056e2f368d47932d77e431bbc4a8292359171cc9ce881ea31ce0aae30d763e68", - "https://deno.land/x/xml@2.1.1/utils/streamable.ts": "1603a5f10c859b95d4e9502365a0fba0b19d5d068356e20d5a6813cd37fee780", - "https://deno.land/x/xml@2.1.1/utils/stringifier.ts": "c701b506835237c0c6c0a08fd94e0a012b644def3f4c819c64788daf2e649ea3", - "https://deno.land/x/xml@2.1.1/utils/types.ts": "ecaf7785e54a6f1da6f8e56da2bce9853407ceb7d5b3b70f0a60a0890151fe4c", - "https://unpkg.com/@highlightjs/cdn-assets@11.6.0/es/languages/bash.min.js": "c68e0c10c8e21aa56ae617cea288b30215bc3941c36d11821f89f350628bf377", - "https://unpkg.com/@highlightjs/cdn-assets@11.6.0/es/languages/javascript.min.js": "1987b5a890a8e5cea9db74c41483fc1bb00923d0bf390a9e866d6cfeebe1c430" - } -} diff --git a/docs/dev/1-overview/index.html b/docs/dev/1-overview/index.html new file mode 100644 index 0000000..b3b54cd --- /dev/null +++ b/docs/dev/1-overview/index.html @@ -0,0 +1,18 @@ + +Overview - Treehouse

Documentation

Developer Guide

Overview

+

Treehouse is a frontend written in TypeScript made to be rendered in a browser or webview for building note-taking and information management tools. It is a "thick" frontend in that it holds most application state in-memory and executes user triggered commands to mutate that state. Persistence of that state, and a few other features, are expected to be provided by a "backend", which the frontend code interacts with via a backend adapter. +Even without a backend adapter, the Treehouse frontend is still a fully functional application packaged as a JavaScript library that can be loaded onto any HTML page.

+

Architecture

+

The library exposes a setup() function that:

+
    +
  1. Takes a DOM document and backend adapter object
  2. +
  3. Sets up a central controller for the UI called Workbench
  4. +
  5. Loads a Workspace using the backend adapter
  6. +
  7. Registers built-in commands and keybindings
  8. +
  9. Uses Mithril.js to mount a top level Mithril component to the document
  10. +
+

The UI is represented by a class called Workbench. This class orchestrates and provides most of the API for the rest of the system. The UI is broken down into Mithril components that implement the views for each part of the UI, pulling state from Workbench and connecting interactions to registered commands that represent all user actions. The Workbench and commands manipulate a Workspace, which represents the main data model for the system.

+

Stack

+

To avoid the complexity and dependency hell, Treehouse avoids most common JavaScript tooling such as Node.js and NPM. Instead, Treehouse uses Deno as a toolchain and otherwise avoids dependencies as much as possible. The other main dependency we have is Mithril.js, which was chosen for its simplicity and lack of further dependencies.

+

That said, there are other dependencies beyond this core stack which are chosen very deliberately. Out of the box search indexing depends on MiniSearch, and our most complex (but unavoidable) dependency is CodeMirror. We're very conscious of project dependencies, including development and toolchain dependencies.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/2-data-model/index.html b/docs/dev/2-data-model/index.html new file mode 100644 index 0000000..46fb8e0 --- /dev/null +++ b/docs/dev/2-data-model/index.html @@ -0,0 +1,12 @@ + +Data Model - Treehouse

Documentation

Developer Guide

Data Model

+

The "document" for Treehouse is the Workspace, which is mostly a container for nodes. These nodes are based on a versatile and extensible API and data model called Manifold. In short, nodes:

+
    +
  • have a unique ID
  • +
  • have a text name
  • +
  • can be children to other nodes
  • +
  • can have key-value attributes
  • +
  • can have components
  • +
+

Most importantly, nodes build a tree-like structure where each node can be extended with components. Components extend the state and functionality of a node. For example, a checkbox component can be added to a node. This gives it a state (checked or not) and allows the UI to render it differently (add a checkbox).

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/3-user-actions/index.html b/docs/dev/3-user-actions/index.html new file mode 100644 index 0000000..90ab2fa --- /dev/null +++ b/docs/dev/3-user-actions/index.html @@ -0,0 +1,7 @@ + +User Actions - Treehouse

Documentation

Developer Guide

User Actions

+

All user performable actions are modeled as commands and registered with a command system. Commands are functions with some extra metadata, like a user displayable name and system identifier. They can be called throughout the system by their system identifier, such as when a user clicks a something.

+

Menus are often defined upfront, usually by commands. Commands can have keybindings registered for them for keyboard shortcuts. These systems work together. For example, a menu item for a command will show the keybinding for it.

+

Commands can take arguments and usually take at least a single argument called a context. This is a way to represent state of the user’s current context, such as the currently selected node.

+

In addition to menus, keyboard shortcuts, and UI event handlers, there is a command palette the user can trigger to show all commands that can be run in the current context.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/4-workbench-ui/index.html b/docs/dev/4-workbench-ui/index.html new file mode 100644 index 0000000..235b43b --- /dev/null +++ b/docs/dev/4-workbench-ui/index.html @@ -0,0 +1,10 @@ + +Workbench UI - Treehouse

Documentation

Developer Guide

Workbench UI

+

Components

+

The workbench UI is made up of Mithril components, which are similar to React components. They take parameters, can have state, and specify a view using JSX. Instead of many atomic components like buttons and labels, Treehouse focuses on larger functional components that map to areas of the UI. Reusable visual elements are represented by CSS classes. Only if a reusable atomic visual element becomes so re-used and is too complex to re-implement is it made into a Mithril component.

+

All the Mithril components can be found under lib/ui. We use Typescript to make sure their parameters are typed so they're picked up by API documentation; no need for custom documentation for view components. Mithril components are just plain old JavaScript objects with a view method.

+

User Context

+

Most components are explicitly passed a reference to the Workbench, which they can use to execute commands or pull data in the current Workspace or Context. The Workbench provides a top level context that has the current selected node or nodes, the current panel, etc. However, when passing a Context around it can be given overrides. For example, you may be currently editing a particular node, but you use the mouse to perform a command on another node. The menu ensures the command will receive a Context with that node being acted on.

+

Design System

+

Our design system is inspired by projects like Pollen, where instead of generating CSS classes from JavaScript like Tailwind (which requires a compile step and Node.js based tooling), we simply use and override CSS custom properties. This means they can be used in inline styles as well. We also have a subset of common Tailwind utility classes defined, though using the custom properties.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/5-backend-adapters/index.html b/docs/dev/5-backend-adapters/index.html new file mode 100644 index 0000000..c66daf4 --- /dev/null +++ b/docs/dev/5-backend-adapters/index.html @@ -0,0 +1,49 @@ + +Backend Adapters - Treehouse

Documentation

Developer Guide

Backend Adapters

+

Backend adapters are classes that implement the backend API for a given backend. If you wanted to make your own custom backend, you would implement your own backend adapter +implementing the APIs you wish to hook into and pass that into the setup function when initializing Treehouse. We also have a handful of built-in adapters for public or +well-known backend interfaces:

+

lib/backend/browser.ts

+

This backend implements the FileStorage API using localStorage. This means data will be stored in the browser for a particular device. It also implements search indexing +using MiniSearch. It does not implement the Authenticator API.

+

lib/backend/github.ts

+

This backend implements the FileStorage API using the GitHub API to store data in a GitHub hosted Git repository. It also implements the Authenticator API against a +script that can be hosted on CloudFlare Workers that implements an OAuth client for the GitHub API. This backend adapter does not implement a search index, it simply +uses the browser implementation (MiniSearch).

+

lib/backend/filesystem.ts (coming soon)

+

This backend implements the FileStorage API using a local filesystem. Since there isn't a good standard filesystem API in browsers, this implementation operates against +a simple REST API that can be implemented by a backend host process, such as Electron, Apptron, or something custom.

+

Writing an Adapter

+

An adapter is just an object that implements this API:

+
interface Backend {
+  auth: Authenticator|null;
+  index: SearchIndex;
+  files: FileStore;
+}
+
+interface Authenticator {
+  login();
+  logout();
+  currentUser(): User|null;
+}
+
+interface User {
+  userID(): string;
+  displayName(): string;
+  avatarURL(): string;
+}
+
+interface SearchIndex {
+  index(node: RawNode);
+  remove(id: string);
+  search(query: string): string[];
+}
+
+interface FileStore {
+  async readFile(path: string): string|null;
+  async writeFile(path: string, contents: string);
+}
+
+

The Authenticator API is optional (and soon so might the SearchIndex API, always defaulting to MiniSearch). Typically a backend adapter will +set auth, index, and files to this and implement each of those interfaces on that same object.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/6-api-reference/index.html b/docs/dev/6-api-reference/index.html new file mode 100644 index 0000000..a3d657a --- /dev/null +++ b/docs/dev/6-api-reference/index.html @@ -0,0 +1,4 @@ + +API Reference - Treehouse

Documentation

Developer Guide

API Reference

+

TypeScript documentation for Treehouse is available via Deno Land.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/dev/index.html b/docs/dev/index.html new file mode 100644 index 0000000..828fe7c --- /dev/null +++ b/docs/dev/index.html @@ -0,0 +1,84 @@ + +Docs - Treehouse

Documentation

Developer Guide

Overview

+

Treehouse is a frontend written in TypeScript made to be rendered in a browser or webview for building note-taking and information management tools. It is a "thick" frontend in that it holds most application state in-memory and executes user triggered commands to mutate that state. Persistence of that state, and a few other features, are expected to be provided by a "backend", which the frontend code interacts with via a backend adapter. +Even without a backend adapter, the Treehouse frontend is still a fully functional application packaged as a JavaScript library that can be loaded onto any HTML page.

+

Architecture

+

The library exposes a setup() function that:

+
    +
  1. Takes a DOM document and backend adapter object
  2. +
  3. Sets up a central controller for the UI called Workbench
  4. +
  5. Loads a Workspace using the backend adapter
  6. +
  7. Registers built-in commands and keybindings
  8. +
  9. Uses Mithril.js to mount a top level Mithril component to the document
  10. +
+

The UI is represented by a class called Workbench. This class orchestrates and provides most of the API for the rest of the system. The UI is broken down into Mithril components that implement the views for each part of the UI, pulling state from Workbench and connecting interactions to registered commands that represent all user actions. The Workbench and commands manipulate a Workspace, which represents the main data model for the system.

+

Stack

+

To avoid the complexity and dependency hell, Treehouse avoids most common JavaScript tooling such as Node.js and NPM. Instead, Treehouse uses Deno as a toolchain and otherwise avoids dependencies as much as possible. The other main dependency we have is Mithril.js, which was chosen for its simplicity and lack of further dependencies.

+

That said, there are other dependencies beyond this core stack which are chosen very deliberately. Out of the box search indexing depends on MiniSearch, and our most complex (but unavoidable) dependency is CodeMirror. We're very conscious of project dependencies, including development and toolchain dependencies.

Data Model

+

The "document" for Treehouse is the Workspace, which is mostly a container for nodes. These nodes are based on a versatile and extensible API and data model called Manifold. In short, nodes:

+
    +
  • have a unique ID
  • +
  • have a text name
  • +
  • can be children to other nodes
  • +
  • can have key-value attributes
  • +
  • can have components
  • +
+

Most importantly, nodes build a tree-like structure where each node can be extended with components. Components extend the state and functionality of a node. For example, a checkbox component can be added to a node. This gives it a state (checked or not) and allows the UI to render it differently (add a checkbox).

User Actions

+

All user performable actions are modeled as commands and registered with a command system. Commands are functions with some extra metadata, like a user displayable name and system identifier. They can be called throughout the system by their system identifier, such as when a user clicks a something.

+

Menus are often defined upfront, usually by commands. Commands can have keybindings registered for them for keyboard shortcuts. These systems work together. For example, a menu item for a command will show the keybinding for it.

+

Commands can take arguments and usually take at least a single argument called a context. This is a way to represent state of the user’s current context, such as the currently selected node.

+

In addition to menus, keyboard shortcuts, and UI event handlers, there is a command palette the user can trigger to show all commands that can be run in the current context.

Workbench UI

+

Components

+

The workbench UI is made up of Mithril components, which are similar to React components. They take parameters, can have state, and specify a view using JSX. Instead of many atomic components like buttons and labels, Treehouse focuses on larger functional components that map to areas of the UI. Reusable visual elements are represented by CSS classes. Only if a reusable atomic visual element becomes so re-used and is too complex to re-implement is it made into a Mithril component.

+

All the Mithril components can be found under lib/ui. We use Typescript to make sure their parameters are typed so they're picked up by API documentation; no need for custom documentation for view components. Mithril components are just plain old JavaScript objects with a view method.

+

User Context

+

Most components are explicitly passed a reference to the Workbench, which they can use to execute commands or pull data in the current Workspace or Context. The Workbench provides a top level context that has the current selected node or nodes, the current panel, etc. However, when passing a Context around it can be given overrides. For example, you may be currently editing a particular node, but you use the mouse to perform a command on another node. The menu ensures the command will receive a Context with that node being acted on.

+

Design System

+

Our design system is inspired by projects like Pollen, where instead of generating CSS classes from JavaScript like Tailwind (which requires a compile step and Node.js based tooling), we simply use and override CSS custom properties. This means they can be used in inline styles as well. We also have a subset of common Tailwind utility classes defined, though using the custom properties.

Backend Adapters

+

Backend adapters are classes that implement the backend API for a given backend. If you wanted to make your own custom backend, you would implement your own backend adapter +implementing the APIs you wish to hook into and pass that into the setup function when initializing Treehouse. We also have a handful of built-in adapters for public or +well-known backend interfaces:

+

lib/backend/browser.ts

+

This backend implements the FileStorage API using localStorage. This means data will be stored in the browser for a particular device. It also implements search indexing +using MiniSearch. It does not implement the Authenticator API.

+

lib/backend/github.ts

+

This backend implements the FileStorage API using the GitHub API to store data in a GitHub hosted Git repository. It also implements the Authenticator API against a +script that can be hosted on CloudFlare Workers that implements an OAuth client for the GitHub API. This backend adapter does not implement a search index, it simply +uses the browser implementation (MiniSearch).

+

lib/backend/filesystem.ts (coming soon)

+

This backend implements the FileStorage API using a local filesystem. Since there isn't a good standard filesystem API in browsers, this implementation operates against +a simple REST API that can be implemented by a backend host process, such as Electron, Apptron, or something custom.

+

Writing an Adapter

+

An adapter is just an object that implements this API:

+
interface Backend {
+  auth: Authenticator|null;
+  index: SearchIndex;
+  files: FileStore;
+}
+
+interface Authenticator {
+  login();
+  logout();
+  currentUser(): User|null;
+}
+
+interface User {
+  userID(): string;
+  displayName(): string;
+  avatarURL(): string;
+}
+
+interface SearchIndex {
+  index(node: RawNode);
+  remove(id: string);
+  search(query: string): string[];
+}
+
+interface FileStore {
+  async readFile(path: string): string|null;
+  async writeFile(path: string, contents: string);
+}
+
+

The Authenticator API is optional (and soon so might the SearchIndex API, always defaulting to MiniSearch). Typically a backend adapter will +set auth, index, and files to this and implement each of those interfaces on that same object.

API Reference

+

TypeScript documentation for Treehouse is available via Deno Land.

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8c3e7fa --- /dev/null +++ b/docs/index.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/project/1-contributing/index.html b/docs/project/1-contributing/index.html new file mode 100644 index 0000000..31ab795 --- /dev/null +++ b/docs/project/1-contributing/index.html @@ -0,0 +1,10 @@ + +Contributing - Treehouse

Documentation

Project Guide

Contributing

+

We'd love your contributions to the project, especially fixes to long-standing issues. If you'd like to work on something that necessitates discussion first, please create an issue so we can provide feedback and avoid stepping on each other's toes.

+

You can also contribute by:

+ +

We welcome any kind of feedback about features you'd like, and we'd especially love to learn about your use case.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/project/2-roadmap/index.html b/docs/project/2-roadmap/index.html new file mode 100644 index 0000000..cf8b0d0 --- /dev/null +++ b/docs/project/2-roadmap/index.html @@ -0,0 +1,4 @@ + +Roadmap - Treehouse

Documentation

Project Guide

Roadmap

+

We're still formalizing our roadmap, how to organize it, share it, and collaborate with the community on it. For now, keep an eye on the GitHub issues and discussions.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/project/index.html b/docs/project/index.html new file mode 100644 index 0000000..848524e --- /dev/null +++ b/docs/project/index.html @@ -0,0 +1,10 @@ + +Docs - Treehouse

Documentation

Project Guide

Contributing

+

We'd love your contributions to the project, especially fixes to long-standing issues. If you'd like to work on something that necessitates discussion first, please create an issue so we can provide feedback and avoid stepping on each other's toes.

+

You can also contribute by:

+ +

We welcome any kind of feedback about features you'd like, and we'd especially love to learn about your use case.

Roadmap

+

We're still formalizing our roadmap, how to organize it, share it, and collaborate with the community on it. For now, keep an eye on the GitHub issues and discussions.

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/quickstart/1-using/index.html b/docs/quickstart/1-using/index.html new file mode 100644 index 0000000..221bfa3 --- /dev/null +++ b/docs/quickstart/1-using/index.html @@ -0,0 +1,17 @@ + +Using from CDN - Treehouse

Documentation

Quickstart

Using from CDN

+

First, you have to directly include Mithril and MiniSearch for now:

+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.3/mithril.min.js"></script>
+<script src="https://cdn.jsdelivr.net/npm/minisearch@6.0.1/dist/umd/index.min.js"></script>
+
+

If you want to use our CSS, you'll also need to include the Google Font it uses:

+
<link href="https://treehouse.sh/app/main.css" rel="stylesheet" />
+<link href="https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,400;0,600;0,700;0,400&display=swap" rel="stylesheet" />
+
+

Then you just need some JavaScript to import and call setup with a built-in or custom backend adapter:

+
<script type="module">
+  import {setup, BrowserBackend} from "https://treehouse.sh/lib/treehouse.min.js";
+  setup(document, document.body, new BrowserBackend());
+</script>
+
+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/quickstart/2-building/index.html b/docs/quickstart/2-building/index.html new file mode 100644 index 0000000..351d89b --- /dev/null +++ b/docs/quickstart/2-building/index.html @@ -0,0 +1,10 @@ + +Building from Source - Treehouse

Documentation

Quickstart

Building from Source

+

You can build from source by cloning or forking the project and installing Deno. Then you can run:

+

deno task bundle

+

This will produce a JS file you can use at web/static/lib/treehouse.min.js.

+

For development or debugging, you can run:

+

deno task serve

+

This will build and serve this website locally, including the live demo site at localhost:9000/demo, which will use +and watch for changes in the lib source.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/quickstart/3-customizing/index.html b/docs/quickstart/3-customizing/index.html new file mode 100644 index 0000000..e7fc51a --- /dev/null +++ b/docs/quickstart/3-customizing/index.html @@ -0,0 +1,10 @@ + +Customizing the Frontend - Treehouse

Documentation

Quickstart

Customizing the Frontend

+

The easiest aspect to customize is the look and feel, which you can do with custom CSS. Our CSS design system is built with +custom properties, so you can include your own CSS and just override them with basic CSS.

+

Further customization might require you to fork and change the source. The source code is very straight forward, but learn +more in the Developer Guide.

+

Lastly, of course, you can implement a custom backend adapter. Although there are just a few things to change this way, +more extension points will be exposed in the future so you won't have to change source. Let us know in the forum what kind +of extension points you'd like!

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/quickstart/index.html b/docs/quickstart/index.html new file mode 100644 index 0000000..ef7689d --- /dev/null +++ b/docs/quickstart/index.html @@ -0,0 +1,30 @@ + +Docs - Treehouse

Documentation

Quickstart

Using from CDN

+

First, you have to directly include Mithril and MiniSearch for now:

+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.3/mithril.min.js"></script>
+<script src="https://cdn.jsdelivr.net/npm/minisearch@6.0.1/dist/umd/index.min.js"></script>
+
+

If you want to use our CSS, you'll also need to include the Google Font it uses:

+
<link href="https://treehouse.sh/app/main.css" rel="stylesheet" />
+<link href="https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,400;0,600;0,700;0,400&display=swap" rel="stylesheet" />
+
+

Then you just need some JavaScript to import and call setup with a built-in or custom backend adapter:

+
<script type="module">
+  import {setup, BrowserBackend} from "https://treehouse.sh/lib/treehouse.min.js";
+  setup(document, document.body, new BrowserBackend());
+</script>
+

Building from Source

+

You can build from source by cloning or forking the project and installing Deno. Then you can run:

+

deno task bundle

+

This will produce a JS file you can use at web/static/lib/treehouse.min.js.

+

For development or debugging, you can run:

+

deno task serve

+

This will build and serve this website locally, including the live demo site at localhost:9000/demo, which will use +and watch for changes in the lib source.

Customizing the Frontend

+

The easiest aspect to customize is the look and feel, which you can do with custom CSS. Our CSS design system is built with +custom properties, so you can include your own CSS and just override them with basic CSS.

+

Further customization might require you to fork and change the source. The source code is very straight forward, but learn +more in the Developer Guide.

+

Lastly, of course, you can implement a custom backend adapter. Although there are just a few things to change this way, +more extension points will be exposed in the future so you won't have to change source. Let us know in the forum what kind +of extension points you'd like!

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/01-what-is-treehouse/index.html b/docs/user/01-what-is-treehouse/index.html new file mode 100644 index 0000000..5e4a7a1 --- /dev/null +++ b/docs/user/01-what-is-treehouse/index.html @@ -0,0 +1,4 @@ + +What is Treehouse? - Treehouse

Documentation

User Guide

What is Treehouse?

+

Treehouse is an outline editor, which you can think of as a nested bulleted list. Each bullet item is called a node. You can use the nodes to create nested layers of folders and notes.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/02-data-storage/index.html b/docs/user/02-data-storage/index.html new file mode 100644 index 0000000..03e772d --- /dev/null +++ b/docs/user/02-data-storage/index.html @@ -0,0 +1,11 @@ + +Data Storage - Treehouse

Documentation

User Guide

Data Storage

+

Localstorage

+

By default, data is stored in your browser’s local storage. That means that the data is linked to your specific browser and device. If you clear your browser cache, the data will be wiped.

+

GitHub

+

If you choose to log in with GitHub, we’ll create a repository and store your data there.

+

To store your workspace, we will create a public repository called .treehouse.sh if it doesn’t already exist. If you want to make the repository private, you can do so in GitHub.

+

To switch back to the local storage backend, log out from the Options menu.

+

Multiple sessions

+

If you log in to Treehouse from multiple devices, the most recent device will have edit and save access. Other sessions will be prompted to refresh the page; if/when you do so, that session becomes the new active session.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/03-nodes/index.html b/docs/user/03-nodes/index.html new file mode 100644 index 0000000..5e6ec8c --- /dev/null +++ b/docs/user/03-nodes/index.html @@ -0,0 +1,34 @@ + +Nodes - Treehouse

Documentation

User Guide

Nodes

+

Your Workspace is your top level node, which all other nodes are nested under.

+

Learn how to manage and edit your nodes.

+

Add

+

You can add a new node in a few ways.

+
    +
  1. Hit ENTER on your keyboard and start typing.
  2. +
  3. Click into the blank area next to the plus symbol and start typing.
  4. +
+

Indent

+
    +
  • Indent a node using TAB ↹
  • +
  • Outdent a node using SHIFT + TAB ⇧ ↹
  • +
+

Move

+
    +
  • Move a node up with SHIFT + COMMAND + UP ARROW ⇧ ⌘ ↑
  • +
  • Move a node down with SHIFT + COMMAND + DOWN ARROW ⇧
  • +
+

Expand or collapse a node

+

If a node bullet has an outline around it, that’s an indication that it has nested content that is currently hidden. Click the node to expand it.

+

Click an expanded node once to collapse it.

+

Node menu

+

When you hover over a node, you’ll see a menu to the left of the node bullet. Click it to access node options, such as indent/outdent, open in a panel, etc.

+

Node formatting

+

Currently, all nodes are formatted as plain text. You can, however, add a checkbox to a node. With a node selected, open the command palette (⌘ K) and select "Add checkbox".

+

View

+

Double click a node to zoom in.

+

Side-by-side (Panel) view

+

To view two nodes side-by-side:

+

Open the node you want to be in the righthand panel. From its menu, choose “Open in New Panel”.

+

You can close or expand either panel to return to a single panel view.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/04-fields/index.html b/docs/user/04-fields/index.html new file mode 100644 index 0000000..269b1f7 --- /dev/null +++ b/docs/user/04-fields/index.html @@ -0,0 +1,11 @@ + +Fields - Treehouse

Documentation

User Guide

Fields

+

A field is a node that can store structured information. Fields provide your data with structure, and allow for special search syntax (see Smart Nodes).

+

Create a field

+

To add a field to a node:

+
    +
  1. Indent underneath the node you want to contain the field, and type the field name
  2. +
  3. Use Command/Control + K to open the command palette, and choose "Create field"
  4. +
  5. Add your field value in the value section
  6. +
+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/05-smart-nodes/index.html b/docs/user/05-smart-nodes/index.html new file mode 100644 index 0000000..fe512a1 --- /dev/null +++ b/docs/user/05-smart-nodes/index.html @@ -0,0 +1,15 @@ + +Smart Nodes - Treehouse

Documentation

User Guide

Smart Nodes

+

Smart Nodes allow you to create an auto-updating search of all the nodes in your workspace. Simply type your search term, or use the format "fieldname:valuename" to filter specifically by field values. The Smart Node will update automatically as your node content changes. This is a simple but super powerful way to view your data in new configurations.

+

To create a Smart Node:

+
    +
  1. Create a new node where you want your Smart Node, and type your search value
  2. +
  3. Use Command/Control + K to open the command palette, and choose "Create Smart Node"
  4. +
+

Tips for using Smart Nodes

+
    +
  • You can filter on multiple fieldname values (using AND, not OR) like so: "fieldname:valuename fieldname:valuename" etc.
  • +
  • If your fieldname has spaces, put quotes around it (fieldname:"Value name")
  • +
  • Search terms are case-insensitive
  • +
+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/06-tags/index.html b/docs/user/06-tags/index.html new file mode 100644 index 0000000..2674541 --- /dev/null +++ b/docs/user/06-tags/index.html @@ -0,0 +1,13 @@ + +Tags - Treehouse

Documentation

User Guide

Tags

+

Tags are versatile metadata. Here are some of the ways you can use them:

+
    +
  • Conduct a keyword search in the searchbar
  • +
  • Use in Smart Nodes to create custom views
  • +
  • Use to add a template to a node
  • +
+

Create a tag

+

On any node, with your cursor at the end of the text, type "#" then the tag name to create a tag. Any tags you have already created will show up in an autocomplete list when you type "#".

+

Delete a tag

+

Click on the tag and press Backspace on your keyboard to delete it.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/07-templates/index.html b/docs/user/07-templates/index.html new file mode 100644 index 0000000..dfbf642 --- /dev/null +++ b/docs/user/07-templates/index.html @@ -0,0 +1,13 @@ + +Templates - Treehouse

Documentation

User Guide

Templates

+

Templates allow you to add a set of predefined fields and other child nodes to a node simply by adding a tag to that node.

+

Create and use a template

+

In this example, we'll create a template called "book" with child fields Author and Release Date.

+
    +
  1. Wherever you want to store your template, type "book" in the node.
  2. +
  3. Add child fields Author and Release Date, and leave the value fields empty. (To turn a regular node into a Field: Command Palette > Create field)
  4. +
  5. With your cursor in the parent "book" node, open the Command Palette (Command/Control+K) and choose Make Template.
  6. +
  7. Create a list of books wherever you'd like, if you haven't already. With your cursor on one if the individual books, type "#book" and hit ENTER to create the tag.
  8. +
  9. Check out one of your book nodes—it should have the child fields Author and Release Date that you set up in your template.
  10. +
+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/07.1-views/index.html b/docs/user/07.1-views/index.html new file mode 100644 index 0000000..0d83295 --- /dev/null +++ b/docs/user/07.1-views/index.html @@ -0,0 +1,32 @@ + +Views - Treehouse

Documentation

User Guide

Views

+

You can view your data in many different ways using commands in the Command Palette.

+

Document

+

Converting your node(s) to document view centers them in the panel, styles the nodes like paragraphs, and supports basic Markdown formatting.

+

Note: document view is a one-way conversion. Document nodes can't be converted back to List (outline) view.

+

Create a Document

+

With your cursor on the parent node of the node(s) you want to convert to a document, open the Command Palette and select Make Document.

+

Markdown formatting

+
Text styles
+
*italic* or _italic_
+**bold**
+~~strikethrough~~
+
+
Lists
+
- unordered lists
+* with any of
++ these characters
+
+1. ordered
+2. lists
+
+

Table

+

Useful for a set of nodes with common fields. Node fields will become columns.

+

Use Table View

+

With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select View As Table.

+

To revert back to List (outline) view, select the parent node and choose "View as List" from the Command Palette.

+

Tabs

+

The tab view tidies up a common group of nodes, allowing you to view one tab at a time.

+

Use Tabs View

+

With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select View As Tabs.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/08-calendar/index.html b/docs/user/08-calendar/index.html new file mode 100644 index 0000000..a0a51c8 --- /dev/null +++ b/docs/user/08-calendar/index.html @@ -0,0 +1,6 @@ + +Calendar - Treehouse

Documentation

User Guide

Calendar

+

The Calendar is a default node that is automatically generated for every workspace. Nodes inside the calendar are grouped by date, week, then year.

+

Today

+

The Today shortcut allows you to quickly view the node for Today in your Calendar.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/09-quick-add/index.html b/docs/user/09-quick-add/index.html new file mode 100644 index 0000000..72e71ca --- /dev/null +++ b/docs/user/09-quick-add/index.html @@ -0,0 +1,4 @@ + +Quick Add - Treehouse

Documentation

User Guide

Quick Add

+

Quick Add in the top navigation is a shortcut that opens a modal in which you can jot a quick note. It will be added to today’s date in your Calendar.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/10-command-palette/index.html b/docs/user/10-command-palette/index.html new file mode 100644 index 0000000..c4d0d70 --- /dev/null +++ b/docs/user/10-command-palette/index.html @@ -0,0 +1,4 @@ + +Command Palette - Treehouse

Documentation

User Guide

Command Palette

+

With a node selected, open the command palette (⌘ K) to view all the available actions for that node.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/11-keyboard-shortcuts/index.html b/docs/user/11-keyboard-shortcuts/index.html new file mode 100644 index 0000000..174a19b --- /dev/null +++ b/docs/user/11-keyboard-shortcuts/index.html @@ -0,0 +1,36 @@ + +Keyboard Shortcuts - Treehouse

Documentation

User Guide

Keyboard Shortcuts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
indent
outdent⇧ ↹
move node up⇧ ⌘ ↑
move node down⇧ ⌘ ↓
delete node⇧ ⌘ ⌫
add or remove checkbox⌘ ↵
mark checkbox as done⌘ ↵
open command palette⌘ K

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/12-css-theming/index.html b/docs/user/12-css-theming/index.html new file mode 100644 index 0000000..4182fe8 --- /dev/null +++ b/docs/user/12-css-theming/index.html @@ -0,0 +1,104 @@ + +CSS Theming - Treehouse

Documentation

User Guide

CSS Theming

+

You can create your own custom theme for Treehouse using our built-in variables a.k.a. custom properties.

+

Create a theme

+
    +
  1. Add a top level folder called "ext" to your treehouse.sh repository
  2. +
  3. Create a CSS file inside the ext folder
  4. +
  5. Use the format below to populate the variables with hex code values. Tip: Create color variables inside the root block to reuse color styles between custom properties.
  6. +
+
:root {
+  --font: 'Font name';
+
+  --color-primary: #hex;
+
+  --color-background: #hex;
+  --color-background-sidebar: #hex;
+
+  --color-icon: #hex;
+  --color-icon-secondary: #hex;
+
+  --color-nav-label: #hex;
+
+  --color-text: #hex;
+  --color-text-placeholder: #hex;
+  --color-text-secondary: #hex;
+
+  --color-highlight: #hex;
+
+  --color-node-handle: #hex;
+  --color-node-handle-secondary: #hex;
+
+  --color-outline: #hex;
+  --color-outline-secondary: #hex;
+}
+
+

Managing multiple CSS files

+

If you have multiple CSS files you want to swap between, append ".disabled" to the end of the unused CSS filename(s).

+

Variable Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescription
--fontGlobal font definition. Change the font itself but not sizes or styles with this.
--color-primaryBackground color of primary button
--color-backgroundBackground color of main panels, menus, and modals
--color-background-sidebarBackground color of sidebar navigation
--color-iconHigh contrast color used for primary icons. For example: icons in the top navigation
--color-icon-secondaryLow-contrast color used for secondary icons
--color-nav-labelUsed for top and sidebar navigation labels
--color-textDefault text color used for body text, navigation, and primary icons
--color-text-placeholderLower-contrast color used for placeholder text in inputs
--color-text-secondaryLower-contrast color used for secondary text
--color-highlightLowest-contrast color to subtly highlight selected item in the menu, search, and command palette
--color-node-handleBullet color for nodes (a.k.a. the node handle)
--color-node-handle-secondaryLower-contrast accent color on node handles. For instance, the outer filled circle on a node indicating collapsed children.
--color-outlineHigh contrast border color on pop-over containers like modals and menus.
--color-outline-secondaryLower contrast border color where less extreme contrast is needed, such as the divider between panels and the navigation.
+

Fonts

+

To use a non-system font, you may import a font into your CSS file using either the @import method or the @font-face method.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/13-backend-extensions/index.html b/docs/user/13-backend-extensions/index.html new file mode 100644 index 0000000..f5fb46f --- /dev/null +++ b/docs/user/13-backend-extensions/index.html @@ -0,0 +1,12 @@ + +Backend Extensions - Treehouse

Documentation

User Guide

Backend Extensions

+

Backends can change or extend how a Treehouse application behaves and are exposed to the frontend via adapters.

+

Workspace Storage

+

The Treehouse frontend uses its backend adapter to store the state of your workspace into a JSON document. The backend can decide where and how that JSON document is stored. For example, the Browser backend adapter will store the JSON into localStorage.

+

User Authentication

+

An optional capability of the frontend is to know whether a particular user is authenticated. You typically want user authentication for cloud or web-based deployments, but not necessarily for a local desktop app.

+

Search Indexing

+

Out of the box, Treehouse will index your workspace for full-text search using Minisearch, +which will be good enough for many cases. However, a backend adapter can choose to hook into the index and searching +so that you could have a more powerful search index such as ElasticSearch.

+

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/docs/user/index.html b/docs/user/index.html new file mode 100644 index 0000000..2501229 --- /dev/null +++ b/docs/user/index.html @@ -0,0 +1,260 @@ + +Docs - Treehouse

Documentation

User Guide

What is Treehouse?

+

Treehouse is an outline editor, which you can think of as a nested bulleted list. Each bullet item is called a node. You can use the nodes to create nested layers of folders and notes.

Data Storage

+

Localstorage

+

By default, data is stored in your browser’s local storage. That means that the data is linked to your specific browser and device. If you clear your browser cache, the data will be wiped.

+

GitHub

+

If you choose to log in with GitHub, we’ll create a repository and store your data there.

+

To store your workspace, we will create a public repository called .treehouse.sh if it doesn’t already exist. If you want to make the repository private, you can do so in GitHub.

+

To switch back to the local storage backend, log out from the Options menu.

+

Multiple sessions

+

If you log in to Treehouse from multiple devices, the most recent device will have edit and save access. Other sessions will be prompted to refresh the page; if/when you do so, that session becomes the new active session.

Nodes

+

Your Workspace is your top level node, which all other nodes are nested under.

+

Learn how to manage and edit your nodes.

+

Add

+

You can add a new node in a few ways.

+
    +
  1. Hit ENTER on your keyboard and start typing.
  2. +
  3. Click into the blank area next to the plus symbol and start typing.
  4. +
+

Indent

+
    +
  • Indent a node using TAB ↹
  • +
  • Outdent a node using SHIFT + TAB ⇧ ↹
  • +
+

Move

+
    +
  • Move a node up with SHIFT + COMMAND + UP ARROW ⇧ ⌘ ↑
  • +
  • Move a node down with SHIFT + COMMAND + DOWN ARROW ⇧
  • +
+

Expand or collapse a node

+

If a node bullet has an outline around it, that’s an indication that it has nested content that is currently hidden. Click the node to expand it.

+

Click an expanded node once to collapse it.

+

Node menu

+

When you hover over a node, you’ll see a menu to the left of the node bullet. Click it to access node options, such as indent/outdent, open in a panel, etc.

+

Node formatting

+

Currently, all nodes are formatted as plain text. You can, however, add a checkbox to a node. With a node selected, open the command palette (⌘ K) and select "Add checkbox".

+

View

+

Double click a node to zoom in.

+

Side-by-side (Panel) view

+

To view two nodes side-by-side:

+

Open the node you want to be in the righthand panel. From its menu, choose “Open in New Panel”.

+

You can close or expand either panel to return to a single panel view.

Fields

+

A field is a node that can store structured information. Fields provide your data with structure, and allow for special search syntax (see Smart Nodes).

+

Create a field

+

To add a field to a node:

+
    +
  1. Indent underneath the node you want to contain the field, and type the field name
  2. +
  3. Use Command/Control + K to open the command palette, and choose "Create field"
  4. +
  5. Add your field value in the value section
  6. +

Smart Nodes

+

Smart Nodes allow you to create an auto-updating search of all the nodes in your workspace. Simply type your search term, or use the format "fieldname:valuename" to filter specifically by field values. The Smart Node will update automatically as your node content changes. This is a simple but super powerful way to view your data in new configurations.

+

To create a Smart Node:

+
    +
  1. Create a new node where you want your Smart Node, and type your search value
  2. +
  3. Use Command/Control + K to open the command palette, and choose "Create Smart Node"
  4. +
+

Tips for using Smart Nodes

+
    +
  • You can filter on multiple fieldname values (using AND, not OR) like so: "fieldname:valuename fieldname:valuename" etc.
  • +
  • If your fieldname has spaces, put quotes around it (fieldname:"Value name")
  • +
  • Search terms are case-insensitive
  • +

Tags

+

Tags are versatile metadata. Here are some of the ways you can use them:

+
    +
  • Conduct a keyword search in the searchbar
  • +
  • Use in Smart Nodes to create custom views
  • +
  • Use to add a template to a node
  • +
+

Create a tag

+

On any node, with your cursor at the end of the text, type "#" then the tag name to create a tag. Any tags you have already created will show up in an autocomplete list when you type "#".

+

Delete a tag

+

Click on the tag and press Backspace on your keyboard to delete it.

Templates

+

Templates allow you to add a set of predefined fields and other child nodes to a node simply by adding a tag to that node.

+

Create and use a template

+

In this example, we'll create a template called "book" with child fields Author and Release Date.

+
    +
  1. Wherever you want to store your template, type "book" in the node.
  2. +
  3. Add child fields Author and Release Date, and leave the value fields empty. (To turn a regular node into a Field: Command Palette > Create field)
  4. +
  5. With your cursor in the parent "book" node, open the Command Palette (Command/Control+K) and choose Make Template.
  6. +
  7. Create a list of books wherever you'd like, if you haven't already. With your cursor on one if the individual books, type "#book" and hit ENTER to create the tag.
  8. +
  9. Check out one of your book nodes—it should have the child fields Author and Release Date that you set up in your template.
  10. +

Views

+

You can view your data in many different ways using commands in the Command Palette.

+

Document

+

Converting your node(s) to document view centers them in the panel, styles the nodes like paragraphs, and supports basic Markdown formatting.

+

Note: document view is a one-way conversion. Document nodes can't be converted back to List (outline) view.

+

Create a Document

+

With your cursor on the parent node of the node(s) you want to convert to a document, open the Command Palette and select Make Document.

+

Markdown formatting

+
Text styles
+
*italic* or _italic_
+**bold**
+~~strikethrough~~
+
+
Lists
+
- unordered lists
+* with any of
++ these characters
+
+1. ordered
+2. lists
+
+

Table

+

Useful for a set of nodes with common fields. Node fields will become columns.

+

Use Table View

+

With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select View As Table.

+

To revert back to List (outline) view, select the parent node and choose "View as List" from the Command Palette.

+

Tabs

+

The tab view tidies up a common group of nodes, allowing you to view one tab at a time.

+

Use Tabs View

+

With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select View As Tabs.

Calendar

+

The Calendar is a default node that is automatically generated for every workspace. Nodes inside the calendar are grouped by date, week, then year.

+

Today

+

The Today shortcut allows you to quickly view the node for Today in your Calendar.

Quick Add

+

Quick Add in the top navigation is a shortcut that opens a modal in which you can jot a quick note. It will be added to today’s date in your Calendar.

Command Palette

+

With a node selected, open the command palette (⌘ K) to view all the available actions for that node.

Keyboard Shortcuts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
indent
outdent⇧ ↹
move node up⇧ ⌘ ↑
move node down⇧ ⌘ ↓
delete node⇧ ⌘ ⌫
add or remove checkbox⌘ ↵
mark checkbox as done⌘ ↵
open command palette⌘ K

CSS Theming

+

You can create your own custom theme for Treehouse using our built-in variables a.k.a. custom properties.

+

Create a theme

+
    +
  1. Add a top level folder called "ext" to your treehouse.sh repository
  2. +
  3. Create a CSS file inside the ext folder
  4. +
  5. Use the format below to populate the variables with hex code values. Tip: Create color variables inside the root block to reuse color styles between custom properties.
  6. +
+
:root {
+  --font: 'Font name';
+
+  --color-primary: #hex;
+
+  --color-background: #hex;
+  --color-background-sidebar: #hex;
+
+  --color-icon: #hex;
+  --color-icon-secondary: #hex;
+
+  --color-nav-label: #hex;
+
+  --color-text: #hex;
+  --color-text-placeholder: #hex;
+  --color-text-secondary: #hex;
+
+  --color-highlight: #hex;
+
+  --color-node-handle: #hex;
+  --color-node-handle-secondary: #hex;
+
+  --color-outline: #hex;
+  --color-outline-secondary: #hex;
+}
+
+

Managing multiple CSS files

+

If you have multiple CSS files you want to swap between, append ".disabled" to the end of the unused CSS filename(s).

+

Variable Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescription
--fontGlobal font definition. Change the font itself but not sizes or styles with this.
--color-primaryBackground color of primary button
--color-backgroundBackground color of main panels, menus, and modals
--color-background-sidebarBackground color of sidebar navigation
--color-iconHigh contrast color used for primary icons. For example: icons in the top navigation
--color-icon-secondaryLow-contrast color used for secondary icons
--color-nav-labelUsed for top and sidebar navigation labels
--color-textDefault text color used for body text, navigation, and primary icons
--color-text-placeholderLower-contrast color used for placeholder text in inputs
--color-text-secondaryLower-contrast color used for secondary text
--color-highlightLowest-contrast color to subtly highlight selected item in the menu, search, and command palette
--color-node-handleBullet color for nodes (a.k.a. the node handle)
--color-node-handle-secondaryLower-contrast accent color on node handles. For instance, the outer filled circle on a node indicating collapsed children.
--color-outlineHigh contrast border color on pop-over containers like modals and menus.
--color-outline-secondaryLower contrast border color where less extreme contrast is needed, such as the divider between panels and the navigation.
+

Fonts

+

To use a non-system font, you may import a font into your CSS file using either the @import method or the @font-face method.

Backend Extensions

+

Backends can change or extend how a Treehouse application behaves and are exposed to the frontend via adapters.

+

Workspace Storage

+

The Treehouse frontend uses its backend adapter to store the state of your workspace into a JSON document. The backend can decide where and how that JSON document is stored. For example, the Browser backend adapter will store the JSON into localStorage.

+

User Authentication

+

An optional capability of the frontend is to know whether a particular user is authenticated. You typically want user authentication for cloud or web-based deployments, but not necessarily for a local desktop app.

+

Search Indexing

+

Out of the box, Treehouse will index your workspace for full-text search using Minisearch, +which will be good enough for many cases. However, a backend adapter can choose to hook into the index and searching +so that you could have a more powerful search index such as ElasticSearch.

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

\ No newline at end of file diff --git a/web/static/halftone_green.png b/halftone_green.png similarity index 100% rename from web/static/halftone_green.png rename to halftone_green.png diff --git a/web/static/halftone_white.png b/halftone_white.png similarity index 100% rename from web/static/halftone_white.png rename to halftone_white.png diff --git a/web/static/halftone_white_15.png b/halftone_white_15.png similarity index 100% rename from web/static/halftone_white_15.png rename to halftone_white_15.png diff --git a/web/static/halftone_white_50.png b/halftone_white_50.png similarity index 100% rename from web/static/halftone_white_50.png rename to halftone_white_50.png diff --git a/web/static/icon.png b/icon.png similarity index 100% rename from web/static/icon.png rename to icon.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..307e21f --- /dev/null +++ b/index.html @@ -0,0 +1,2 @@ + +Treehouse: Note-taking Frontend

A lightweight note-taking tool to make your own

An open source note-taking frontend to extend and customize. Bring your own backend or configure a built-in backend to get started.

 
Treehouse UI preview

Treehouse is in early development. View release 0.7.0 →

Simple note-taking out of the box

Fields and Smart Nodes

Add metadata, then create a custom search view.

Quick Add and Daily Notes

Quickly add notes organized by date.

Full-Text Search

Quick, intuitive search that can be extended.

Quick development setup

The project is lightweight and uses Deno as the JavaScript toolchain. Get up and running in seconds.

1. Clone the repository

Open in GitHub or use the command below to clone it with Git.

git clone https://github.com/treehousedev/treehouse.git

2. Start the dev server

Run locally with the built-in development server.

deno task serve

Bring your own backend

We include several backend options, but you can build your own for different scenarios and requirements.

Storage

Keep your data where you want, how you want it. Use the filesystem or put it in the cloud.

Custom search

If our full-text search isn't powerful enough, plug-in your own indexer. Pull in results from external tools if you want!

Authentication

Optional pluggable authentication lets you use cloud platforms or integrate with your company authentication.

Learn about the project

Read our blog and devlog series to follow along with the development process.

Stay in touch

Sign up for our mailing list to receive updates on the project.

We don't share your email.

Interested in building a specialized note-taking tool? Work with us.

\ No newline at end of file diff --git a/web/static/lib/0.2.0/treehouse.min.js b/lib/0.2.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.2.0/treehouse.min.js rename to lib/0.2.0/treehouse.min.js diff --git a/web/static/lib/0.3.0/treehouse.min.js b/lib/0.3.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.3.0/treehouse.min.js rename to lib/0.3.0/treehouse.min.js diff --git a/web/static/lib/0.3.0/treehouse.min.js.map b/lib/0.3.0/treehouse.min.js.map similarity index 100% rename from web/static/lib/0.3.0/treehouse.min.js.map rename to lib/0.3.0/treehouse.min.js.map diff --git a/web/static/lib/0.4.0/treehouse.min.js b/lib/0.4.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.4.0/treehouse.min.js rename to lib/0.4.0/treehouse.min.js diff --git a/web/static/lib/0.4.0/treehouse.min.js.map b/lib/0.4.0/treehouse.min.js.map similarity index 100% rename from web/static/lib/0.4.0/treehouse.min.js.map rename to lib/0.4.0/treehouse.min.js.map diff --git a/web/static/lib/0.5.0/treehouse.min.js b/lib/0.5.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.5.0/treehouse.min.js rename to lib/0.5.0/treehouse.min.js diff --git a/web/static/lib/0.5.0/treehouse.min.js.map b/lib/0.5.0/treehouse.min.js.map similarity index 100% rename from web/static/lib/0.5.0/treehouse.min.js.map rename to lib/0.5.0/treehouse.min.js.map diff --git a/web/static/lib/0.6.0/treehouse.min.js b/lib/0.6.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.6.0/treehouse.min.js rename to lib/0.6.0/treehouse.min.js diff --git a/web/static/lib/0.6.0/treehouse.min.js.map b/lib/0.6.0/treehouse.min.js.map similarity index 100% rename from web/static/lib/0.6.0/treehouse.min.js.map rename to lib/0.6.0/treehouse.min.js.map diff --git a/web/static/lib/0.7.0/treehouse.min.js b/lib/0.7.0/treehouse.min.js similarity index 100% rename from web/static/lib/0.7.0/treehouse.min.js rename to lib/0.7.0/treehouse.min.js diff --git a/web/static/lib/0.7.0/treehouse.min.js.map b/lib/0.7.0/treehouse.min.js.map similarity index 100% rename from web/static/lib/0.7.0/treehouse.min.js.map rename to lib/0.7.0/treehouse.min.js.map diff --git a/lib/action/commands.ts b/lib/action/commands.ts deleted file mode 100644 index 029c95b..0000000 --- a/lib/action/commands.ts +++ /dev/null @@ -1,39 +0,0 @@ - -export interface Command { - id: string; - title?: string; - category?: string; - icon?: string; - hidden?: boolean; - action: Function; - when?: Function; -} - -export class CommandRegistry { - commands: {[index: string]: Command} - - constructor() { - this.commands = {}; - } - - registerCommand(cmd: Command) { - this.commands[cmd.id] = cmd; - } - - canExecuteCommand(id: string, ...rest: any): boolean { - if (this.commands[id]) { - if (this.commands[id].when && !this.commands[id].when(...rest)) { - return false; - } - return true; - } - return false; - } - - executeCommand(id: string, ...rest: any): Promise { - return new Promise((resolve) => { - const ret = this.commands[id].action(...rest); - resolve(ret); - }); - } -} \ No newline at end of file diff --git a/lib/action/commands_test.ts b/lib/action/commands_test.ts deleted file mode 100644 index d2b161a..0000000 --- a/lib/action/commands_test.ts +++ /dev/null @@ -1,16 +0,0 @@ - -import { assertEquals } from "https://deno.land/std@0.173.0/testing/asserts.ts"; -import { CommandRegistry } from "./commands.ts"; - -Deno.test("command registration and execution", async () => { - const r = new CommandRegistry(); - r.registerCommand({ - id: "test", - action: async (msg: string) => { - return msg - } - }) - const ret = await r.executeCommand("test", "Hello world"); - - assertEquals(ret, "Hello world"); -}); \ No newline at end of file diff --git a/lib/action/keybinds.ts b/lib/action/keybinds.ts deleted file mode 100644 index cb6284c..0000000 --- a/lib/action/keybinds.ts +++ /dev/null @@ -1,83 +0,0 @@ - -const isMac = (navigator.userAgent.toLowerCase().indexOf("mac") !== -1); - -export function bindingSymbols(key?: string): string[] { - if (!key) return []; - const symbols = { - "backspace": "⌫", - "shift": "⇧", - "meta": "⌘", - "tab": "↹", - "ctrl": "⌃", - "arrowup": "↑", - "arrowdown": "↓", - "arrowleft": "←", - "arrowright": "→", - "enter": "⏎" - }; - const keys = key.toLowerCase().split("+"); - return keys.map(filterKeyForNonMacMeta).map(k => (Object.keys(symbols).includes(k)) ? symbols[k] : k); -} - -// if key is meta and not on a mac, change it to ctrl, -// otherwise return the key as is -function filterKeyForNonMacMeta(key: string): string { - return (!isMac && key === "meta") ? "ctrl": key; -} - -export interface Binding { - command: string; - key: string; - //when - //args -} - -export class KeyBindings { - bindings: Binding[]; - - constructor() { - this.bindings = []; - } - - registerBinding(binding: Binding) { - this.bindings.push(binding); - } - - getBinding(commandId: string): Binding|null { - for (const b of this.bindings) { - if (b.command === commandId) { - return b; - } - } - return null; - } - - evaluateEvent(event: KeyboardEvent): Binding|null { - bindings: for (const b of this.bindings) { - let modifiers = b.key.toLowerCase().split("+"); - let key = modifiers.pop(); - if (key !== event.key.toLowerCase()) { - continue; - } - for (const checkMod of ["shift", "ctrl", "alt", "meta"]) { - let hasMod = modifiers.includes(checkMod); - if (!isMac) { - if (checkMod === "meta") continue; - if (checkMod === "ctrl") { - hasMod = modifiers.includes("meta") || modifiers.includes("ctrl"); - } - } - // @ts-ignore - const modState = event[`${filterKeyForNonMacMeta(checkMod)}Key`]; - if (!modState && hasMod) { - continue bindings; - } - if (modState && !hasMod) { - continue bindings; - } - } - return b; - } - return null; - } -} \ No newline at end of file diff --git a/lib/action/keybinds_test.ts b/lib/action/keybinds_test.ts deleted file mode 100644 index 1c71aa3..0000000 --- a/lib/action/keybinds_test.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import { assertEquals } from "https://deno.land/std@0.173.0/testing/asserts.ts"; -import { KeyBindings } from "./keybinds.ts"; - -Deno.test("binding registration", async () => { - const bindings = new KeyBindings(); - bindings.registerBinding({ - command: "test", - key: "shift+a" - }) - const ret = bindings.getBinding("test"); - - assertEquals(ret?.key, "shift+a"); -}); \ No newline at end of file diff --git a/lib/action/menus.ts b/lib/action/menus.ts deleted file mode 100644 index 4803e47..0000000 --- a/lib/action/menus.ts +++ /dev/null @@ -1,23 +0,0 @@ - -export interface MenuItem { - command?: string; - //alt?: string; - when?: Function; - title?: Function; - onclick?: Function; - disabled?: boolean; - //group - //submenu -} - -export class MenuRegistry { - menus: {[index: string]: MenuItem[]}; - - constructor() { - this.menus = {}; - } - - registerMenu(id: string, items: MenuItem[]) { - this.menus[id] = items; - } -} \ No newline at end of file diff --git a/lib/action/menus_test.ts b/lib/action/menus_test.ts deleted file mode 100644 index 6632dce..0000000 --- a/lib/action/menus_test.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { assertEquals } from "https://deno.land/std@0.173.0/testing/asserts.ts"; -import { MenuRegistry } from "./menus.ts"; - -Deno.test("menu registration", async () => { - const menus = new MenuRegistry(); - menus.registerMenu("test/test", [{ - command: "test" - }]); - const ret = menus.menus["test/test"]; - - assertEquals(ret[0].command, "test"); -}); \ No newline at end of file diff --git a/lib/action/mod.ts b/lib/action/mod.ts deleted file mode 100644 index cfe815d..0000000 --- a/lib/action/mod.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * A module for modeling user actions around commands. - * - * Primarily providing a command registry, this module also - * provides a registry and utilities for implementing keybindings - * and menus. - * - * @module - */ \ No newline at end of file diff --git a/lib/backend/browser.ts b/lib/backend/browser.ts deleted file mode 100644 index bef0e36..0000000 --- a/lib/backend/browser.ts +++ /dev/null @@ -1,101 +0,0 @@ -import {RawNode} from "../model/mod.ts"; -import { SearchIndex, FileStore, ChangeNotifier } from "./mod.ts"; - -export class BrowserBackend { - auth: null; - index: SearchIndex; - files: FileStore; - changes?: ChangeNotifier; - - constructor() { - this.auth = null; - this.files = new LocalStorageFileStore(); - if (window.MiniSearch) { - this.index = new SearchIndex_MiniSearch(); - } else { - this.index = new SearchIndex_Dumb(); - } - this.changes = { - registerNotifier(cb: (nodeIDs: string[]) => void) { - window.reloadNodes = cb; - } - }; - } -} - -export class SearchIndex_MiniSearch { - indexer: any; // MiniSearch - - constructor() { - this.indexer = new MiniSearch({ - idField: "ID", - fields: ['ID', 'Name', 'Value', 'Value.markdown'], // fields to index for full-text search - storeFields: ['ID'], // fields to return with search results - extractField: (document, fieldName) => { - return fieldName.split('.').reduce((doc, key) => doc && doc[key], document); - } - }); - } - - index(node: RawNode) { - if (this.indexer.has(node.ID)) { - this.indexer.replace(node); - } else { - this.indexer.add(node); - } - } - - remove(id: string) { - try { - this.indexer.discard(id); - } catch {} - } - - search(query: string): string[] { - const suggested = this.indexer.autoSuggest(query); - if (suggested.length === 0) return []; - return this.indexer.search(suggested[0].suggestion, { - prefix: true, - combineWith: 'AND', - }).map(doc => doc.ID); - } -} - - -export class SearchIndex_Dumb { - nodes: Record; - - constructor() { - this.nodes = {}; - } - - index(node: RawNode) { - this.nodes[node.ID] = node.Name; - } - - remove(id: string) { - delete this.nodes[id]; - } - - search(query: string): string[] { - const results: string[] = []; - for (const id in this.nodes) { - if (this.nodes[id].includes(query)) { - results.push(id); - } - } - return results; - } -} - - - -export class LocalStorageFileStore { - async readFile(path: string): Promise { - return localStorage.getItem(`treehouse:${path}`); - } - - async writeFile(path: string, contents: string) { - localStorage.setItem(`treehouse:${path}`, contents); - } -} \ No newline at end of file diff --git a/lib/backend/github.ts b/lib/backend/github.ts deleted file mode 100644 index 4f554f4..0000000 --- a/lib/backend/github.ts +++ /dev/null @@ -1,320 +0,0 @@ - -import { Authenticator, SearchIndex, FileStore, ChangeNotifier } from "./mod.ts"; -import { BrowserBackend } from "./browser.ts"; -import { encode, decode } from 'https://cdn.jsdelivr.net/npm/js-base64@3.7.5/base64.mjs'; - -export interface Options { - domain: string; // domain used with username subdomain to produce repo name - checkDomain: boolean; // redirect to user domain if it is not current location - authFallbackURL?: string; // URL to redirect to if auth fails - privateRepo?: boolean; // if the user workspace repo should be created private -} - -export class GitHubBackend { - auth: Authenticator; - - index: SearchIndex; - files: FileStore; - changes?: ChangeNotifier; - - loginURL: string; - clientFactory: any; // Octokit class - client: any; // Octokit instance - user: User|null; - shas: Record; // path => sha - - opts: Options; - - constructor(loginURL: string, octokit: any, opts?: Options) { - this.loginURL = loginURL; - this.clientFactory = octokit; - this.auth = this; - this.shas = {}; - - this.opts = Object.assign({ - domain: "treehouse.sh", - checkDomain: false, - privateRepo: false - }, opts || {}); - - // fallbacks - const localbackend = new BrowserBackend(); - this.index = localbackend.index; - this.files = localbackend.files; - - - } - - get repoName(): string { - return `${this.user?.userID().toLowerCase()}.${this.opts.domain}`; - } - - async initialize() { - // delegate authorize callback to loginURL - const code = new URL(location.href).searchParams.get("code"); - if (code) { - try { - // remove ?code=... from URL - const querystring = location.search.replace(/\bcode=\w+/, "").replace(/\?$/, ""); - history.pushState({}, "", `${location.pathname}${querystring}`); - - const response = await fetch(this.loginURL, { - method: "POST", - mode: "cors", - headers: {"content-type": "application/json"}, - body: JSON.stringify({ code }) - }); - - const result = await response.json(); - if (result.error) { - throw result.error; - } - - localStorage.setItem("treehouse:gh-token", result.token); - - } catch (e: Error) { - this.reset(); - console.error(e); - return; - } - } - - // capture access token if provided directly - const token = new URL(location.href).searchParams.get("access_token"); - if (token) { - try { - // remove ?access_token=... from URL - const querystring = location.search.replace(/\baccess_token=\w+/, "").replace(/\?$/, ""); - history.pushState({}, "", `${location.pathname}${querystring}`); - - localStorage.setItem("treehouse:gh-token", token); - } catch (e: Error) { - this.reset(); - console.error(e); - return; - } - } - - try { - await this.authenticate(); - if (!this.user) { - throw "authentication failed"; - } - } catch (e: Error) { - console.error(e); - if (this.opts.authFallbackURL) { - location.href = this.opts.authFallbackURL; - } - return; - } - - // check domain if set to - if (this.opts.checkDomain && this.repoName !== location.hostname.toLowerCase()) { - location.hostname = this.repoName; - return; - } - - // check if repo exists - try { - await this.client.rest.repos.get({ - owner: this.user.userID(), - repo: this.repoName - }); - } catch (e: Error) { - if (e.message !== "Not Found") { - throw e; - } - // create if not - console.log("Creating repository..."); - const resp = await this.client.rest.repos.createForAuthenticatedUser({name: this.repoName, private: this.opts.privateRepo}); - if (resp.status !== 201) { - console.error(resp); - return; - } - } - - // check for workspace.json now - try { - await this.client.rest.repos.getContent({ - owner: this.user.userID(), - repo: this.repoName, - path: "workspace.json" - }); - } catch (e: Error) { - if (e.name !== "HttpError") { - throw e; - } - // create empty if not - console.log("Creating workspace.json..."); - const resp = await this.client.rest.repos.createOrUpdateFileContents({ - owner: this.user.userID(), - repo: this.repoName, - path: "workspace.json", - message: "initial commit", - content: btoa(JSON.stringify([])) - }); - if (resp.status !== 201) { - console.error(resp); - return; - } - } - - // satisfy filestore interface with methods on this - this.files = this; - - // create and regularly check a session lockfile - const sessID = uniqueID(); - await this.readFile("treehouse.lock"); - await this.writeFile("treehouse.lock", sessID); - const lockCheck = setInterval(async () => { - const lockFile = await this.readFile("treehouse.lock"); - if (lockFile !== sessID) { - clearInterval(lockCheck); - document.dispatchEvent(new CustomEvent("BackendError")); - console.warn("lock stolen!"); - } - }, 5000); - } - - async loadExtensions() { - try { - const dirCheck = await this.client.rest.repos.getContent({ - owner: this.user?.userID(), - repo: this.repoName, - path: "", - random: Math.random().toString(36).substring(2) - }); - if (dirCheck.data.find(o => o.type === "dir" && o.name === "ext")) { - const dirList = await this.client.rest.repos.getContent({ - owner: this.user?.userID(), - repo: this.repoName, - path: "ext", - random: Math.random().toString(36).substring(2) - }); - for (const file of dirList.data) { - if (file.name.endsWith(".css")) { - // Load CSS - const resp = await this.client.rest.repos.getContent({ - owner: this.user?.userID(), - repo: this.repoName, - path: file.path, - random: Math.random().toString(36).substring(2) - }); - const css = document.createElement("link"); - css.setAttribute("href", `data:text/css;charset=utf-8;base64,${resp.data.content}`); - css.setAttribute("rel", "stylesheet"); - css.setAttribute("type", "text/css"); - document.head.appendChild(css); - } else if (file.name.endsWith(".js")) { - // Load JavaScript - const resp = await this.client.rest.repos.getContent({ - owner: this.user?.userID(), - repo: this.repoName, - path: file.path, - random: Math.random().toString(36).substring(2) - }); - const js = document.createElement("script"); - js.setAttribute("type", "module"); - js.setAttribute("src", `data:text/javascript;charset=utf-8;base64,${resp.data.content}`); - document.head.appendChild(js); - } - } - } - - } catch (e: Error) {} - - } - - async authenticate() { - const token = localStorage.getItem("treehouse:gh-token"); - if (!token) { - return; - } - - this.client = new this.clientFactory({auth: token}); - const resp = await this.client.rest.users.getAuthenticated(); - if (!resp || resp.error) { - return; - } - this.user = new User(resp.data); - - if(m)m.redraw(); - } - - currentUser(): User|null { - return this.user; - } - - login() { - location.assign(this.loginURL); - } - - reset() { - localStorage.removeItem("treehouse:gh-token"); - this.user = null; - - if(m)m.redraw(); - } - - logout() { - this.reset(); - location.reload(); - } - - - async readFile(path: string): Promise { - try { - const resp = await this.client.rest.repos.getContent({ - owner: this.user?.userID(), - repo: this.repoName, - path: path, - random: Math.random().toString(36).substring(2) - }); - this.shas[path] = resp.data.sha; - return decode(resp.data.content); - } catch (e: Error) { - if (e.name !== "HttpError") { - console.error(e); - } - return null; - } - } - - async writeFile(path: string, contents: string) { - const resp = await this.client.rest.repos.createOrUpdateFileContents({ - owner: this.user?.userID(), - repo: this.repoName, - path: path, - message: "autosave", - content: encode(contents), - sha: this.shas[path] - }); - this.shas[path] = resp.data.content.sha; - } -} - -export class User { - user: any; // github user object - - constructor(user: any) { - this.user = user; - } - - userID(): string { - return this.user.login; - } - - displayName(): string { - return this.user.name; - } - - avatarURL(): string { - return this.user.avatar_url; - } -} - -function uniqueID() { - const dateString = Date.now().toString(36); - const randomness = Math.random().toString(36).substring(2); - return dateString + randomness; -}; \ No newline at end of file diff --git a/lib/backend/mod.ts b/lib/backend/mod.ts deleted file mode 100644 index 6c111ee..0000000 --- a/lib/backend/mod.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Backend provides the APIs to implement a backend adapter as well - * as several built-in backend adapters. These are instantiated and - * passed to `setup` for a working SPA. - * - * @module - */ -import {RawNode} from "../model/mod.ts"; - -/** - * Backend is the adapter object API to be implemented for a working backend. - * Typically these fields are set to `this` and the different APIs are - * implemented on the same object. - */ -export interface Backend { - auth: Authenticator|null; - index: SearchIndex; - files: FileStore; - changes?: ChangeNotifier; -} - - -export interface Authenticator { - login(); - logout(); - currentUser(): User|null; -} - -export interface User { - userID(): string; - displayName(): string; - avatarURL(): string; -} - -export interface SearchIndex { - index(node: RawNode); - remove(id: string); - search(query: string): string[]; -} - -export interface FileStore { - readFile(path: string): Promise; - writeFile(path: string, contents: string): Promise; -} - -export interface ChangeNotifier { - registerNotifier(cb: (nodeIDs: string[]) => void); -} diff --git a/lib/com/checkbox.tsx b/lib/com/checkbox.tsx deleted file mode 100644 index d2a9e17..0000000 --- a/lib/com/checkbox.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -@component -export class Checkbox { - checked: boolean; - - constructor() { - this.checked = false; - } - - beforeEditor() { - return CheckboxEditor; - } -} - -const CheckboxEditor = { - view({attrs: {node}}) { - const toggleCheckbox = (e) => { - const checkbox = node.getComponent(Checkbox); - checkbox.checked = !checkbox.checked; - node.changed(); - } - return - } -} \ No newline at end of file diff --git a/lib/com/clock.tsx b/lib/com/clock.tsx deleted file mode 100644 index 2d1a43b..0000000 --- a/lib/com/clock.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; -import { Workbench, Context } from "../workbench/mod.ts"; - -@component -export class Clock { - startedAt?: Date; - log: Date[][]; - showLog: boolean; - - component?: Node; - object?: Node; - - constructor() { - this.log = []; - this.showLog = false; - } - - onAttach(node: Node) { - this.component = node; - this.object = node.parent; - } - - - fromJSON(obj: any) { - if (obj.startedAt) { - this.startedAt = new Date(obj.startedAt); - } - this.log = (obj.log||[]).map(entry => [new Date(entry[0]), new Date(entry[1])]); - this.showLog = obj.showLog; - } - - toJSON(key: string): any { - return { - startedAt: this.startedAt, - log: this.log, - showLog: this.showLog - }; - } - - localTotal(): number { - return this.log.map(this.entryDuration).reduce((acc, val) => acc+val, 0); - } - - grandTotal(): number { - let total = this.localTotal(); - if (this.object) { - this.object.children.forEach(child => { - if (child.hasComponent(Clock)) { - total += child.getComponent(Clock).grandTotal(); - } - }); - } - return total; - } - - start() { - if (this.startedAt) return; - this.startedAt = new Date(); - } - - stop() { - if (!this.startedAt) return; - let now = new Date(); - let diff = now.getTime() - this.startedAt.getTime(); - if (diff/1000 >= 60) { - // only log if more than a minute - this.log.push([this.startedAt, now]); - } - this.startedAt = undefined; - } - - formatEntry(entry: Date[]): string { - if (entry.length !== 2) return ""; - return `${this.formatDate(entry[0])} - ${new Intl.DateTimeFormat("en", { - timeStyle: "short", - }).format(entry[1])}`; - } - - // duration in seconds - entryDuration(entry: Date[]): number { - const a = entry[0]; - const b = entry[1] || new Date(); - return (b.getTime() - a.getTime()) / 1000; - } - - formatDate(d?: Date): string { - if (!d) { - return ""; - } - return new Intl.DateTimeFormat("en", { - dateStyle: "short", - timeStyle: "short", - }).format(d); - } - - formatDuration(seconds: number): string { - let dur = seconds / 60; - let min = Math.floor(dur % 60); - dur = dur / 60; - let hrs = Math.floor(dur % 60); - return `${hrs}:${min.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false})}`; - } - - afterEditor() { - return ClockBadge; - } - - belowEditor() { - return ClockLog; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "stop-clock", - title: "Stop clock", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - if (!ctx.node.hasComponent(Clock)) { - const clock = new Clock(); - ctx.node.addComponent(clock); - } - ctx.node.getComponent(Clock).stop(); - ctx.node.changed(); - } - }); - workbench.keybindings.registerBinding({command: "stop-clock", key: "meta+o" }); - workbench.commands.registerCommand({ - id: "start-clock", - title: "Start clock", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - if (!ctx.node.hasComponent(Clock)) { - const clock = new Clock(); - ctx.node.addComponent(clock); - } - ctx.node.getComponent(Clock).start(); - ctx.node.changed(); - } - }); - workbench.keybindings.registerBinding({command: "start-clock", key: "meta+i" }); - workbench.commands.registerCommand({ - id: "remove-clock", - title: "Remove clock", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - if (ctx.node.hasComponent(Clock)) return true; - return false; - }, - action: (ctx: Context) => { - ctx.node.removeComponent(Clock); - } - }); - } -} - -const ClockBadge = { - view({attrs: {node}}) { - const clock = node.getComponent(Clock); - const toggleLog = () => { - clock.showLog = !clock.showLog; - node.changed(); - } - if (!clock.showLog && clock.startedAt) { - return ( -
- -
{clock.formatDuration(clock.entryDuration([clock.startedAt]))}
-
- ) - } - return ( -
- -
{clock.formatDuration(clock.grandTotal())}
-
- ) - } -} - -const ClockLog = { - view({attrs: {node}}) { - const clock = node.getComponent(Clock); - if (!clock.showLog) return; - return ( -
-
-
- {clock.startedAt && -
-
{clock.formatDate(clock.startedAt)} - ...
-
- -
{clock.formatDuration(clock.entryDuration([clock.startedAt]))}
-
-
- } - {clock.log.toReversed().map(entry => ( -
-
{clock.formatEntry(entry)}
-
- -
{clock.formatDuration(clock.entryDuration(entry))}
-
-
- ))} -
-
- ) - } -} diff --git a/lib/com/codeblock.tsx b/lib/com/codeblock.tsx deleted file mode 100644 index f908bef..0000000 --- a/lib/com/codeblock.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { component } from "../model/components.ts"; -import { Workbench, Context } from "../workbench/mod.ts"; -export interface CodeExecutor { - // executes the source and returns an output string. - // exceptions in execution should be caught and returned as a string. - execute(source: string, options: ExecuteOptions): Promise; - - canExecute(options: ExecuteOptions): boolean; -} - -export interface ExecuteOptions { - language: string; -} - -// defaultExecutor can be replaced with an external service, etc -export let defaultExecutor: CodeExecutor = { - async execute( - source: string, - options: ExecuteOptions - ): Promise { - if (options.language !== "javascript") { - return `Unsupported language: ${options.language}`; - } - let output = window.eval(source); - //return JSON.stringify(output); - return output.toString(); - }, - - canExecute(options: ExecuteOptions): boolean { - if (options.language === "javascript") { - return true; - } - return false; - }, -}; - -@component -export class CodeBlock { - code: string; - language: string; - detectLanguage: boolean; - - constructor(language?: string) { - this.code = ""; - this.language = ""; - this.detectLanguage = true; - - if (language) { - this.language = language; - this.detectLanguage = false; - } - } - - childrenView() { - return CodeEditorWithOutput; - } - - handleIcon(collapsed: boolean = false): any { - return ( - - - - - ); - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "make-code-block", - title: "Make Code Block", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) - return false; - return true; - }, - action: (ctx: Context, language?: string) => { - const com = new CodeBlock(language); - if (ctx?.node) { - ctx.node.addComponent(com); - ctx.node.changed(); - workbench.workspace.setExpanded( - ctx.path.head, - ctx.path.node, - true - ); - } - }, - }); - } -} - -const CodeEditor = { - oncreate(vnode) { - const { - dom, - attrs: { path }, - } = vnode; - const snippet = path.node.getComponent(CodeBlock); - - //@ts-ignore - dom.jarEditor = new window.CodeJar(dom, (editor) => { - // highlight.js does not trim old tags, - // let's do it by this hack. - editor.textContent = editor.textContent; - //@ts-ignore - window.hljs.highlightBlock(editor); - - if (snippet.detectLanguage) { - //@ts-ignore - snippet.language = window.hljs.highlightAuto(editor.textContent).language || ""; - } - - }); - dom.jarEditor.updateCode(snippet.code); - dom.jarEditor.onUpdate((code) => { - snippet.code = code; - path.node.changed(); - }); - }, - - view({ attrs: { workbench, path } }) { - // this cancels the keydown on the outline node - // so you can use arrow keys normally - const onkeydown = (e) => e.stopPropagation(); - - return
; - }, -}; - -const Output = { - view({ dom, state, attrs: { path } }) { - const snippet = path.node.getComponent(CodeBlock); - - let handleClick = async () => { - state.output = "Running..."; - try { - const res = await defaultExecutor.execute(snippet.code, { - language: snippet.language, - }); - - // Update output using m.prop to ensure it's persistent across re-renders - state.output = res; // Call m.prop with the new value - } catch (error) { - state.output = error.toString(); - } - }; - return ( -
-

{state.output ? "Output: " + state.output : ""}

- -
- ); - }, -}; - -class CodeEditorWithOutput { - view(vnode) { - return [m(CodeEditor, vnode.attrs), m(Output, vnode.attrs)]; - } -} diff --git a/lib/com/description.tsx b/lib/com/description.tsx deleted file mode 100644 index c6303ce..0000000 --- a/lib/com/description.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; -import { objectManaged } from "../model/hooks.ts"; - -@component -export class Description { - text: string; - - constructor() { - this.text = ""; - } - - editor() { - return DescriptionEditor; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "add-description", - title: "Add Description", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return false; - if (ctx.node.hasComponent(Description)) return false; - return true; - }, - action: (ctx: Context, name: string) => { - const desc = new Description(); - ctx.node.addComponent(desc); - ctx.node.changed(); - } - }); - workbench.commands.registerCommand({ - id: "remove-description", - title: "Remove Description", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return false; - if (ctx.node.hasComponent(Description)) return true; - return false; - }, - action: (ctx: Context, name: string) => { - ctx.node.removeComponent(Description); - ctx.node.changed(); - } - }); - } -} - -const DescriptionEditor = { - view({attrs: {node}}) { - const oninput = (e) => { - const desc = node.getComponent(Description); - desc.text = e.target.value; - node.changed(); - } - const onblur = (e) => { - const desc = node.getComponent(Description); - if (desc.text === "") { - node.removeComponent(Description); - } - node.changed(); - } - - return - } -} \ No newline at end of file diff --git a/lib/com/document.tsx b/lib/com/document.tsx deleted file mode 100644 index 82acf52..0000000 --- a/lib/com/document.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -@component -export class Document { - object?: Node; - - constructor() { - } - - onAttach(node: Node) { - this.object = node.parent; - this.object.setAttr("view", "document"); - } - - handleIcon(collapsed: boolean = false): any { - return ( - - {/* {collapsed?:null} */} - - - - - - - ); - } - - toJSON(key: string): any { - return {}; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "make-document", - title: "Make Document", - action: (ctx: Context) => { - if (!ctx.node) return; - const doc = new Document(); - ctx.node.addComponent(doc); - ctx.node.changed(); - workbench.executeCommand("zoom", ctx); - } - }); - } -} diff --git a/lib/com/iframe.tsx b/lib/com/iframe.tsx deleted file mode 100644 index 2c008e4..0000000 --- a/lib/com/iframe.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -@component -export class InlineFrame { - url: string; - - constructor() { - this.url = "https://example.com"; - } - - childrenView() { - return InlineFrameView; - } - handleIcon(collapsed: boolean = false): any { - return ( - - - - - - ); - } - - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "make-iframe", - title: "Make Inline Frame", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - const frame = new InlineFrame(); - if (ctx.node.name.startsWith("http://") || - ctx.node.name.startsWith("https://")) { - frame.url = ctx.node.name; - ctx.node.addComponent(frame); - workbench.defocus(); - ctx.node.name = ctx.node.name.replaceAll("https://", "").replaceAll("http://"); - workbench.workspace.setExpanded(ctx.path.head, ctx.node, true); - workbench.focus(ctx.path); - } - - } - }); - } - -} - -const InlineFrameView = { - view({attrs: {path}}) { - const iframe = path.node.getComponent(InlineFrame); - return ( -
- -
- ) - } -} \ No newline at end of file diff --git a/lib/com/smartnode.tsx b/lib/com/smartnode.tsx deleted file mode 100644 index c4aa22f..0000000 --- a/lib/com/smartnode.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Workbench } from "../workbench/mod.ts"; -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -function debounce(func, timeout = 1000){ - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => { func.apply(this, args); }, timeout); - }; -} - -@component -export class SmartNode { - workbench: Workbench; - component?: Node; - object?: Node; - results?: Node[]; - query: string; - - lastQuery?: string; - lastResultCount?: number; - initialSearch: boolean; - - constructor() { - this.workbench = window.workbench; - this.searchDebounce = debounce(this.search.bind(this)); - this.query = ""; - this.initialSearch = false; - } - - handleIcon(collapsed: boolean = false): any { - return ( - - {collapsed?:null} - - - - - - ); - } - - belowEditor() { - return SmartFilter; - } - - onAttach(node: Node) { - this.component = node; - this.object = node.parent; - node.bus.observe((n: Node) => { - if (!node.isDestroyed) { - this.searchDebounce(); - } - }); - } - - search() { - if (!this.object) return; - if (!this.query) { - this.lastQuery = ""; - this.results = []; - return; - } - this.initialSearch = true; - - const results = this.workbench.search(this.query) - .filter(n => n.id !== this.object.id && n.id !== this.component.id); - - if (results.length !== this.lastResultCount || this.query !== this.lastQuery) { - if (this.results) { - // clean up old results - this.results.forEach((n) => n.destroy()); - } - this.results = results.map(n => { - const ref = this.object.bus.make(""); - ref.raw.Parent = "@tmp"; // cleaned up next import - ref.refTo = n; - return ref; - }); - this.lastQuery = this.query; - this.lastResultCount = results.length; - } - } - - objectChildren(node: Node, children: Node[]): Node[] { - if (!this.results && this.query && !this.initialSearch) { - this.search(); - } - return this.results || []; - } - - toJSON(key: string): any { - return { - query: this.query - }; - } - - fromJSON(obj: any) { - this.query = obj.query || ""; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "make-smart-node", - title: "Make Smart Node", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.childCount > 0) return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - workbench.defocus(); - const search = new SmartNode(); - ctx.node.addComponent(search); - workbench.workspace.setExpanded(ctx.path.head, ctx.node, true); - if (ctx.node.name === "") { - setTimeout(() => { - // defocusing will overwrite this from buffer - // without a delay - ctx.node.name = "Unnamed Smart Node"; - m.redraw(); - document.querySelector(`#node-${ctx.path.id}-${ctx.node.id} input`).focus(); - }, 10); - } - } - }); - } -} - - -const SmartFilter = { - view({attrs: {node, component, expanded}}) { - if (!expanded) return; - - const oninput = (e) => { - component.query = e.target.value; - component.search(); - node.changed(); - } - return ( -
-
- -
- ) - } -} \ No newline at end of file diff --git a/lib/com/tag.tsx b/lib/com/tag.tsx deleted file mode 100644 index 4a38387..0000000 --- a/lib/com/tag.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; -import { Workbench, Workspace } from "../workbench/mod.ts"; -import { Path } from "../workbench/path.ts"; -import { Template } from "./template.tsx"; -import { Picker } from "../ui/picker.tsx"; - -@component -export class Tag { - name: string; - - constructor(name: string) { - this.name = name; - } - - afterEditor() { - return TagBadge; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "add-tag", - title: "Add tag", - hidden: true, - action: (ctx: Context, name: string) => { - if (!ctx.node) return; - const tag = new Tag(name); - ctx.node.addComponent(tag); - const tmpl = Template.findNode(workbench.workspace, name); - if (tmpl) { - tmpl.fields.map(f => f.duplicate()).forEach(f => { - ctx.node.addLinked("Fields", f); - f.raw.Parent = ctx.node.raw.ID; - }); - tmpl.children.map(c => c.duplicate()).forEach(c => { - ctx.node.addChild(c); - c.raw.Parent = ctx.node.raw.ID; - }); - } - ctx.node.changed(); - } - }); - } - - static findAll(ws: Workspace): string[] { - const tags = new Set(); - ws.mainNode().walk((n) => { - if (n.value instanceof Tag) { - tags.add(n.value.name); - } - return false; - }, {includeComponents: true}); - return [...tags]; - } - - static findTagged(ws: Workspace, name: string): Node[] { - const nodes = []; - ws.mainNode().walk((n) => { - if (n.value instanceof Tag && n.value.name === name) { - nodes.push(n.parent); - } - return false; - }, {includeComponents: true}); - return nodes; - } - - static showPopover(bench: Workbench, path: Path, node: Node, inputview: Function, closer: Function) { - const tags = Tag.findAll(bench.workspace); - const trigger = bench.getInput(path); - const rect = trigger.getBoundingClientRect(); - let x = document.body.scrollLeft + rect.x + (trigger.selectionStart * 10) + 20; - let y = document.body.scrollTop + rect.y + rect.height; - bench.showPopover(() => ( - { - closer(); - bench.getInput(path).blur(); - node.name = node.name.replace(/\s*#\w*/, ""); - bench.executeCommand("add-tag", {node, path}, item.name); - }} - onchange={(state) => { - if (node.name.includes("#")) { - state.input = node.name.split("#")[1]; - } else { - state.input = ""; - } - const filtered = [...tags] - .filter(t => t.toLowerCase().startsWith(state.input.toLowerCase())) - .map(t => {return {name: t}}); - if ((filtered[0] && filtered[0].name != state.input && state.input != "") || filtered.length === 0) { - filtered.unshift({name: state.input, prefix: "Create tag: "}); - } - state.items = filtered; - }} - inputview={inputview} - itemview={(item) => -
-
{item.prefix||""}{item.name}
-
- } - /> - ), {top: `${y}px`, left: `${x}px`}); - } -} - -const TagBadge = { - view({attrs: {node, component}}) { - const onkeydown = (e) => { - if (e.key === "Backspace") { - node.removeComponent(component); - node.changed(); - } - }; - return ( -
- -
{component.name}
-
- ) - } -} \ No newline at end of file diff --git a/lib/com/template.tsx b/lib/com/template.tsx deleted file mode 100644 index a40c52c..0000000 --- a/lib/com/template.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -@component -export class Template { - object?: Node; - - constructor() { - } - - onAttach(node: Node) { - this.object = node.parent; - } - - handleIcon(collapsed: boolean = false): any { - return ( - - {collapsed?:null} - - - - ); - } - - toJSON(key: string): any { - return {}; - } - - static initialize(workbench: Workbench) { - workbench.commands.registerCommand({ - id: "make-template", - title: "Make Template", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - const tmpl = new Template(); - ctx.node.addComponent(tmpl); - ctx.node.changed(); - } - }); - } - - static findNode(ws: Workspace, name: string): Node|null { - let node = null; - ws.mainNode().walk((n) => { - if (n.value instanceof Template && n.value.object.name === name) { - node = n.value.object; - return true; - } - return false; - }, {includeComponents: true}); - return node; - } -} diff --git a/lib/com/textfield.tsx b/lib/com/textfield.tsx deleted file mode 100644 index b7a72bc..0000000 --- a/lib/com/textfield.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { component } from "../model/components.ts"; -import { Node } from "../model/mod.ts"; - -@component -export class TextField { - constructor() { - - } - - handleIcon(): any { - return - } -} \ No newline at end of file diff --git a/lib/mod.ts b/lib/mod.ts deleted file mode 100644 index d2cabe6..0000000 --- a/lib/mod.ts +++ /dev/null @@ -1,786 +0,0 @@ -/** - * A configurable, embeddable frontend for a graph/outline based note-taking tool. - * - * Treehouse can be embedded on a page and given a backend for a fully functional - * SPA. The backend adapter provides hooks to integrate with various backends. - * - * Typical usage involves including resource dependencies on the page then running: - * - * ```ts - * import {setup, BrowserBackend} from "https://treehouse.sh/lib/treehouse.min.js"; - * setup(document, document.body, new BrowserBackend()); - * ``` - * - * In this case using the built-in BrowserBackend to store state in localStorage. - * For more information see the [Quickstart Guide](https://treehouse.sh/docs/quickstart/). - * - * @module - */ -import { Path, Workbench } from "./workbench/mod.ts"; -import { App } from "./ui/app.tsx"; -import { Backend } from "./backend/mod.ts"; -import { CodeBlock } from "./com/codeblock.tsx"; -import { InlineFrame } from "./com/iframe.tsx"; -import { SmartNode } from "./com/smartnode.tsx"; -import { Checkbox } from "./com/checkbox.tsx"; -import { TextField } from "./com/textfield.tsx"; -import { Clock } from "./com/clock.tsx"; -import { Tag } from "./com/tag.tsx"; -import { Template } from "./com/template.tsx"; -import { Document } from "./com/document.tsx"; -import { Description } from "./com/description.tsx"; -import { objectManaged } from "./model/hooks.ts"; - -export { BrowserBackend, SearchIndex_MiniSearch } from "./backend/browser.ts"; -export { GitHubBackend } from "./backend/github.ts"; - - -/** - * setup initializes and mounts a workbench UI with a given backend adapter to a document. - * More specifically, first it initializes the given backend, then creates and initializes - * a Workbench instance with that backend, then it mounts the App component to the given - * target element. It will also add some event listeners to the document and currently - * this is where it registers all the built-in commands and their keybindings, as well - * as menus. - */ -export async function setup(document: Document, target: HTMLElement, backend: Backend) { - if (backend.initialize) { - await backend.initialize(); - } - - const workbench = new Workbench(backend); - window.workbench = workbench; - - await workbench.initialize(); - - // TODO: better way to initialize components? - [ - Clock, - TextField, - Document, - Checkbox, - Tag, - Template, - SmartNode, - Description, - InlineFrame, - CodeBlock, - ].forEach(com => { - if (com.initialize) { - com.initialize(workbench); - } - }); - - - // pretty specific to github backend right now - document.addEventListener("BackendError", () => { - workbench.showNotice("lockstolen", () => { - location.reload(); - }); - }); - - workbench.commands.registerCommand({ - id: "cut", - title: "Cut", - when: (ctx: Context) => { - if (!ctx.node) return false; - - // no text is selected - const input = workbench.getInput(ctx.path); - if (input && input.selectionStart === input.selectionEnd) { - return true; - } - - // builtin copy is being performed, - // clear clipboard so it doesn't override on paste - workbench.clipboard = undefined; - - return false; - }, - action: (ctx: Context) => { - workbench.clipboard = {op: "cut", node: ctx.node}; - } - }); - workbench.keybindings.registerBinding({ command: "cut", key: "meta+x" }); - - workbench.commands.registerCommand({ - id: "copy", - title: "Copy", - when: (ctx: Context) => { - if (!ctx.node) return false; - - // no text is selected - const input = workbench.getInput(ctx.path); - if (input && input.selectionStart === input.selectionEnd) { - return true; - } - - // builtin copy is being performed, - // clear clipboard so it doesn't override on paste - workbench.clipboard = undefined; - - return false; - }, - action: (ctx: Context) => { - workbench.clipboard = {op: "copy", node: ctx.node}; - } - }); - workbench.keybindings.registerBinding({ command: "copy", key: "meta+c" }); - - workbench.commands.registerCommand({ - id: "copy-reference", - title: "Copy as Reference", - when: (ctx: Context) => { - if (!ctx.node) return false; - - // no text is selected - const input = workbench.getInput(ctx.path); - if (input && input.selectionStart === input.selectionEnd) { - return true; - } - - // builtin copy is being performed, - // clear clipboard so it doesn't override on paste - workbench.clipboard = undefined; - - return false; - }, - action: (ctx: Context) => { - workbench.clipboard = {op: "copyref", node: ctx.node}; - } - }); - workbench.keybindings.registerBinding({ command: "copy-reference", key: "shift+ctrl+c" }); - - workbench.commands.registerCommand({ - id: "paste", - title: "Paste", - when: (ctx: Context) => { - if (workbench.clipboard) { - return true; - } - return false; - }, - action: (ctx: Context) => { - if (!ctx.node) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; - switch (workbench.clipboard.op) { - case "copy": - workbench.clipboard.node = workbench.clipboard.node.duplicate(); - break; - case "copyref": - const ref = workbench.workspace.new(""); - ref.refTo = workbench.clipboard.node; - workbench.clipboard.node = ref; - break; - } - if (workbench.clipboard.node.raw.Rel === "Fields") { - workbench.clipboard.node.raw.Parent = ctx.node.parent.id; - ctx.node.parent.addLinked("Fields", workbench.clipboard.node); - } else { - workbench.clipboard.node.parent = ctx.node.parent; - workbench.clipboard.node.siblingIndex = ctx.node.siblingIndex; - } - m.redraw.sync(); - const p = ctx.path.clone(); - p.pop(); - workbench.focus(p.append(workbench.clipboard.node)); - if (workbench.clipboard.op === "cut") { - workbench.clipboard = undefined; - } - } - }); - workbench.keybindings.registerBinding({ command: "paste", key: "meta+v" }); - - - - workbench.commands.registerCommand({ - id: "view-list", - title: "View as List", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - ctx.node.setAttr("view", "list"); - } - }); - - workbench.commands.registerCommand({ - id: "view-table", - title: "View as Table", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - ctx.node.setAttr("view", "table"); - ctx.node.children.forEach(child => { - workbench.workspace.setExpanded(ctx.path.head, child, false); - }); - } - }); - - workbench.commands.registerCommand({ - id: "view-tabs", - title: "View as Tabs", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - ctx.node.setAttr("view", "tabs"); - } - }); - - workbench.commands.registerCommand({ - id: "view-cards", - title: "View as Cards", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - ctx.node.setAttr("view", "cards"); - } - }); - - - workbench.commands.registerCommand({ - id: "add-checkbox", - title: "Add checkbox", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.hasComponent(Checkbox)) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - const checkbox = new Checkbox(); - ctx.node.addComponent(checkbox); - } - }); - - workbench.commands.registerCommand({ - id: "remove-checkbox", - title: "Remove checkbox", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - if (ctx.node.hasComponent(Checkbox)) return true; - return false; - }, - action: (ctx: Context) => { - ctx.node.removeComponent(Checkbox); - } - }); - - workbench.commands.registerCommand({ - id: "create-field", - title: "Create Field", - action: (ctx: Context) => { - if (!ctx.node) return; - if (ctx.node.childCount > 0) return; - if (ctx.node.componentCount > 0) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; - const path = ctx.path.clone(); - path.pop(); // drop node - const field = workbench.workspace.new(ctx.node.name, ""); - field.raw.Parent = ctx.node.parent.id; - const text = new TextField(); - field.addComponent(text); - ctx.node.parent.addLinked("Fields", field); - path.push(field); - ctx.node.destroy(); - m.redraw.sync(); - workbench.focus(path); - } - }); - - workbench.commands.registerCommand({ - id: "mark-done", - title: "Mark Done", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - if (!ctx.node) return; - if (ctx.node.hasComponent(Checkbox)) { - const checkbox = ctx.node.getComponent(Checkbox); - if (!checkbox.checked) { - checkbox.checked = true; - ctx.node.changed(); - } else { - ctx.node.removeComponent(Checkbox); - } - } else { - const checkbox = new Checkbox(); - ctx.node.addComponent(checkbox); - } - } - }); - workbench.keybindings.registerBinding({ command: "mark-done", key: "meta+enter" }); - - - - workbench.commands.registerCommand({ - id: "expand", - title: "Expand", - action: (ctx: Context) => { - if (!ctx.node) return; - workbench.workspace.setExpanded(ctx.path.head, ctx.node, true); - m.redraw(); - } - }); - workbench.keybindings.registerBinding({ command: "expand", key: "meta+arrowdown" }); - workbench.commands.registerCommand({ - id: "collapse", - title: "Collapse", - action: (ctx: Context) => { - if (!ctx.node) return; - workbench.workspace.setExpanded(ctx.path.head, ctx.node, false); - m.redraw(); - } - }); - workbench.keybindings.registerBinding({ command: "collapse", key: "meta+arrowup" }); - workbench.commands.registerCommand({ - id: "indent", - title: "Indent", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - const node = ctx.node; // redraw seems to unset ctx.node - const path = ctx.path.clone(); - let prev = node.prevSibling; - while (prev && objectManaged(prev)) { - prev = prev.prevSibling; - if (!prev) return; - } - if (prev !== null) { - path.pop(); // drop node - path.push(prev); - node.parent = prev; - path.push(node); - workbench.workspace.setExpanded(ctx.path.head, prev, true); - m.redraw.sync(); - workbench.focus(path); - } - } - }); - workbench.keybindings.registerBinding({ command: "indent", key: "tab" }); - workbench.commands.registerCommand({ - id: "outdent", - title: "Outdent", - when: (ctx: Context) => { - if (!ctx.node) return false; - if (ctx.node.raw.Rel === "Fields") return false; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return false; - if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false; - return true; - }, - action: (ctx: Context) => { - const node = ctx.node; // redraw seems to unset ctx.node - const parent = ctx.path.previous; - const path = ctx.path.clone(); - if (parent !== null && parent.id !== "@root" && parent.id !== workbench.workspace.lastOpenedID) { - path.pop(); // drop node - path.pop(); // drop parent - node.parent = parent.parent; - path.push(node); - node.siblingIndex = parent.siblingIndex + 1; - if (parent.childCount === 0 && parent.getLinked("Fields").length === 0) { - workbench.workspace.setExpanded(ctx.path.head, parent, false); - } - m.redraw.sync(); - workbench.focus(path); - } - } - }); - workbench.keybindings.registerBinding({ command: "outdent", key: "shift+tab" }); - workbench.commands.registerCommand({ - id: "move-up", - title: "Move Up", - action: (ctx: Context) => { - if (!ctx.node) return; - const node = ctx.node; // redraw seems to unset ctx.node - const parent = node.parent; - if (parent !== null && parent.id !== "@root") { - const children = parent.childCount; - if (node.siblingIndex === 0) { - if (!parent.prevSibling) { - return; - } - const p = ctx.path.clone(); - p.pop(); // drop node - p.pop(); // drop parent - let parentSib = parent.prevSibling; - while (parentSib && objectManaged(parentSib)) { - parentSib = parentSib.prevSibling; - if (!parentSib) return; - } - p.push(parentSib); - p.push(node); - node.parent = parentSib; - node.siblingIndex = parentSib.childCount - 1; - workbench.workspace.setExpanded(ctx.path.head, parentSib, true); - m.redraw.sync(); - workbench.focus(p); - } else { - if (children === 1) { - return; - } - node.siblingIndex = node.siblingIndex - 1; - m.redraw.sync(); - } - } - } - }); - workbench.keybindings.registerBinding({ command: "move-up", key: "shift+meta+arrowup" }); - workbench.commands.registerCommand({ - id: "move-down", - title: "Move Down", - action: (ctx: Context) => { - if (!ctx.node) return; - const node = ctx.node; // redraw seems to unset ctx.node - const parent = node.parent; - if (parent !== null && parent.id !== "@root") { - const children = parent.childCount; - // if last child - if (node.siblingIndex === children - 1) { - if (!parent.nextSibling) { - return; - } - const p = ctx.path.clone(); - p.pop(); // drop node - p.pop(); // drop parent - let parentSib = parent.nextSibling; - while (parentSib && objectManaged(parentSib)) { - parentSib = parentSib.nextSibling; - if (!parentSib) return; - } - p.push(parentSib); - p.push(node); - node.parent = parentSib; - node.siblingIndex = 0; - workbench.workspace.setExpanded(ctx.path.head, parentSib, true); - m.redraw.sync(); - workbench.focus(p); - } else { - if (children === 1) { - return; - } - node.siblingIndex = node.siblingIndex + 1; - m.redraw.sync(); - } - } - } - }); - workbench.keybindings.registerBinding({ command: "move-down", key: "shift+meta+arrowdown" }); - workbench.commands.registerCommand({ - id: "insert-child", - title: "Insert Child", - action: (ctx: Context, name: string = "", siblingIndex?: number) => { - if (!ctx.node) return; - if (objectManaged(ctx.node)) return; - const node = workbench.workspace.new(name); - node.parent = ctx.node; - if (siblingIndex !== undefined) { - node.siblingIndex = siblingIndex; - } - workbench.workspace.setExpanded(ctx.path.head, ctx.node, true); - m.redraw.sync(); - workbench.focus(ctx.path.append(node), name.length); - } - }); - workbench.commands.registerCommand({ - id: "insert-before", - title: "Insert Before", - action: (ctx: Context) => { - if (!ctx.node) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; - const node = workbench.workspace.new(""); - node.parent = ctx.node.parent; - node.siblingIndex = ctx.node.siblingIndex; - m.redraw.sync(); - const p = ctx.path.clone(); - p.pop(); - workbench.focus(p.append(node)); - } - }); - workbench.commands.registerCommand({ - id: "insert", - title: "Insert Node", - action: (ctx: Context, name: string = "") => { - if (!ctx.node) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; - const node = workbench.workspace.new(name); - node.parent = ctx.node.parent; - node.siblingIndex = ctx.node.siblingIndex + 1; - m.redraw.sync(); - const p = ctx.path.clone(); - p.pop(); - workbench.focus(p.append(node)); - } - }); - workbench.keybindings.registerBinding({ command: "insert", key: "shift+enter" }); - workbench.commands.registerCommand({ - id: "create-reference", - title: "Create Reference", - action: (ctx: Context) => { - // TODO: prevent creating reference to reference - if (!ctx.node) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; - const node = workbench.workspace.new(""); - node.parent = ctx.node.parent; - node.siblingIndex = ctx.node.siblingIndex + 1; - node.refTo = ctx.node; - m.redraw.sync(); - const p = ctx.path.clone(); - p.pop(); - workbench.focus(p.append(node)); - } - }); - workbench.commands.registerCommand({ - id: "delete", - title: "Delete Node", - action: (ctx: Context) => { - if (!ctx.node) return; - if (ctx.node.id.startsWith("@")) return; - if (ctx.path.previous && objectManaged(ctx.path.previous)) return; // should probably provide feedback or disable delete - const above = workbench.workspace.findAbove(ctx.path); - ctx.node.destroy(); - m.redraw.sync(); - if (above) { - let pos = 0; - if (ctx.event && ctx.event.key === "Backspace") { - if (above.node.value) { - pos = above.node.value.length; - } else { - pos = above.node.name.length; - } - } - if (above.node.childCount === 0) { - // TODO: use subCount - workbench.workspace.setExpanded(ctx.path.head, above.node, false); - } - workbench.focus(above, pos); - } - } - }); - workbench.keybindings.registerBinding({ command: "delete", key: "shift+meta+backspace" }); - workbench.commands.registerCommand({ - id: "prev", - title: "Previous Node", - action: (ctx: Context) => { - if (!ctx.node) return; - const above = workbench.workspace.findAbove(ctx.path); - if (above) { - workbench.focus(above); - } - } - }); - workbench.keybindings.registerBinding({ command: "prev", key: "arrowup" }); - workbench.commands.registerCommand({ - id: "next", - title: "Next Node", - action: (ctx: Context) => { - if (!ctx.node) return; - const below = workbench.workspace.findBelow(ctx.path); - if (below) { - workbench.focus(below); - } - } - }); - workbench.keybindings.registerBinding({ command: "next", key: "arrowdown" }); - workbench.commands.registerCommand({ - id: "pick-command", - title: "Command Palette", - hidden: true, - when: (ctx: Context) => { - if (workbench.isDialogOpen()) return false; - return true; - }, - action: (ctx: Context) => { - let node = ctx.node; - let path = ctx.path; - let posBelow = false; - if (!node) { - // no node is selected, use panel node - node = ctx.path.head; - path = new Path(ctx.path.head, ctx.path.name); - posBelow = true; - } - const trigger = workbench.getInput(path); - const rect = trigger.getBoundingClientRect(); - let x = document.body.scrollLeft + rect.x + (trigger.selectionStart * 10) + 20; - let y = document.body.scrollTop + rect.y - 8; - if (trigger.coordsAtCursor) { - x = trigger.coordsAtCursor.left-17; - y = trigger.coordsAtCursor.top-16; - } - if (posBelow) { - x = document.body.scrollLeft + rect.x; - y = document.body.scrollTop + rect.y + rect.height; - } - workbench.showPalette(x, y, workbench.newContext({ node })); - } - }); - workbench.keybindings.registerBinding({ command: "pick-command", key: "meta+k" }); - workbench.commands.registerCommand({ - id: "new-panel", - title: "Open in New Panel", - action: (ctx: Context) => { - if (!ctx.node) return; - workbench.openNewPanel(ctx.node); - m.redraw(); - } - }); - workbench.commands.registerCommand({ - id: "close-panel", - title: "Close Panel", - action: (ctx: Context, panel?: Path) => { - workbench.closePanel(panel || ctx.path); - workbench.context.path = workbench.mainPanel; - m.redraw(); - } - }); - workbench.commands.registerCommand({ - id: "zoom", - title: "Open", - action: (ctx: Context) => { - workbench.workspace.lastOpenedID = ctx.node.id; - workbench.workspace.save(); - workbench.context.path = ctx.path.append(ctx.node); - workbench.panels[0] = workbench.context.path; - m.redraw(); - } - }); - workbench.commands.registerCommand({ - id: "generate-random", - hidden: true, - title: "Generate Random Children", - action: (ctx: Context) => { - if (!ctx.node) return; - [...Array(100)].forEach(() => { - const node = workbench.workspace.new(generateName(8)); - node.parent = ctx.node; - }); - } - }); - - - - workbench.menus.registerMenu("node", [ - { command: "zoom" }, - { command: "new-panel" }, - { command: "cut" }, - { command: "copy" }, - { command: "paste" }, - { command: "indent" }, - { command: "outdent" }, - { command: "move-up" }, - { command: "move-down" }, - { command: "delete" }, - // {command: "add-checkbox"}, - // {command: "remove-checkbox"}, - // {command: "mark-done"}, - // {command: "add-page"}, - // {command: "remove-page"}, - // {command: "generate-random"}, - // {command: "create-reference"}, - ]); - - workbench.menus.registerMenu("settings", [ - { title: () => `${workbench.backend.auth?.currentUser()?.userID()} @ GitHub`, disabled: true, when: () => workbench.authenticated() }, - { - title: () => "Login with GitHub", when: () => !workbench.authenticated(), onclick: () => { - if (!localStorage.getItem("github")) { - workbench.showNotice("github", () => { - workbench.backend.auth.login() - }) - } else { - workbench.backend.auth.login() - } - } - }, - { - title: () => "Reset Demo", when: () => !workbench.authenticated(), onclick: () => { - localStorage.clear(); - location.reload(); - } - }, - { title: () => "Settings", onclick: () => workbench.showSettings() }, - { title: () => "Documentation", onclick: () => window.open("https://treehouse.sh/docs/user", "_blank") }, - { title: () => "Submit Issue", onclick: () => window.open("https://github.com/treehousedev/treehouse/issues", "_blank") }, - { title: () => "Logout", when: () => workbench.authenticated(), onclick: () => workbench.backend.auth.logout() }, - ]); - - document.addEventListener("keydown", (e) => { - const binding = workbench.keybindings.evaluateEvent(e); - if (binding && workbench.canExecuteCommand(binding.command, workbench.context)) { - workbench.executeCommand(binding.command, workbench.context); - e.stopPropagation(); - e.preventDefault(); - return; - } - }); - - - m.mount(target, { view: () => m(App, { workbench }) }); -} - - - -function generateName(length = 10) { - const random = (min: any, max: any) => { - return Math.round(Math.random() * (max - min) + min) - }; - const word = () => { - const words = [ - 'got', - 'ability', - 'shop', - 'recall', - 'fruit', - 'easy', - 'dirty', - 'giant', - 'shaking', - 'ground', - 'weather', - 'lesson', - 'almost', - 'square', - 'forward', - 'bend', - 'cold', - 'broken', - 'distant', - 'adjective' - ]; - return words[random(0, words.length - 1)]; - }; - const words = (length) => ( - [...Array(length)] - .map((_, i) => word()) - .join(' ') - .trim() - ); - return words(random(2, length)) -} diff --git a/lib/model/components.ts b/lib/model/components.ts deleted file mode 100644 index e53112d..0000000 --- a/lib/model/components.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Components are classes that can be used for component values in component nodes. - * These classes need to be registered so they can be properly "hydrated" from - * marshaled form (usually JSON) back into class instances. - * - * @module - */ - -const registry: Record = {}; - -export function component(target: any) { - registry[componentName(target)] = target; -} - -export function componentName(target: any): string { - if (target.prototype === undefined) { - target = target.constructor; - } - return `treehouse.${target.name}`; -} - -export function getComponent(com: any): any { - if (typeof com === "string") { - return registry[com]; - } - return registry[componentName(com)]; -} - -export function inflateToComponent(com: any, obj: any): any { - const o = new (getComponent(com)); - if (o["fromJSON"] instanceof Function) { - o.fromJSON(obj); - } else { - Object.defineProperties(o, Object.getOwnPropertyDescriptors(obj)); - } - return o; -} - -export function duplicate(obj: any): any { - if (obj === undefined) { - return undefined; - } - const com = getComponent(obj); - if (!com) { - return structuredClone(obj); - } - const src = JSON.parse(JSON.stringify(obj)||""); - const dst = new obj.constructor(); - if (dst["fromJSON"] instanceof Function) { - dst.fromJSON(src); - } else { - Object.defineProperties(dst, Object.getOwnPropertyDescriptors(src)); - } - return dst; -} \ No newline at end of file diff --git a/lib/model/hooks.ts b/lib/model/hooks.ts deleted file mode 100644 index 5a40f97..0000000 --- a/lib/model/hooks.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Hooks are single method interfaces implemented by component values. There are - * some system hook interfaces defined here as well as utilities for working with - * system and app hooks. - * - * @module - */ -import { Node } from "./mod.ts"; - -// triggered on parent set or import (if has parent), or addcomponent -export interface AttachListener { - onAttach(node: Node): void; -} - -// called on accessing children -export interface ChildProvider { - objectChildren(node: Node, children: Node[]): Node[]; -} - -export function hasHook(node: Node, hook: string): boolean { - return node.value && node.value[hook] instanceof Function; -} - -export function triggerHook(node: Node, hook: string, ...args: any[]): any { - if (hasHook(node, hook)) { - return node.value[hook].apply(node.value, args); - } -} - -export function objectHas(obj: Node, hook: string): boolean { - for (const com of obj.components) { - if (hasHook(com, hook)) return true; - } - return false; -} - -export function objectCall(obj: Node, hook: string, ...args: any[]): any { - for (const com of obj.components) { - if (hasHook(com, hook)) { - return com.value[hook].apply(com.value, args); - } - } -} - -export function componentsWith(obj: Node, hook: string, ...args: any[]): any[] { - const ret = []; - for (const com of obj.components) { - if (hasHook(com, hook)) { - ret.push(com.value); - } - } - return ret; -} - - - -// shorthand for nodes that have child provider hook. -// use this to determine if some commands should be -// prevented since visible children will not be impacted. -export function objectManaged(obj: Node): boolean { - return objectHas(obj, "objectChildren"); -} \ No newline at end of file diff --git a/lib/model/mod.ts b/lib/model/mod.ts deleted file mode 100644 index 59b38da..0000000 --- a/lib/model/mod.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * The Treehouse model is an extensible node tree system. It is an implementation of the - * Manifold system which is in active development, so expect changes here. - * - * @module - */ - -export type WalkFunc = (node: Node) => boolean; -export type ObserverFunc = (node: Node) => void; - -export interface WalkOptions { - followRefs: boolean; - includeComponents: boolean; -} - -export interface RawNode { - ID: string; - Name: string; - Value?: any; - Parent?: string; - Linked: Record; // Rel => IDs - Attrs: Record; - - Rel?: string; // Parent Rel hint kludge (Components, Fields) -} - - - -export interface Node { - readonly id: string; - readonly bus: Bus; - readonly raw: RawNode; - - name: string; - value: any; - parent: Node|null; - refTo: Node|null; - siblingIndex: number; - - readonly prevSibling: Node|null; - readonly nextSibling: Node|null; - readonly ancestors: Node[]; - readonly isDestroyed: boolean; - readonly path: string; - - readonly children: Node[]; - readonly childCount: number; - addChild(node: Node): void; - removeChild(node: Node): void; - - readonly components: Node[]; - readonly componentCount: number; - addComponent(obj: any): void; - removeComponent(type: any): void; - hasComponent(type: any): boolean; - getComponent(type: any): any|null; - // getComponentsInChildren - // getComponentsInParents - - getLinked(rel: string): Node[]; - addLinked(rel: string, node: Node): void; - removeLinked(rel: string, node: Node): void; - moveLinked(rel: string, node: Node, idx: number): void; - - componentField(name: string): any|null; - - getAttr(name: string): string; - setAttr(name: string, value: string): void; - - find(path: string): Node|null; - walk(fn: WalkFunc, opts?: WalkOptions): boolean; - destroy(): void; - duplicate(): Node; - changed(): void; - - -} - - -export interface Bus { - import(nodes: RawNode[]): void; - export(): RawNode[]; - make(name: string, value?: any): Node; - destroy(node: Node): void; - roots(): Node[]; - root(name?: string): Node|null; - find(path:string): Node|null; - walk(fn: WalkFunc, opts?: WalkOptions): void; - observe(fn: ObserverFunc): void; -} - - - diff --git a/lib/model/module/bus.ts b/lib/model/module/bus.ts deleted file mode 100644 index 828ff0d..0000000 --- a/lib/model/module/bus.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { RawNode, Node as INode, Bus as IBus, WalkFunc, ObserverFunc, WalkOptions } from "../mod.ts"; -import { componentName, getComponent, inflateToComponent } from "../components.ts"; -import { triggerHook, hasHook } from "../hooks.ts"; -import { Node } from "./mod.ts"; - -export class Bus { - nodes: Record; - observers: ObserverFunc[]; - - constructor() { - this.nodes = {"@root": { - ID: "@root", - Name: "@root", - Linked: {Children: [], Components: []}, - Attrs: {} - }}; - this.observers = []; - } - - changed(n: INode) { - this.observers.forEach(cb => cb(n)); - } - - /* Bus interface */ - - import(nodes: RawNode[]) { - for (const n of nodes) { - if (n.Value && getComponent(n.Name)) { - n.Value = inflateToComponent(n.Name, n.Value); - n.Rel = "Components"; // kludge - } - this.nodes[n.ID] = n; - } - for (const n of nodes) { - // clear stored tmp nodes - if (n.Parent === "@tmp") { - delete this.nodes[n.ID]; - continue; - } - // clear nodes with no parent that aren't system - if (!n.ID.startsWith("@") && n.Parent === undefined) { - delete this.nodes[n.ID]; - continue; - } - // clear nodes with non-existant parent - if (n.Parent && !this.nodes[n.Parent]) { - delete this.nodes[n.ID]; - continue; - } - const node = this.find(n.ID); - if (node) { - // check orphan - if (node.parent && !node.parent.raw) { - delete this.nodes[n.ID]; - continue; - } - // trigger attach - triggerHook(node, "onAttach", node); - } - } - } - - export(): RawNode[] { - const nodes: RawNode[] = []; - for (const n of Object.values(this.nodes)) { - nodes.push(n); - } - return nodes; - } - - make(name: string, value?: any): INode { - let parent: INode|null = null; - if (name.includes("/")) { - const parts = name.split("/"); - parent = this.find(parts[0]); - for (let i = 1; i < parts.length-1; i++) { - if (parent === null) { - throw "unable to get root"; - } - - let child = parent.find(parts[i]); - if (!child) { - child = this.make(parts.slice(0, i+1).join("/")); - } - parent = child; - } - name = parts[parts.length-1]; - } - const id = (name.startsWith("@"))?name:uniqueId(); - this.nodes[id] = { - ID: id, - Name: name, - Value: value, - Linked: {Children: [], Components: []}, - Attrs: {} - }; - const node = new Node(this, id); - if (parent) { - node.parent = parent; - } - return node; - } - - // destroys node but not linked nodes - destroy(n: INode) { - const p = n.parent; - if (p !== null && !p.isDestroyed) { - let rel = n.raw.Rel || "Children"; - if (p.raw.Linked[rel].includes(n.id)) { - p.raw.Linked[rel].splice(n.siblingIndex, 1); - } - } - delete this.nodes[n.id]; - if (p) { - this.changed(p); - } - } - - roots(): INode[] { - return Object.values(this.nodes).filter(n => n.Parent === undefined).map(n => new Node(this, n.ID)); - } - - root(name?: string): INode|null { - name = name || "@root" - const node = this.roots().find(root => root.name === name); - if (node === undefined) return null; - return node; - } - - find(path:string): INode|null { - const byId = this.nodes[path]; - if (byId) return new Node(this, byId.ID); - const parts = path.split("/"); - if (parts.length === 1 && parts[0].startsWith("@")) { - // did not find @id by ID so return null - return null; - } - let cur = this.root(parts[0]); - if (!cur && this.nodes[parts[0]]) { - cur = new Node(this, this.nodes[parts[0]].ID); - } - if (cur) { - parts.shift(); - } else { - cur = this.root("@root"); - } - if (!cur) { - return null; - } - const findChild = (n: INode, name: string): INode|undefined => { - if (n.refTo) { - n = n.refTo; - } - return n.children.find(child => child.name === name); - } - for (const name of parts) { - const child = findChild(cur, name); - if (!child) return null; - cur = child; - } - return cur; - } - - walk(fn: WalkFunc, opts?: WalkOptions) { - for (const root of this.roots()) { - if (root.walk(fn, opts)) return; - } - } - - observe(fn: ObserverFunc) { - this.observers.push(fn); - } -} - -const uniqueId = () => { - const dateString = Date.now().toString(36); - const randomness = Math.random().toString(36).substring(2); - return dateString + randomness; -}; diff --git a/lib/model/module/mod.ts b/lib/model/module/mod.ts deleted file mode 100644 index 51fde7c..0000000 --- a/lib/model/module/mod.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Bus } from "./bus.ts"; -export { Node } from "./node.ts"; \ No newline at end of file diff --git a/lib/model/module/mod_test.ts b/lib/model/module/mod_test.ts deleted file mode 100644 index 21f9965..0000000 --- a/lib/model/module/mod_test.ts +++ /dev/null @@ -1,96 +0,0 @@ - -import { assertEquals, assert, assertExists } from "https://deno.land/std@0.173.0/testing/asserts.ts"; -import * as module from "./mod.ts"; -import { Node } from "../mod.ts"; -import { component } from "../components.ts"; - -Deno.test("node children", () => { - const bus = new module.Bus(); - const nodeA = bus.make("@root/NodeA"); - const nodeB = bus.make("@root/NodeB"); - const nodeC = bus.make("@root/NodeC"); - - const root = bus.root(); - assertExists(root); - assertEquals(root.childCount, 3); - - assertEquals(nodeA.siblingIndex, 0); - assertEquals(nodeB.siblingIndex, 1); - nodeB.siblingIndex = 0; - assertEquals(nodeA.siblingIndex, 1); - assertEquals(nodeB.siblingIndex, 0); - - assertEquals(nodeA.parent?.name, root.name); - -}); - -@component -class Foobar { - state: string; - nodes: Node[]; - - constructor() { - this.state = ""; - this.nodes = []; - } - - onAttach(node: Node) { - if (!this.nodes.length) { - const b = node.bus; - this.nodes.push(b.make("Foo1")); - this.nodes.push(b.make("Foo2")); - this.nodes.push(b.make("Foo3")); - } - } - - objectChildren(node: Node, children: Node[]): Node[] { - return this.nodes; - } -} - -Deno.test("components", () => { - const b = new module.Bus(); - const root = b.root(); - assertExists(root); - - const f = new Foobar(); - f.state = "hello"; - root.addComponent(f); - - assertEquals(root.hasComponent(Foobar), true); - - const ff = root.getComponent(Foobar); - assertEquals(f, ff); - - root.removeComponent(Foobar); - assertEquals(root.hasComponent(Foobar), false); - -}); - -Deno.test("references", () => { - const bus = new module.Bus(); - const a = bus.make("A"); - const b = bus.make("A/B"); - const c = bus.make("A/B/C"); - - const x = bus.make("X"); - const r = bus.make(""); - r.refTo = b; - r.parent = x; - r.value = "value"; - - assertEquals(r.name, b.name); - assertEquals(r.value, b.value); - - assertEquals(r.path, "X/B"); - assertEquals(r.refTo.path, "A/B"); - - assertEquals(b.children[0].id, r.refTo.children[0].id); - - const names = ["A", "B", "C"]; - a.walk((n: Node): boolean => { - assertEquals(n.name, names.shift()); - return false; - }); - -}); \ No newline at end of file diff --git a/lib/model/module/node.ts b/lib/model/module/node.ts deleted file mode 100644 index 6ed444c..0000000 --- a/lib/model/module/node.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { RawNode, Node as INode, Bus as IBus, WalkFunc, WalkOptions } from "../mod.ts"; -import { componentName, getComponent, duplicate } from "../components.ts"; -import { triggerHook, hasHook } from "../hooks.ts"; -import { Bus } from "./mod.ts"; - -export class Node { - _id: string; - _bus: Bus; - - constructor(bus: Bus, id: string) { - this._bus = bus; - this._id = id; - } - - [Symbol.for("Deno.customInspect")]() { - return `Node[${this.id}:${this.name}]`; - } - - /* Node interface */ - - get id(): string { - return this._id; - } - - get bus(): IBus { - return this._bus; - } - - get raw(): RawNode { - const raw = this._bus.nodes[this.id]; - if (!raw) throw `use of non-existent node ${this.id}`; - return raw; - } - - - get name(): string { - if (this.refTo) { - return this.refTo.name; - } - return this.raw.Name; - } - - set name(val: string) { - if (this.refTo) { - this.refTo.name = val; - } else { - this.raw.Name = val; - } - this.changed(); - } - - get value(): any { - if (this.refTo) { - return this.refTo.value; - } - return this.raw.Value; - } - - set value(val: string) { - if (this.refTo) { - this.refTo.value = val; - } else { - this.raw.Value = val; - } - this.changed(); - } - - get parent(): INode|null { - if (!this.raw.Parent) return null; - if (!this._bus.nodes[this.raw.Parent]) return null; - return new Node(this._bus, this.raw.Parent); - } - - set parent(n: INode|null) { - const p = this.parent; - if (p !== null) { - p.raw.Linked.Children.splice(this.siblingIndex, 1); - } - if (n !== null) { - this.raw.Parent = n.id; - n.raw.Linked.Children.push(this.id); - triggerHook(n, "onAttach", n); - } else { - this.raw.Parent = undefined; - } - this.changed(); - } - - get refTo(): INode|null { - const id = this.raw.Attrs["refTo"]; - if (!id) return null; - const refTo = this._bus.nodes[id]; - if (!refTo) return null; - return new Node(this._bus, id); - } - - set refTo(n: INode|null) { - if (!n) { - delete this.raw.Attrs["refTo"]; - this.changed(); - return; - } - this.raw.Attrs["refTo"] = n.id; - this.changed(); - } - - get siblingIndex(): number { - const p = this.parent; - if (p === null) return 0; - let rel = this.raw.Rel || "Children"; - return p.raw.Linked[rel].findIndex(id => id === this.id); - } - - set siblingIndex(i: number) { - const p = this.parent; - if (p === null) return; - let rel = this.raw.Rel || "Children"; - p.raw.Linked[rel].splice(this.siblingIndex, 1); - p.raw.Linked[rel].splice(i, 0, this.id); - p.changed(); - } - - get prevSibling(): INode|null { - const p = this.parent; - if (p === null) return null; - if (this.siblingIndex === 0) return null; - let rel = this.raw.Rel || "Children"; - return p.getLinked(rel)[this.siblingIndex-1]; - } - - get nextSibling(): INode|null { - const p = this.parent; - if (p === null) return null; - if (this.siblingIndex === p.children.length-1) return null; - let rel = this.raw.Rel || "Children"; - return p.getLinked(rel)[this.siblingIndex+1]; - } - - get ancestors(): INode[] { - const anc = []; - let p = this.parent; - while (p !== null) { - anc.push(p); - p = p.parent; - } - return anc; - } - - get isDestroyed(): boolean { - return !this._bus.nodes.hasOwnProperty(this.id); - } - - get path(): string { - let cur: INode|null = this; - const path = []; - while (cur) { - path.unshift(cur.name); - cur = cur.parent; - } - return path.join("/"); - } - - get children(): INode[] { - if (this.refTo) return this.refTo.children; - let children: INode[] = []; - if (this.raw.Linked.Children) { - children = this.raw.Linked.Children.map(id => new Node(this._bus, id)); - }; - for (const com of this.components) { - if (hasHook(com, "objectChildren")) { - return triggerHook(com, "objectChildren", this, children); - } - } - return children; - } - - get childCount(): number { - if (this.refTo) return this.refTo.childCount; - for (const com of this.components) { - if (hasHook(com, "objectChildren")) { - return triggerHook(com, "objectChildren", this, null).length; - } - } - if (!this.raw.Linked.Children) return 0; - return this.raw.Linked.Children.length; - } - - addChild(node: INode) { - if (this.refTo) { - this.refTo.addChild(node); - return; - } - this.raw.Linked.Children.push(node.id); - this.changed(); - } - - removeChild(node: INode) { - if (this.refTo) { - this.refTo.removeChild(node); - return; - } - const children = this.raw.Linked.Children.filter(id => id === node.id); - this.raw.Linked.Children = children; - this.changed(); - } - - get fields(): INode[] { - if (!this.raw.Linked.Fields) return []; - return this.raw.Linked.Fields.map(id => new Node(this._bus, id)); - } - - get fieldCount(): number { - if (!this.raw.Linked.Fields) return 0; - return this.raw.Linked.Fields.length; - } - - get components(): INode[] { - if (!this.raw.Linked.Components) return []; - return this.raw.Linked.Components.map(id => new Node(this._bus, id)); - } - - get componentCount(): number { - if (!this.raw.Linked.Components) return 0; - return this.raw.Linked.Components.length; - } - - componentField(name: string): any|null { - for (const com of this.components) { - if (Object.keys(com.value||{}).includes(name)) { - return com.value[name]; - } - } - return null; - } - - addComponent(obj: any) { - const node = this.bus.make(componentName(obj), obj); - node.raw.Parent = this.id; - node.raw.Rel = "Components" // kludge - this.raw.Linked.Components.push(node.id); - triggerHook(node, "onAttach", node); - this.changed(); - } - - removeComponent(obj: any) { - let coms; - if (obj.name && getComponent(obj)) { - coms = this.components.filter(n => n.name === componentName(obj)); - } else { - coms = this.components.filter(n => n.value === obj); - } - if (coms.length > 0) { - coms[0].destroy(); - } - this.changed(); - } - - hasComponent(type: any): boolean { - const coms = this.components.filter(n => n.name === componentName(type)); - if (coms.length > 0) { - return true; - } - return false; - } - - getComponent(type: any): any|null { - const coms = this.components.filter(n => n.name === componentName(type)); - if (coms.length > 0) { - return coms[0].value; - } - return null; - } - // getComponentsInChildren - // getComponentsInParents - - getLinked(rel: string): INode[] { - if (!this.raw.Linked[rel]) return []; - return this.raw.Linked[rel].map(id => new Node(this._bus, id)); - } - - addLinked(rel: string, node: INode) { - if (!this.raw.Linked[rel]) { - this.raw.Linked[rel] = []; - } - node.raw.Rel = rel; // kludge - this.raw.Linked[rel].push(node.id); - this.changed(); - } - - removeLinked(rel: string, node: INode) { - if (!this.raw.Linked[rel]) { - this.raw.Linked[rel] = []; - } - const linked = this.raw.Linked[rel].filter(id => id === node.id); - this.raw.Linked[rel] = linked; - this.changed(); - } - - moveLinked(rel: string, node: INode, idx: number) { - if (!this.raw.Linked[rel]) { - this.raw.Linked[rel] = []; - } - const oldIdx = this.raw.Linked[rel].findIndex(id => id === node.id); - if (oldIdx === -1) return; - const linked = this.raw.Linked[rel]; - linked.splice(idx, 0, linked.splice(oldIdx, 1)[0]); - this.raw.Linked[rel] = linked; - this.changed(); - } - - getAttr(name: string): string { - return this.raw.Attrs[name] || ""; - } - - setAttr(name: string, value: string) { - this.raw.Attrs[name] = value; - this.changed(); - } - - find(path: string): INode|null { - return this.bus.find([this.path, path].join("/")); - } - - walk(fn: WalkFunc, opts?: WalkOptions): boolean { - opts = opts || { - followRefs: false, - includeComponents: false - }; - if (fn(this)) { - return true; - } - let children = this.children; - if (this.refTo && opts.followRefs) { - if (fn(this.refTo)) { - return true; - } - children = this.refTo.children; - } - for (const child of children) { - if (child.walk(fn, opts)) return true; - } - if (opts.includeComponents) { - for (const com of this.components) { - if (com.walk(fn, opts)) return true; - } - } - return false; - } - - destroy() { - if (this.isDestroyed) return; - if (this.refTo) { - this._bus.destroy(this); - return; - } - const nodes: INode[] = []; - this.walk((n: INode): boolean => { - nodes.push(n); - return false; - }, { - followRefs: false, - includeComponents: true - }); - nodes.reverse().forEach(n => this._bus.destroy(n)); - } - - duplicate(): INode { - const n = this._bus.make(this.name, duplicate(this.value)); - n.raw.Rel = this.raw.Rel; - this.fields.map(f => f.duplicate()).forEach(f => { - n.addLinked("Fields", f); - f.raw.Parent = n.raw.ID; - }); - this.components.map(c => c.duplicate()).forEach(c => { - n.addLinked("Components", c); - c.raw.Parent = n.raw.ID; - }); - this.children.map(c => c.duplicate()).forEach(c => { - n.addChild(c); - c.raw.Parent = n.raw.ID; - }); - return n; - } - - changed() { - this._bus.changed(this); - } - - // duplicate? -} diff --git a/lib/treehouse.min.js b/lib/treehouse.min.js new file mode 100644 index 0000000..3bad554 --- /dev/null +++ b/lib/treehouse.min.js @@ -0,0 +1,5 @@ +var ye=Object.defineProperty;var Je=Object.getOwnPropertyDescriptor;var a=(i,e)=>ye(i,"name",{value:e,configurable:!0});var N=(i,e,t,n)=>{for(var o=n>1?void 0:n?Je(e,t):e,r=i.length-1,s;r>=0;r--)(s=i[r])&&(o=(n?s(e,t,o):s(o))||o);return n&&o&&ye(e,t,o),o};var be=navigator.userAgent.toLowerCase().indexOf("mac")!==-1;function Y(i){if(!i)return[];let e={backspace:"\u232B",shift:"\u21E7",meta:"\u2318",tab:"\u21B9",ctrl:"\u2303",arrowup:"\u2191",arrowdown:"\u2193",arrowleft:"\u2190",arrowright:"\u2192",enter:"\u23CE"};return i.toLowerCase().split("+").map(xe).map(n=>Object.keys(e).includes(n)?e[n]:n)}a(Y,"bindingSymbols");function xe(i){return!be&&i==="meta"?"ctrl":i}a(xe,"filterKeyForNonMacMeta");var oe=class{constructor(){this.bindings=[]}registerBinding(e){this.bindings.push(e)}getBinding(e){for(let t of this.bindings)if(t.command===e)return t;return null}evaluateEvent(e){e:for(let t of this.bindings){let n=t.key.toLowerCase().split("+");if(n.pop()===e.key.toLowerCase()){for(let r of["shift","ctrl","alt","meta"]){let s=n.includes(r);if(!be){if(r==="meta")continue;r==="ctrl"&&(s=n.includes("meta")||n.includes("ctrl"))}let l=e[`${xe(r)}Key`];if(!l&&s||l&&!s)continue e}return t}}return null}};a(oe,"KeyBindings");var re=class{constructor(){this.commands={}}registerCommand(e){this.commands[e.id]=e}canExecuteCommand(e,...t){return this.commands[e]?!(this.commands[e].when&&!this.commands[e].when(...t)):!1}executeCommand(e,...t){return new Promise(n=>{let o=this.commands[e].action(...t);n(o)})}};a(re,"CommandRegistry");var ie=class{constructor(){this.menus={}}registerMenu(e,t){this.menus[e]=t}};a(ie,"MenuRegistry");function Ce(i,e,t,n){return t?e.disabled||!i.canExecuteCommand(t.id,n):e.disabled}a(Ce,"isDisabled");var Ne={view({attrs:{workbench:i,x:e,y:t,items:n,align:o,commands:r,ctx:s}}){let l=a((d,c)=>h=>{h.stopPropagation(),!Ce(i,d,c,s)&&(i.closeMenu(),d.onclick&&d.onclick(),c&&i.executeCommand(c.id,s))},"onclick");return m("ul",{class:"menu",style:{margin:"0",display:"inline-block"}},n.filter(d=>!d.when||d.when()).map(d=>{let c="",h,p;return d.command&&(p=r.find(C=>C.id===d.command),h=i.keybindings.getBinding(p.id),c=p.title),d.title&&(c=d.title()),m("li",{onclick:l(d,p),class:Ce(i,d,p,s)?"disabled":"",style:{display:"flex"}},m("div",null,c),h&&m("div",{class:"keybindings grow text-right"},Y(h.key).join(" ").toUpperCase()))}))}};var G={onupdate({state:i,dom:e}){let t=e.querySelector(".items").children;i.selected!==void 0&&t.length>0&&t[i.selected].scrollIntoView({block:"nearest"})},oncreate({attrs:i,state:e,dom:t}){i.inputview&&t.querySelector("input")?.focus(),e.selected===void 0&&(e.selected=0)},view({attrs:i,state:e}){e.selected=e.selected===void 0?0:e.selected,e.input=e.input===void 0?i.input||"":e.input,e.items===void 0&&(e.items=[],i.onchange(e));let t=a(o=>{let r=a((s,l)=>(s%l+l)%l,"mod");if(o.key==="ArrowDown")return e.selected===void 0?(e.selected=0,!1):(e.selected=r(e.selected+1,e.items.length),!1);if(o.key==="ArrowUp")return e.selected===void 0&&(e.selected=0),e.selected=r(e.selected-1,e.items.length),!1;if(o.key==="Enter")return e.selected!==void 0&&i.onpick(e.items[e.selected]),!1},"onkeydown"),n=a(o=>{e.input=o.target.value,e.selected=0,i.onchange(e)},"oninput");return m("div",{class:"picker"},i.inputview(t,n,e.input),m("div",{class:"items"},e.items.map((o,r)=>m("div",{class:e.selected===r?"item selected":"item",onclick:()=>i.onpick(o),onmouseover:()=>e.selected=r},i.itemview(o,r)))))}};var Se={view({attrs:{workbench:i,ctx:e}}){let t=a(d=>(d.title||d.id).replace("-"," ").replace(/(^|\s)\S/g,h=>h.toUpperCase()),"getTitle"),n=a((d,c)=>t(d).localeCompare(t(c)),"sort"),o=a(d=>{i.closeDialog(),i.commands.executeCommand(d.id,e)},"onpick"),r=a(d=>{d.items=l.filter(c=>(c.title||c.id).toLowerCase().includes(d.input.toLowerCase()))},"onchange"),s=a(d=>{let c=i.keybindings.getBinding(d.id);return c?Y(c.key).join(" ").toUpperCase():""},"getBindingSymbols"),l=Object.values(i.commands.commands).filter(d=>!d.hidden).filter(d=>i.canExecuteCommand(d.id,e)).sort(n);return m("div",{class:"palette"},m(G,{onpick:o,onchange:r,inputview:(d,c)=>m("div",null,m("input",{style:{width:"98%"},type:"text",onkeydown:d,oninput:c,placeholder:"Enter command..."})),itemview:d=>m("div",{class:"flex"},m("div",null,t(d)),m("div",{class:"keybindings grow text-right"},s(d)))}))}};function J(i,e){return i.value&&i.value[e]instanceof Function}a(J,"hasHook");function Q(i,e,...t){if(J(i,e))return i.value[e].apply(i.value,t)}a(Q,"triggerHook");function W(i,e){for(let t of i.components)if(J(t,e))return!0;return!1}a(W,"objectHas");function se(i,e,...t){for(let n of i.components)if(J(n,e))return n.value[e].apply(n.value,t)}a(se,"objectCall");function X(i,e,...t){let n=[];for(let o of i.components)J(o,e)&&n.push(o.value);return n}a(X,"componentsWith");function j(i){return W(i,"objectChildren")}a(j,"objectManaged");var we={};function S(i){we[K(i)]=i}a(S,"component");function K(i){return i.prototype===void 0&&(i=i.constructor),`treehouse.${i.name}`}a(K,"componentName");function Z(i){return typeof i=="string"?we[i]:we[K(i)]}a(Z,"getComponent");function De(i,e){let t=new(Z(i));return t.fromJSON instanceof Function?t.fromJSON(e):Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)),t}a(De,"inflateToComponent");function Ie(i){if(i===void 0)return;if(!Z(i))return structuredClone(i);let t=JSON.parse(JSON.stringify(i)||""),n=new i.constructor;return n.fromJSON instanceof Function?n.fromJSON(t):Object.defineProperties(n,Object.getOwnPropertyDescriptors(t)),n}a(Ie,"duplicate");var x=class{constructor(){}onAttach(e){this.object=e.parent,this.object.setAttr("view","document")}handleIcon(e=!1){return m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"node-bullet"},m("path",{d:"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"}),m("polyline",{points:"14 2 14 8 20 8"}),m("line",{x1:"16",y1:"13",x2:"8",y2:"13"}),m("line",{x1:"16",y1:"17",x2:"8",y2:"17"}),m("polyline",{points:"10 9 9 9 8 9"}))}toJSON(e){return{}}static initialize(e){e.commands.registerCommand({id:"make-document",title:"Make Document",action:t=>{if(!t.node)return;let n=new x;t.node.addComponent(n),t.node.changed(),e.executeCommand("zoom",t)}})}};a(x,"Document"),x=N([S],x);var L=class{constructor(){this.text=""}editor(){return Qe}static initialize(e){e.commands.registerCommand({id:"add-description",title:"Add Description",when:t=>!(!t.node||t.path.previous&&j(t.path.previous)||t.node.hasComponent(L)),action:(t,n)=>{let o=new L;t.node.addComponent(o),t.node.changed()}}),e.commands.registerCommand({id:"remove-description",title:"Remove Description",when:t=>!t.node||t.path.previous&&j(t.path.previous)?!1:!!t.node.hasComponent(L),action:(t,n)=>{t.node.removeComponent(L),t.node.changed()}})}};a(L,"Description"),L=N([S],L);var Qe={view({attrs:{node:i}}){let e=a(n=>{let o=i.getComponent(L);o.text=n.target.value,i.changed()},"oninput"),t=a(n=>{i.getComponent(L).text===""&&i.removeComponent(L),i.changed()},"onblur");return m("input",{class:"node-description",placeholder:"Add Description",type:"text",value:i.getComponent(L).text,onblur:t,oninput:e})}};var q={view({attrs:{workbench:i,path:e,onkeydown:t,oninput:n,disallowEmpty:o,editValue:r,placeholder:s},state:l}){let d=e.node,c=r?"value":"name",h=a(()=>c==="name"?W(d,"displayName")?se(d,"displayName",d):d.name:d[c]||"","display"),p=a(()=>{l.initialValue=d[c],i.context.node=d,i.context.path=e},"onfocus"),C=a(()=>d[c],"getter"),A=a((b,g)=>{d.isDestroyed||(o&&b.length===0?d[c]=l.initialValue:d[c]=b),g&&(i.context.node=null)},"setter");d.raw.Rel==="Fields"&&(s=r?"Value":"Field");let k=`input-${e.id}-${d.id}`;c==="value"&&(k=k+"-value");let v=Ye;d.parent&&d.parent.hasComponent(x)&&window.Editor&&(v=Ke);let f;return d.hasComponent(L)&&(f=d.getComponent(L)),m("div",{class:"node-editor flex flex-col"},m(v,{id:k,getter:C,setter:A,display:h,onkeydown:t,onfocus:p,oninput:n,placeholder:s,workbench:i,path:e}),f?m(f.editor(),{node:d}):null)}},Ke={oncreate({dom:i,state:e,attrs:{id:t,onkeydown:n,onfocus:o,onblur:r,oninput:s,getter:l,setter:d,display:c,placeholder:h}}){let p=e.editing?e.buffer:c?c():l(),C=a(f=>{f.key==="Enter"&&(f.preventDefault(),f.stopPropagation())},"defaultKeydown"),A=a(f=>{o&&o(f),e.editing=!0,e.buffer=l()},"startEdit"),k=a(f=>{e.editing&&(e.editing=!1,d(e.buffer,!0),e.buffer=void 0),r&&r(f)},"finishEdit"),v=a(f=>{e.buffer=f.target.value,d(e.buffer,!1),s&&s(f)},"edit");e.editor=new window.Editor(i,p,h),e.editor.onblur=k,e.editor.onfocus=A,e.editor.oninput=v,e.editor.onkeydown=n||C,i.editor=e.editor,i.id=t},onupdate({dom:i,state:e,attrs:{getter:t,display:n}}){e.editor.value=e.editing?e.buffer:n?n():t()},view(){return m("div",{class:"text-editor"})}},Ye={oncreate({dom:i,attrs:e}){let t=i.querySelector("textarea"),n=t.offsetHeight,o=i.querySelector("span");this.updateHeight=()=>{o.style.width=`${Math.max(t.offsetWidth,100)}px`,o.innerHTML=t.value.replace(` +`,"
");let r=o.offsetHeight;r===0&&n>0&&(r=n),t.style.height=r>0?`${r}px`:"var(--body-line-height)"},t.addEventListener("input",()=>this.updateHeight()),t.addEventListener("blur",()=>o.innerHTML=""),setTimeout(()=>this.updateHeight(),50),e.onmount&&e.onmount(t)},onupdate(){this.updateHeight()},view({attrs:{id:i,onkeydown:e,onfocus:t,onblur:n,oninput:o,getter:r,setter:s,display:l,placeholder:d,path:c,workbench:h},state:p}){let C=p.editing?p.buffer:l?l():r();return m("div",{class:"text-editor"},m("textarea",{id:i,rows:"1",onfocus:a(g=>{t&&t(g),p.editing=!0,p.buffer=r()},"startEdit"),onblur:a(g=>{p.editing&&(p.editing=!1,s(p.buffer,!0),p.buffer=void 0),n&&n(g)},"finishEdit"),oninput:a(g=>{p.buffer=g.target.value,s(p.buffer,!1),o&&o(g)},"edit"),onpaste:a(g=>{let T=g.clipboardData.getData("Text");if(T.length>0){g.preventDefault(),g.stopPropagation();let u=T.split(` +`).map(w=>w.trim()).filter(w=>w.length>0);p.buffer=u.shift(),s(p.buffer,!0);let I=c.node;for(let w of u){let y=h.workspace.new(w);y.parent=I.parent,y.siblingIndex=I.siblingIndex+1,m.redraw.sync();let E=c.clone();E.pop(),h.focus(E.append(y)),I=y}}},"handlePaste"),placeholder:d,onkeydown:e||a(g=>{g.key==="Enter"&&(g.preventDefault(),g.stopPropagation())},"defaultKeydown"),value:C},C),m("span",{style:{visibility:"hidden",position:"fixed"}}))}};var H=class{constructor(){}onAttach(e){this.object=e.parent}handleIcon(e=!1){return m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"3","stroke-linecap":"round","stroke-linejoin":"round",class:"node-bullet"},e?m("circle",{id:"node-collapsed-handle",stroke:"none",cx:"12",cy:"12",r:"12"}):null,m("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),m("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"}))}toJSON(e){return{}}static initialize(e){e.commands.registerCommand({id:"make-template",title:"Make Template",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)),action:t=>{let n=new H;t.node.addComponent(n),t.node.changed()}})}static findNode(e,t){let n=null;return e.mainNode().walk(o=>o.value instanceof H&&o.value.object.name===t?(n=o.value.object,!0):!1,{includeComponents:!0}),n}};a(H,"Template"),H=N([S],H);var F=class{constructor(e){this.name=e}afterEditor(){return Ge}static initialize(e){e.commands.registerCommand({id:"add-tag",title:"Add tag",hidden:!0,action:(t,n)=>{if(!t.node)return;let o=new F(n);t.node.addComponent(o);let r=H.findNode(e.workspace,n);r&&(r.fields.map(s=>s.duplicate()).forEach(s=>{t.node.addLinked("Fields",s),s.raw.Parent=t.node.raw.ID}),r.children.map(s=>s.duplicate()).forEach(s=>{t.node.addChild(s),s.raw.Parent=t.node.raw.ID})),t.node.changed()}})}static findAll(e){let t=new Set;return e.mainNode().walk(n=>(n.value instanceof F&&t.add(n.value.name),!1),{includeComponents:!0}),[...t]}static findTagged(e,t){let n=[];return e.mainNode().walk(o=>(o.value instanceof F&&o.value.name===t&&n.push(o.parent),!1),{includeComponents:!0}),n}static showPopover(e,t,n,o,r){let s=F.findAll(e.workspace),l=e.getInput(t),d=l.getBoundingClientRect(),c=document.body.scrollLeft+d.x+l.selectionStart*10+20,h=document.body.scrollTop+d.y+d.height;e.showPopover(()=>m(G,{onpick:p=>{r(),e.getInput(t).blur(),n.name=n.name.replace(/\s*#\w*/,""),e.executeCommand("add-tag",{node:n,path:t},p.name)},onchange:p=>{n.name.includes("#")?p.input=n.name.split("#")[1]:p.input="";let C=[...s].filter(A=>A.toLowerCase().startsWith(p.input.toLowerCase())).map(A=>({name:A}));(C[0]&&C[0].name!=p.input&&p.input!=""||C.length===0)&&C.unshift({name:p.input,prefix:"Create tag: "}),p.items=C},inputview:o,itemview:p=>m("div",{class:"flex"},m("div",null,p.prefix||"",p.name))}),{top:`${h}px`,left:`${c}px`})}};a(F,"Tag"),F=N([S],F);var Ge={view({attrs:{node:i,component:e}}){return m("div",{tabindex:"1",class:"badge flex flex-row items-center",onkeydown:a(n=>{n.key==="Backspace"&&(i.removeComponent(e),i.changed())},"onkeydown")},m("span",null,"#\xA0"),m("div",{style:{whiteSpace:"nowrap"}},e.name))}};var Xe={async execute(i,e){return e.language!=="javascript"?`Unsupported language: ${e.language}`:window.eval(i).toString()},canExecute(i){return i.language==="javascript"}},M=class{constructor(e){this.code="",this.language="",this.detectLanguage=!0,e&&(this.language=e,this.detectLanguage=!1)}childrenView(){return le}handleIcon(e=!1){return m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"node-bullet"},m("polyline",{points:"16 18 22 12 16 6"}),m("polyline",{points:"8 6 2 12 8 18"}))}static initialize(e){e.commands.registerCommand({id:"make-code-block",title:"Make Code Block",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)),action:(t,n)=>{let o=new M(n);t?.node&&(t.node.addComponent(o),t.node.changed(),e.workspace.setExpanded(t.path.head,t.path.node,!0))}})}};a(M,"CodeBlock"),M=N([S],M);var Ze={oncreate(i){let{dom:e,attrs:{path:t}}=i,n=t.node.getComponent(M);e.jarEditor=new window.CodeJar(e,o=>{o.textContent=o.textContent,window.hljs.highlightBlock(o),n.detectLanguage&&(n.language=window.hljs.highlightAuto(o.textContent).language||"")}),e.jarEditor.updateCode(n.code),e.jarEditor.onUpdate(o=>{n.code=o,t.node.changed()})},view({attrs:{workbench:i,path:e}}){return m("div",{class:"code-editor",onkeydown:a(n=>n.stopPropagation(),"onkeydown")})}},_e={view({dom:i,state:e,attrs:{path:t}}){let n=t.node.getComponent(M),o=a(async()=>{e.output="Running...";try{let r=await Xe.execute(n.code,{language:n.language});e.output=r}catch(r){e.output=r.toString()}},"handleClick");return m("div",{className:"code-editor-output"},m("p",null,e.output?"Output: "+e.output:""),m("button",{type:"button",onclick:o},"Run"))}},le=class{view(e){return[m(Ze,e.attrs),m(_e,e.attrs)]}};a(le,"CodeEditorWithOutput");var Ee={view({attrs:{workbench:i,path:e}}){return m("div",{class:"empty-view"})}};var ce={view({attrs:{workbench:i,path:e}}){return m("div",{class:"new-node flex flex-row items-center"},m("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"currentColor",viewBox:"0 0 16 16"},m("circle",{cx:"8",cy:"7",r:"7"}),m("path",{style:{transform:"translate(0px, -1px)"},d:"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"})),m("div",{class:"flex grow"},m("input",{class:"grow",type:"text",onkeydown:a(n=>{if(n.key==="Tab"){if(n.stopPropagation(),n.preventDefault(),node.childCount>0){let o=e.node.children[e.node.childCount-1];i.executeCommand("insert-child",{node:o,path:e})}}else i.executeCommand("insert-child",{node:e.node,path:e},n.target.value)},"keydown"),value:""})))}};function et(i,e=1e3){let t;return(...n)=>{clearTimeout(t),t=setTimeout(()=>{i.apply(this,n)},e)}}a(et,"debounce");var U=class{constructor(){this.workbench=window.workbench,this.searchDebounce=et(this.search.bind(this)),this.query="",this.initialSearch=!1}handleIcon(e=!1){return m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",class:"node-bullet",width:"15",height:"15",fill:"none",stroke:"currentColor","stroke-width":"3","stroke-linecap":"round","stroke-linejoin":"round"},e?m("circle",{id:"node-collapsed-handle",stroke:"none",cx:"12",cy:"12",r:"12"}):null,m("svg",{xmlns:"http://www.w3.org/2000/svg",x:"3",y:"3",width:"19",height:"19",viewBox:"0 0 24 24"},m("circle",{cx:"11",cy:"11",r:"8"}),m("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"})))}belowEditor(){return tt}onAttach(e){this.component=e,this.object=e.parent,e.bus.observe(t=>{e.isDestroyed||this.searchDebounce()})}search(){if(!this.object)return;if(!this.query){this.lastQuery="",this.results=[];return}this.initialSearch=!0;let e=this.workbench.search(this.query).filter(t=>t.id!==this.object.id&&t.id!==this.component.id);(e.length!==this.lastResultCount||this.query!==this.lastQuery)&&(this.results&&this.results.forEach(t=>t.destroy()),this.results=e.map(t=>{let n=this.object.bus.make("");return n.raw.Parent="@tmp",n.refTo=t,n}),this.lastQuery=this.query,this.lastResultCount=e.length)}objectChildren(e,t){return!this.results&&this.query&&!this.initialSearch&&this.search(),this.results||[]}toJSON(e){return{query:this.query}}fromJSON(e){this.query=e.query||""}static initialize(e){e.commands.registerCommand({id:"make-smart-node",title:"Make Smart Node",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.childCount>0||t.node.parent&&t.node.parent.hasComponent(Document)),action:t=>{e.defocus();let n=new U;t.node.addComponent(n),e.workspace.setExpanded(t.path.head,t.node,!0),t.node.name===""&&setTimeout(()=>{t.node.name="Unnamed Smart Node",m.redraw(),document.querySelector(`#node-${t.path.id}-${t.node.id} input`).focus()},10)}})}};a(U,"SmartNode"),U=N([S],U);var tt={view({attrs:{node:i,component:e,expanded:t}}){if(!t)return;let n=a(o=>{e.query=o.target.value,e.search(),i.changed()},"oninput");return m("div",{class:"expanded-node flex flex-row"},m("div",{class:"indent flex"}),m("input",{type:"text",class:"grow",placeholder:"Enter search",value:e.query,oninput:n,style:{background:"inherit",border:"1px solid var(--color-outline-secondary)",outline:"0",padding:"var(--1)",marginBottom:"var(--1)",borderRadius:"var(--border-radius)"}}))}};var Le={view({attrs:{workbench:i,path:e,alwaysShowNew:t}}){let n=e.node;e.node.refTo&&(n=e.node.refTo);let o=!1;return(n.childCount===0&&n.getLinked("Fields").length===0||t)&&(o=!0),n.hasComponent(U)&&(o=!1),m("div",{class:"list-view"},m("div",{class:"fields"},n.getLinked("Fields").length>0&&n.getLinked("Fields").map(r=>m(V,{key:r.id,workbench:i,path:e.append(r)}))),m("div",{class:"children"},n.childCount>0&&n.children.map(r=>m(V,{key:r.id,workbench:i,path:e.append(r)})),o&&m(ce,{workbench:i,path:e})))}};var Ae={view({attrs:{workbench:i,path:e},state:t}){let n=e.node;t.fields=t.fields===void 0?new Set:t.fields,n.children.forEach(r=>{r.getLinked("Fields").forEach(s=>t.fields.add(s.name))});let o=a((r,s)=>{let l=r.getLinked("Fields").filter(d=>d.name===s);return l.length===0?"":m(q,{editValue:!0,workbench:i,path:e.append(l[0])})},"getFieldEditor");return m("table",{class:"table-view",style:{gridTemplateColumns:`repeat(${t.fields.size+1}, 1fr)`}},m("thead",null,m("tr",null,m("th",null,"Title"),[...t.fields].map(r=>m("th",null,r)))),m("tbody",null,n.children.map(r=>m("tr",null,m("td",null,m(V,{key:r.id,workbench:i,path:e.append(r)})),[...t.fields].map(s=>m("td",null,o(r,s)))))))}};var Pe={view({attrs:{workbench:i,path:e},state:t}){let n=e.node;t.tabs=t.tabs===void 0?new Set:t.tabs,t.selectedTab=t.selectedTab===void 0?"":t.selectedTab,n.children.forEach(s=>{t.tabs.add(s.raw),t.selectedTab===""&&(t.selectedTab=s.raw.ID)});let o=a(s=>{t.selectedTab=s},"handleTabClick"),r=n.children.find(s=>s.id===t.selectedTab);return m("div",{class:"tabs-view"},m("div",{class:"tabs"},[...t.tabs].map(s=>m("div",{class:s.ID===t.selectedTab?"active":"",onclick:()=>o(s.ID)},s.Name)),m("div",{style:{flexGrow:1}})),m("div",{class:"tab-content"},m(ae(r),{workbench:i,path:e.append(r)})))}};var Fe={view({attrs:{workbench:i,path:e,alwaysShowNew:t}}){let n=e.node;e.node.refTo&&(n=e.node.refTo);let o=!1;return(n.childCount===0&&n.getLinked("Fields").length===0||t)&&(o=!0),m("div",{class:"document-view"},m("div",{class:"fields"},n.getLinked("Fields").length>0&&n.getLinked("Fields").map(r=>m(V,{key:r.id,workbench:i,path:e.append(r)}))),m("div",{class:"children"},n.childCount>0&&n.children.map(r=>m(V,{key:r.id,workbench:i,path:e.append(r)})),o&&m(ce,{workbench:i,path:e})))}};var $=class{constructor(){this.url="https://example.com"}childrenView(){return nt}handleIcon(e=!1){return m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"node-bullet"},m("circle",{cx:"12",cy:"12",r:"10"}),m("line",{x1:"2",y1:"12",x2:"22",y2:"12"}),m("path",{d:"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"}))}static initialize(e){e.commands.registerCommand({id:"make-iframe",title:"Make Inline Frame",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)),action:t=>{let n=new $;(t.node.name.startsWith("http://")||t.node.name.startsWith("https://"))&&(n.url=t.node.name,t.node.addComponent(n),e.defocus(),t.node.name=t.node.name.replaceAll("https://","").replaceAll("http://"),e.workspace.setExpanded(t.path.head,t.node,!0),e.focus(t.path))}})}};a($,"InlineFrame"),$=N([S],$);var nt={view({attrs:{path:i}}){let e=i.node.getComponent($);return m("div",{class:"iframe-view"},m("iframe",{src:e.url,style:{width:"100%",height:"500px",border:"0",marginLeft:"-0.5rem"}}))}};function ue(i,e){for(let t of e){let n=i.componentField(t);if(n)return n}return null}a(ue,"tryFields");var Re={view({attrs:{workbench:i,path:e}}){let t=e.node;return m("div",{class:"cards-view flex flex-row",style:{gap:"1rem",paddingBottom:"1rem",flexWrap:"wrap"}},t.children.map(n=>{let o=ue(n,["linkURL"]),r=ue(n,["updatedAt","createdAt"]),s=ue(n,["updatedBy","createdBy","username"]),l=ue(n,["thumbnailURL","coverURL"]),d=n.getComponent($),c=m("img",{style:{objectFit:"fill",objectPosition:"center",width:"12rem",height:"9rem"},src:l});return d&&(c=m("div",{style:{width:"120rem",height:"90rem",transform:"scale(0.1)",pointerEvents:"none",transformOrigin:"0 0"}},m("iframe",{src:d.url,style:{border:"0",width:"100%",height:"100%"}}))),m("div",{style:{border:"1px solid gray",overflow:"hidden",borderRadius:"0.5rem",paddingBottom:"0.5rem",width:"12rem"}},m("div",{style:{position:"relative",overflow:"hidden",width:"12rem",height:"9rem"}},o?m("a",{href:o},c):c),m("div",{style:{padding:"0.5rem",paddingBottom:"0.25rem"}},o?m("a",{href:o},n.name):n.name),s&&m("div",{style:{padding:"0.5rem",paddingTop:"0",paddingBottom:"0.75rem",color:"#aaa"}},s),r&&m("div",{style:{padding:"0.5rem",paddingTop:"0",paddingBottom:"0.75rem",color:"#aaa"}},ot(r)))}))}};function ot(i){if(!(i instanceof Date))throw new Error("Input must be a valid Date object.");let e=new Date,t=Math.floor((e.getTime()-i.getTime())/1e3),n={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[o,r]of Object.entries(n)){let s=Math.floor(t/r);if(s>0)return`${s} ${o}${s>1?"s":""} ago`}return"just now"}a(ot,"timeAgo");var Te={list:Le,table:Ae,tabs:Pe,document:Fe,cards:Re};function ae(i){return Te[i.getAttr("view")||"list"]||Ee}a(ae,"getNodeView");window.registerView=(i,e)=>{Te[i]=e,workbench.commands.registerCommand({id:`view-${i}`,title:`View as ${rt(i)}`,action:t=>{t.node&&t.node.setAttr("view",i)}})};function rt(i){return i.replace(/\w\S*/g,function(e){return e.charAt(0).toUpperCase()+e.substr(1).toLowerCase()})}a(rt,"toTitleCase");var pe={view({attrs:{workbench:i,path:e,alwaysShowNew:t}}){return W(e.node,"childrenView")?m(X(e.node,"childrenView")[0].childrenView(),{workbench:i,path:e}):m(ae(e.node),{workbench:i,path:e,alwaysShowNew:t})}},V={view({attrs:i,state:e,children:t}){let{path:n,workbench:o}=i,r=n.node,s=!1,l=r;r.refTo&&(s=!0,r=l.refTo);let d=!1;o.clipboard&&o.clipboard.op==="cut"&&o.clipboard.node.id===r.id&&(d=!0);let c=o.workspace.getExpanded(n.head,l),h=W(r,"handlePlaceholder")?se(r,"handlePlaceholder"):"",p=a(u=>{e.hover=!0,u.stopPropagation()},"hover"),C=a(u=>{e.hover=!1,u.stopPropagation()},"unhover"),A=a(()=>{e.tagPopover&&(o.closePopover(),e.tagPopover=void 0)},"cancelTagPopover"),k=a(u=>{e.tagPopover?(e.tagPopover.oninput(u),u.target.value.includes("#")||A()):u.target.value.includes("#")&&(e.tagPopover={},F.showPopover(o,n,r,(I,w)=>{e.tagPopover={onkeydown:I,oninput:w}},A))},"oninput"),v=a(u=>{if(e.tagPopover){if(u.key==="Escape"){A();return}if(e.tagPopover.onkeydown(u)===!1)return u.stopPropagation(),!1}let I=u.shiftKey||u.metaKey||u.altKey||u.ctrlKey;switch(u.key){case"ArrowUp":u.target.selectionStart!==0&&!I&&u.stopPropagation();break;case"ArrowDown":u.target.selectionStart!==u.target.value.length&&u.target.selectionStart!==0&&!I&&u.stopPropagation();break;case"Backspace":if(u.target.value===""){if(u.preventDefault(),u.stopPropagation(),r.childCount>0)return;o.executeCommand("delete",{node:r,path:n,event:u});return}if(u.target.value!==""&&u.target.selectionStart===0&&u.target.selectionEnd===0){if(u.preventDefault(),u.stopPropagation(),r.childCount>0)return;let w=o.workspace.findAbove(n);if(!w)return;let y=w.node.name;w.node.name=y+u.target.value,r.destroy(),m.redraw.sync(),o.focus(w,y.length);return}break;case"Enter":if(u.preventDefault(),u.ctrlKey||u.shiftKey||u.metaKey||u.altKey)return;if(u.target.value.startsWith("```")&&!r.hasComponent(M)){let w=u.target.value.slice(3);if(w){o.executeCommand("make-code-block",{node:r,path:n},w),u.stopPropagation();return}}if(u.target.selectionStart===u.target.value.length){r.childCount>0&&o.workspace.getExpanded(n.head,r)?o.executeCommand("insert-child",{node:r,path:n},"",0):o.executeCommand("insert",{node:r,path:n}),u.stopPropagation();return}if(u.target.selectionStart===0){o.executeCommand("insert-before",{node:r,path:n}),u.stopPropagation();return}if(u.target.selectionStart>0&&u.target.selectionStart{r.name=u.target.value.slice(0,u.target.selectionStart)}),u.stopPropagation();return}break}},"onkeydown"),f=a(u=>{u.preventDefault(),u.stopPropagation(),o.executeCommand("zoom",{node:r,path:n}),document.selection&&document.selection.empty?document.selection.empty():window.getSelection&&window.getSelection().removeAllRanges()},"open"),b=a(u=>{if(r.hasComponent(x)){f(u);return}c?o.executeCommand("collapse",{node:l,path:n}):o.executeCommand("expand",{node:l,path:n}),u.stopPropagation()},"toggle"),g=a(u=>u.childCount+u.getLinked("Fields").length,"subCount"),T=a(()=>{if(r.id===o.context?.node?.id||e.hover||r.name.length>0||h.length>0)return!0},"showHandle");return m("div",{onmouseover:p,onmouseout:C,id:`node-${n.id}-${l.id}`,class:d?"cut-node":""},m("div",{class:"node-row-outer-wrapper flex flex-row items-start"},m("svg",{class:"node-menu shrink-0",xmlns:"http://www.w3.org/2000/svg",onclick:u=>o.showMenu(u,{node:l,path:n}),oncontextmenu:u=>o.showMenu(u,{node:l,path:n}),"data-menu":"node",viewBox:"0 0 16 16"},e.hover&&m("path",{style:{transform:"translateY(-1px)"},fill:"currentColor","fill-rule":"evenodd",d:"M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"})),m("div",{class:"node-handle shrink-0",onclick:b,ondblclick:f,oncontextmenu:u=>o.showMenu(u,{node:l,path:n}),"data-menu":"node",style:{display:T()?"block":"none"}},W(r,"handleIcon")?se(r,"handleIcon",g(r)>0&&!c):m("svg",{class:"node-bullet",viewBox:"0 0 16 16",xmlns:"http://www.w3.org/2000/svg"},g(r)>0&&!c?m("circle",{id:"node-collapsed-handle",cx:"8",cy:"8",r:"8"}):null,m("circle",{cx:"8",cy:"8",r:"3",fill:"currentColor"}),",",s?m("circle",{id:"node-reference-handle",cx:"8",cy:"8",r:"7",fill:"none","stroke-width":"1",stroke:"currentColor","stroke-dasharray":"3,3"}):null)),r.raw.Rel==="Fields"?m("div",{class:"flex grow items-start flex-row"},m("div",null,m(q,{workbench:o,path:n,onkeydown:v,oninput:k})),m(q,{editValue:!0,workbench:o,path:n,onkeydown:v,oninput:k})):m("div",{class:"flex grow items-start flex-row",style:{gap:"0.5rem"}},W(r,"beforeEditor")&&X(r,"beforeEditor").map(u=>m(u.beforeEditor(),{node:r,component:u})),m(q,{workbench:o,path:n,onkeydown:v,oninput:k,placeholder:h}),W(r,"afterEditor")&&X(r,"afterEditor").map(u=>m(u.afterEditor(),{node:r,component:u})))),W(r,"belowEditor")&&X(r,"belowEditor").map(u=>m(u.belowEditor(),{node:r,component:u,expanded:c})),c===!0&&m("div",{class:"expanded-node flex flex-row"},m("div",{class:"indent flex",onclick:b}),m("div",{class:"view grow"},W(r,"childrenView")?m(X(r,"childrenView")[0].childrenView(),{workbench:o,path:n}):m(ae(r),{workbench:o,path:n}))))}};var Be={view({attrs:{workbench:i,node:e}}){let t=new O(e,"quickadd");return m("div",{class:"notice"},m("h3",null,"Quick Add"),m(pe,{workbench:i,path:t,alwaysShowNew:!0}),m("div",{class:"button-bar"},m("button",{class:"primary",onclick:()=>{i.commitQuickAdd(),i.closeDialog()}},"Add to Today")))}};var je={view({attrs:{workbench:i},state:e}){let t=i.workspace.settings.theme;return e.selectedTheme=e.selectedTheme===void 0?t:e.selectedTheme,m("div",{class:"notice"},m("h3",null,"Settings"),m("div",{class:"flex flex-row"},m("div",{class:"grow"},"Theme"),m("div",null,m("select",{name:"theme",oninput:a(o=>{e.selectedTheme=o.target.value},"oninput")},m("option",{selected:e.selectedTheme==="",value:""},"Light"),m("option",{selected:e.selectedTheme==="darkmode",value:"darkmode"},"Dark"),m("option",{selected:e.selectedTheme==="sepia",value:"sepia"},"Sepia"),m("option",{selected:e.selectedTheme==="sublime",value:"sublime"},"Sublime")))),m("div",{class:"button-bar"},m("button",{onclick:()=>{i.closeDialog()}},"Cancel"),m("button",{class:"primary",onclick:async o=>{t!==e.selectedTheme?(i.workspace.settings.theme=e.selectedTheme,await i.workspace.save(!0),location.reload()):i.closeDialog()}},"Save Changes")))}};var Oe={view(){return m("div",{class:"notice"},m("h3",null,"Refresh to view latest updates"),m("p",null,"Your notes were updated in another browser session. Refresh the page to view the latest version."),m("div",{class:"button-bar"},m("button",{class:"primary",onclick:()=>{location.reload()}},"Refresh Now")))}},Me={view({attrs:{workbench:i}}){return m("div",{class:"notice"},m("h3",null,"Treehouse is under active development"),m("p",null,"This is a preview based on our main branch, which is actively being developed."),m("p",null,"If you find a bug, please report it via \xA0",m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 14",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-menu"},m("line",{x1:"3",y1:"12",x2:"21",y2:"12"}),m("line",{x1:"3",y1:"6",x2:"21",y2:"6"}),m("line",{x1:"3",y1:"18",x2:"21",y2:"18"})),"\xA0> ",m("strong",null,"Submit Issue"),"."),m("p",null,"Data is stored using localstorage, which you can reset via \xA0",m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 14",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-menu"},m("line",{x1:"3",y1:"12",x2:"21",y2:"12"}),m("line",{x1:"3",y1:"6",x2:"21",y2:"6"}),m("line",{x1:"3",y1:"18",x2:"21",y2:"18"})),"\xA0> ",m("strong",null,"Reset Demo"),"."),m("div",{class:"button-bar"},m("button",{class:"primary",onclick:()=>{localStorage.setItem("firsttime","1"),i.closeDialog()}},"Got it")))}},He={view({attrs:{workbench:i,finished:e}}){return m("div",{class:"notice"},m("h3",null,"Login with GitHub"),m("p",null,"The GitHub backend is experimental so use at your own risk!"),m("p",null,"To store your workbench we will create a public repository called ",m("pre",{style:{display:"inline"}},".treehouse.sh")," if it doesn't already exist. You can manually make this repository private via GitHub if you want."),m("p",null,"You can Logout via the \xA0",m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 14",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-menu"},m("line",{x1:"3",y1:"12",x2:"21",y2:"12"}),m("line",{x1:"3",y1:"6",x2:"21",y2:"6"}),m("line",{x1:"3",y1:"18",x2:"21",y2:"18"})),"\xA0 menu in the top right to return to the localstorage backend."),m("div",{class:"button-bar"},m("button",{onclick:()=>{i.closeDialog()}},"Cancel"),m("button",{class:"primary",onclick:()=>{i.closeDialog(),localStorage.setItem("github","1"),e()}},"Log in with GitHub")))}};var _=class{constructor(e){this.commands=new re,this.keybindings=new oe,this.menus=new ie,this.backend=e,this.workspace=new ee(e.files,e.changes),this.context={node:null},this.panels=[],this.drawer={open:!1},this.dialog={body:()=>null},this.menu={body:()=>null}}get mainPanel(){return this.panels[0]}async initialize(){if(await this.workspace.load(),this.workspace.rawNodes.forEach(e=>this.backend.index.index(e)),this.workspace.observe(e=>{this.workspace.save(),e.isDestroyed?this.backend.index.remove(e.id):(this.backend.index.index(e.raw),e.components.forEach(t=>this.backend.index.index(t.raw)))}),this.workspace.lastOpenedID?this.openNewPanel(this.workspace.find(this.workspace.lastOpenedID)||this.workspace.mainNode()):this.openNewPanel(this.workspace.mainNode()),this.backend.loadExtensions&&await this.backend.loadExtensions(),this.workspace.settings.theme){let e=document.createElement("link");e.setAttribute("href",`https://treehouse.sh/style/themes/${this.workspace.settings.theme}.css`),e.setAttribute("rel","stylesheet"),e.setAttribute("type","text/css"),document.head.appendChild(e)}m.redraw()}authenticated(){return this.backend.auth&&this.backend.auth.currentUser()}openQuickAdd(){let e=this.workspace.find("@quickadd");e||(e=this.workspace.new("@quickadd")),this.showDialog(()=>m(Be,{workbench:this,node:e}),!0),setTimeout(()=>{document.querySelector("main > dialog .new-node input").focus()},1)}commitQuickAdd(){let e=this.workspace.find("@quickadd");if(!e)return;let t=this.todayNode();e.children.forEach(n=>n.parent=t)}clearQuickAdd(){let e=this.workspace.find("@quickadd");e&&e.children.forEach(t=>t.destroy())}todayNode(){let e=new Date,t=e.toUTCString().split(e.getFullYear())[0],n=`Week ${String(it(e)).padStart(2,"0")}`,r=["@calendar",`${e.getFullYear()}`,n,t].join("/"),s=this.workspace.find(r);return s||(s=this.workspace.new(r)),s}openToday(){this.open(this.todayNode())}open(e){this.workspace.expanded[e.id]||(this.workspace.expanded[e.id]={}),this.workspace.lastOpenedID=e.id,this.workspace.save();let t=new O(e);this.panels[0]=t,this.context.path=t}openNewPanel(e){this.workspace.expanded[e.id]||(this.workspace.expanded[e.id]={}),this.workspace.lastOpenedID=e.id,this.workspace.save();let t=new O(e);this.panels.push(t),this.context.path=t}closePanel(e){this.panels=this.panels.filter(t=>t.name!==e.name)}defocus(){let e=this.getInput(this.context.path);e&&e.blur(),this.context.node=null,this.context.path=null}focus(e,t=0){let n=this.getInput(e);n?(this.context.path=e,n.focus(),t!==void 0&&n.setSelectionRange(t,t)):console.warn("unable to find input for",e)}getInput(e){let t=`input-${e.id}-${e.node.id}`;e.node.raw.Rel==="Fields"&&e.node.name!==""&&(t=t+"-value");let n=document.getElementById(t);return n.editor?n.editor:n}canExecuteCommand(e,t,...n){return t=this.newContext(t),this.commands.canExecuteCommand(e,t,...n)}executeCommand(e,t,...n){return t=this.newContext(t),console.log(e,t,...n),this.commands.executeCommand(e,t,...n)}newContext(e){return Object.assign({},this.context,e)}showMenu(e,t,n){e.stopPropagation(),e.preventDefault();let o=e.target.closest("*[data-menu]"),r=o.getBoundingClientRect();if(!n){let d=o.dataset.align||"left";n={top:`${document.body.scrollTop+r.y+r.height}px`},d==="right"?(n.marginLeft="auto",n.marginRight=`${document.body.offsetWidth-r.right}px`):(n.marginLeft=`${document.body.scrollLeft+r.x}px`,n.marginRight="auto")}let s=this.menus.menus[o.dataset.menu],l=s.filter(d=>d.command).map(d=>this.commands.commands[d.command]);s&&(this.menu={body:()=>m(Ne,{workbench:this,ctx:this.newContext(t),items:s,commands:l}),style:n},m.redraw(),setTimeout(()=>{document.querySelector("main > dialog.menu").showModal()},0))}closeMenu(){document.querySelector("main > dialog.menu").close(),workbench.menu.body=()=>null}showPalette(e,t,n){this.showDialog(()=>m(Se,{workbench:this,ctx:n}),!1,{left:`${e}px`,top:`${t}px`})}showNotice(e,t){this.showDialog(()=>m({firsttime:Me,github:He,lockstolen:Oe}[e],{workbench:this,finished:t}),!0,void 0,e==="lockstolen")}toggleDrawer(){this.drawer.open=!this.drawer.open,m.redraw()}showSettings(){this.showDialog(()=>m(je,{workbench:this}),!0)}showPopover(e,t){this.popover={body:e,style:t},m.redraw()}closePopover(){this.popover=null,m.redraw()}showDialog(e,t,n,o){this.dialog={body:e,backdrop:t,style:n,explicitClose:o},m.redraw(),setTimeout(()=>{document.querySelector("main > dialog.modal").showModal()},0)}isDialogOpen(){return document.querySelector("main > dialog.modal").hasAttribute("open")}closeDialog(){document.querySelector("main > dialog.modal").close(),this.dialog.body=()=>null}search(e){if(!e)return[];let t=e.split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/),n=t.filter(l=>!l.includes(":")).join(" "),o=Object.fromEntries(t.filter(l=>l.includes(":")).map(l=>l.toLowerCase().split(":")));!n&&Object.keys(o).length>0&&(n=Object.keys(o)[0]);let r=a(l=>{if(Object.keys(o).length>0){let d={};for(let c of l.getLinked("Fields"))d[c.name.toLowerCase()]=c.value.toLowerCase();for(let c in o){let h=d[c.replace(/['"]/g,"")];if(!h||h!==o[c].replace(/['"]/g,""))return!1}}return!0},"passFieldQuery");if(n.startsWith("#"))return F.findTagged(this.workspace,n.replace("#","")).filter(r);let s={};return this.backend.index.search(n).forEach(l=>{let d=window.workbench.workspace.find(l);d&&(d.value&&(d=d.parent,!d.raw)||r(d)&&(s[d.id]=d))}),Object.values(s)}};a(_,"Workbench");function it(i){var e=new Date(Date.UTC(i.getFullYear(),i.getMonth(),i.getDate())),t=e.getUTCDay()||7;e.setUTCDate(e.getUTCDate()+4-t);var n=new Date(Date.UTC(e.getUTCFullYear(),0,1));return Math.ceil(((e-n)/864e5+1)/7)}a(it,"getWeekOfYear");function $e(i){function e(w,y){var E=w<>>32-y;return E}a(e,"rotate_left");function t(w){var y="",E,B,ke;for(E=0;E<=6;E+=2)B=w>>>E*4+4&15,ke=w>>>E*4&15,y+=B.toString(16)+ke.toString(16);return y}a(t,"lsb_hex");function n(w){var y="",E,B;for(E=7;E>=0;E--)B=w>>>E*4&15,y+=B.toString(16);return y}a(n,"cvt_hex");function o(w){w=w.replace(/\r\n/g,` +`);for(var y="",E=0;E127&&B<2048?(y+=String.fromCharCode(B>>6|192),y+=String.fromCharCode(B&63|128)):(y+=String.fromCharCode(B>>12|224),y+=String.fromCharCode(B>>6&63|128),y+=String.fromCharCode(B&63|128))}return y}a(o,"Utf8Encode");var r,s,l,d=new Array(80),c=1732584193,h=4023233417,p=2562383102,C=271733878,A=3285377520,k,v,f,b,g,I;i=o(i);var T=i.length,u=new Array;for(s=0;s>>29),u.push(T<<3&4294967295),r=0;re.id)].join(":"))}get node(){return this.nodes[this.nodes.length-1]}get previous(){return this.nodes.length<2?null:this.nodes[this.nodes.length-2]}get head(){return this.nodes[0]}};a(O,"Path");var te=class{constructor(){this.nodes={"@root":{ID:"@root",Name:"@root",Linked:{Children:[],Components:[]},Attrs:{}}},this.observers=[]}changed(e){this.observers.forEach(t=>t(e))}import(e){for(let t of e)t.Value&&Z(t.Name)&&(t.Value=De(t.Name,t.Value),t.Rel="Components"),this.nodes[t.ID]=t;for(let t of e){if(t.Parent==="@tmp"){delete this.nodes[t.ID];continue}if(!t.ID.startsWith("@")&&t.Parent===void 0){delete this.nodes[t.ID];continue}if(t.Parent&&!this.nodes[t.Parent]){delete this.nodes[t.ID];continue}let n=this.find(t.ID);if(n){if(n.parent&&!n.parent.raw){delete this.nodes[t.ID];continue}Q(n,"onAttach",n)}}}export(){let e=[];for(let t of Object.values(this.nodes))e.push(t);return e}make(e,t){let n=null;if(e.includes("/")){let s=e.split("/");n=this.find(s[0]);for(let l=1;le.Parent===void 0).map(e=>new R(this,e.ID))}root(e){e=e||"@root";let t=this.roots().find(n=>n.name===e);return t===void 0?null:t}find(e){let t=this.nodes[e];if(t)return new R(this,t.ID);let n=e.split("/");if(n.length===1&&n[0].startsWith("@"))return null;let o=this.root(n[0]);if(!o&&this.nodes[n[0]]&&(o=new R(this,this.nodes[n[0]].ID)),o?n.shift():o=this.root("@root"),!o)return null;let r=a((s,l)=>(s.refTo&&(s=s.refTo),s.children.find(d=>d.name===l)),"findChild");for(let s of n){let l=r(o,s);if(!l)return null;o=l}return o}walk(e,t){for(let n of this.roots())if(n.walk(e,t))return}observe(e){this.observers.push(e)}};a(te,"Bus");var st=a(()=>{let i=Date.now().toString(36),e=Math.random().toString(36).substring(2);return i+e},"uniqueId");var R=class{constructor(e,t){this._bus=e,this._id=t}[Symbol.for("Deno.customInspect")](){return`Node[${this.id}:${this.name}]`}get id(){return this._id}get bus(){return this._bus}get raw(){let e=this._bus.nodes[this.id];if(!e)throw`use of non-existent node ${this.id}`;return e}get name(){return this.refTo?this.refTo.name:this.raw.Name}set name(e){this.refTo?this.refTo.name=e:this.raw.Name=e,this.changed()}get value(){return this.refTo?this.refTo.value:this.raw.Value}set value(e){this.refTo?this.refTo.value=e:this.raw.Value=e,this.changed()}get parent(){return!this.raw.Parent||!this._bus.nodes[this.raw.Parent]?null:new R(this._bus,this.raw.Parent)}set parent(e){let t=this.parent;t!==null&&t.raw.Linked.Children.splice(this.siblingIndex,1),e!==null?(this.raw.Parent=e.id,e.raw.Linked.Children.push(this.id),Q(e,"onAttach",e)):this.raw.Parent=void 0,this.changed()}get refTo(){let e=this.raw.Attrs.refTo;return!e||!this._bus.nodes[e]?null:new R(this._bus,e)}set refTo(e){if(!e){delete this.raw.Attrs.refTo,this.changed();return}this.raw.Attrs.refTo=e.id,this.changed()}get siblingIndex(){let e=this.parent;if(e===null)return 0;let t=this.raw.Rel||"Children";return e.raw.Linked[t].findIndex(n=>n===this.id)}set siblingIndex(e){let t=this.parent;if(t===null)return;let n=this.raw.Rel||"Children";t.raw.Linked[n].splice(this.siblingIndex,1),t.raw.Linked[n].splice(e,0,this.id),t.changed()}get prevSibling(){let e=this.parent;if(e===null||this.siblingIndex===0)return null;let t=this.raw.Rel||"Children";return e.getLinked(t)[this.siblingIndex-1]}get nextSibling(){let e=this.parent;if(e===null||this.siblingIndex===e.children.length-1)return null;let t=this.raw.Rel||"Children";return e.getLinked(t)[this.siblingIndex+1]}get ancestors(){let e=[],t=this.parent;for(;t!==null;)e.push(t),t=t.parent;return e}get isDestroyed(){return!this._bus.nodes.hasOwnProperty(this.id)}get path(){let e=this,t=[];for(;e;)t.unshift(e.name),e=e.parent;return t.join("/")}get children(){if(this.refTo)return this.refTo.children;let e=[];this.raw.Linked.Children&&(e=this.raw.Linked.Children.map(t=>new R(this._bus,t)));for(let t of this.components)if(J(t,"objectChildren"))return Q(t,"objectChildren",this,e);return e}get childCount(){if(this.refTo)return this.refTo.childCount;for(let e of this.components)if(J(e,"objectChildren"))return Q(e,"objectChildren",this,null).length;return this.raw.Linked.Children?this.raw.Linked.Children.length:0}addChild(e){if(this.refTo){this.refTo.addChild(e);return}this.raw.Linked.Children.push(e.id),this.changed()}removeChild(e){if(this.refTo){this.refTo.removeChild(e);return}let t=this.raw.Linked.Children.filter(n=>n===e.id);this.raw.Linked.Children=t,this.changed()}get fields(){return this.raw.Linked.Fields?this.raw.Linked.Fields.map(e=>new R(this._bus,e)):[]}get fieldCount(){return this.raw.Linked.Fields?this.raw.Linked.Fields.length:0}get components(){return this.raw.Linked.Components?this.raw.Linked.Components.map(e=>new R(this._bus,e)):[]}get componentCount(){return this.raw.Linked.Components?this.raw.Linked.Components.length:0}componentField(e){for(let t of this.components)if(Object.keys(t.value||{}).includes(e))return t.value[e];return null}addComponent(e){let t=this.bus.make(K(e),e);t.raw.Parent=this.id,t.raw.Rel="Components",this.raw.Linked.Components.push(t.id),Q(t,"onAttach",t),this.changed()}removeComponent(e){let t;e.name&&Z(e)?t=this.components.filter(n=>n.name===K(e)):t=this.components.filter(n=>n.value===e),t.length>0&&t[0].destroy(),this.changed()}hasComponent(e){return this.components.filter(n=>n.name===K(e)).length>0}getComponent(e){let t=this.components.filter(n=>n.name===K(e));return t.length>0?t[0].value:null}getLinked(e){return this.raw.Linked[e]?this.raw.Linked[e].map(t=>new R(this._bus,t)):[]}addLinked(e,t){this.raw.Linked[e]||(this.raw.Linked[e]=[]),t.raw.Rel=e,this.raw.Linked[e].push(t.id),this.changed()}removeLinked(e,t){this.raw.Linked[e]||(this.raw.Linked[e]=[]);let n=this.raw.Linked[e].filter(o=>o===t.id);this.raw.Linked[e]=n,this.changed()}moveLinked(e,t,n){this.raw.Linked[e]||(this.raw.Linked[e]=[]);let o=this.raw.Linked[e].findIndex(s=>s===t.id);if(o===-1)return;let r=this.raw.Linked[e];r.splice(n,0,r.splice(o,1)[0]),this.raw.Linked[e]=r,this.changed()}getAttr(e){return this.raw.Attrs[e]||""}setAttr(e,t){this.raw.Attrs[e]=t,this.changed()}find(e){return this.bus.find([this.path,e].join("/"))}walk(e,t){if(t=t||{followRefs:!1,includeComponents:!1},e(this))return!0;let n=this.children;if(this.refTo&&t.followRefs){if(e(this.refTo))return!0;n=this.refTo.children}for(let o of n)if(o.walk(e,t))return!0;if(t.includeComponents){for(let o of this.components)if(o.walk(e,t))return!0}return!1}destroy(){if(this.isDestroyed)return;if(this.refTo){this._bus.destroy(this);return}let e=[];this.walk(t=>(e.push(t),!1),{followRefs:!1,includeComponents:!0}),e.reverse().forEach(t=>this._bus.destroy(t))}duplicate(){let e=this._bus.make(this.name,Ie(this.value));return e.raw.Rel=this.raw.Rel,this.fields.map(t=>t.duplicate()).forEach(t=>{e.addLinked("Fields",t),t.raw.Parent=e.raw.ID}),this.components.map(t=>t.duplicate()).forEach(t=>{e.addLinked("Components",t),t.raw.Parent=e.raw.ID}),this.children.map(t=>t.duplicate()).forEach(t=>{e.addChild(t),t.raw.Parent=e.raw.ID}),e}changed(){this._bus.changed(this)}};a(R,"Node");var ee=class{constructor(e,t){this.fs=e,this.bus=new te,this.expanded={},this.settings={},t&&t.registerNotifier(this.reload.bind(this)),this.writeDebounce=dt(async(n,o)=>{try{await this.fs.writeFile(n,o),console.log("Saved workspace.")}catch(r){console.error(r),document.dispatchEvent(new CustomEvent("BackendError"))}})}get rawNodes(){return this.bus.export()}observe(e){this.bus.observe(e)}async save(e){let t=JSON.stringify({version:1,lastopen:this.lastOpenedID,expanded:this.expanded,nodes:this.rawNodes,settings:this.settings},null,2);e?await this.fs.writeFile("workspace.json",t):this.writeDebounce("workspace.json",t)}migrateRawNode(e){return e.Name==="treehouse.SearchNode"&&(e.Name="treehouse.SmartNode"),e}async reload(e){let t=JSON.parse(await this.fs.readFile("workspace.json")||"{}");t.nodes&&(t.nodes=t.nodes.filter(n=>e.includes(n.ID)),t.nodes=t.nodes.map(this.migrateRawNode),this.bus.import(t.nodes),m.redraw(),console.log(`Reloaded ${t.nodes.length} nodes.`))}async load(){let e=JSON.parse(await this.fs.readFile("workspace.json")||"{}");if(e.nodes&&(e.nodes=e.nodes.map(this.migrateRawNode),this.bus.import(e.nodes),console.log(`Loaded ${e.nodes.length} nodes.`)),e.expanded)for(let t in e.expanded)for(let n in e.expanded[t])this.bus.find(n)&&(this.expanded[t]||(this.expanded[t]={}),this.expanded[t][n]=e.expanded[t][n]);e.lastopen&&(this.lastOpenedID=e.lastopen),e.settings&&(this.settings=Object.assign(this.settings,e.settings))}mainNode(){let e=this.bus.find("@workspace");if(!e){console.info("Building missing workspace node.");let t=this.bus.find("@root"),n=this.bus.make("@workspace");n.name="Workspace",n.parent=t;let o=this.bus.make("@calendar");o.name="Calendar",o.parent=n;let r=this.bus.make("Home");r.parent=n,e=n}return e}find(e){return this.bus.find(e)}new(e,t){return this.bus.make(e,t)}getExpanded(e,t){this.expanded[e.id]||(this.expanded[e.id]={});let n=this.expanded[e.id][t.id];return n===void 0&&(n=!1),n}setExpanded(e,t,n){this.expanded[e.id]||(this.expanded[e.id]={}),this.expanded[e.id][t.id]=n,this.save()}findAbove(e){if(e.node.id===e.head.id)return null;let t=e.clone();t.pop();let n=e.node.prevSibling;if(!n){let r=e.previous.getLinked("Fields").length;return e.node.raw.Rel!=="Fields"&&r>0?t.append(e.previous.getLinked("Fields")[r-1]):t}let o=a(r=>{if(!this.getExpanded(e.head,r.node))return r;let l=r.node.getLinked("Fields").length;if(r.node.childCount===0&&l>0){let c=r.node.getLinked("Fields")[l-1];return o(r.append(c))}if(r.node.childCount===0)return r;let d=r.node.children[r.node.childCount-1];return o(r.append(d))},"lastSubIfExpanded");return o(t.append(n))}findBelow(e){let t=e.clone();if(this.getExpanded(e.head,e.node)&&e.node.getLinked("Fields").length>0)return t.append(e.node.getLinked("Fields")[0]);if(this.getExpanded(e.head,e.node)&&e.node.childCount>0)return t.append(e.node.children[0]);let n=a(o=>{let r=o.node.nextSibling;if(r)return o.pop(),o.append(r);let s=o.previous;return s?o.node.raw.Rel==="Fields"&&s.childCount>0?(o.pop(),o.append(s.children[0])):(o.pop(),n(o)):null},"nextSiblingOrParentNextSibling");return n(t)}};a(ee,"Workspace");function dt(i,e=3e3){let t;return(...n)=>{clearTimeout(t),t=setTimeout(()=>{i.apply(this,n)},e)}}a(dt,"debounce");var We={view({attrs:i,children:e}){let t=i.open;return m("div",{class:`drawer ${t?"open":"closed"}`},e)}};var Ue={view({attrs:i}){let e=i.path,t=i.workbench,n=e.node,o=a(c=>{t.executeCommand("close-panel",{},e)},"close"),r=a(c=>{e.pop()===e.node&&e.pop()},"goBack"),s=a(c=>{t.panels=[e],t.context.path=e},"maximize");function l(c=""){return 20+(c.match(/\n/g)||[]).length*20}a(l,"calcHeight");let d="";return n.getAttr("view")&&(d=`${n.getAttr("view")}-panel`),m("div",{class:`panel flex flex-col grow ${d}`},m("div",{class:"bar flex"},e.length>1?m("div",{class:"panel-back",style:{rightPadding:"var(--padding)"}},m("svg",{onclick:r,xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",fill:"currentColor",viewBox:"0 0 16 16"},m("path",{"fill-rule":"evenodd",d:"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"}))):null,m("div",{class:"panel-back-parent grow"},n.parent&&n.parent.id!=="@root"?m("span",{style:{cursor:"pointer"},onclick:()=>t.open(n.parent)},n.parent.name):m("span",null,"\xA0")),t.panels.length>1?m("div",{class:"panel-icons flex items-center"},m("svg",{onclick:s,style:{cursor:"pointer"},xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-maximize-2"},m("polyline",{points:"15 3 21 3 21 9"}),m("polyline",{points:"9 21 3 21 3 15"}),m("line",{x1:"21",y1:"3",x2:"14",y2:"10"}),m("line",{x1:"3",y1:"21",x2:"10",y2:"14"})),m("svg",{onclick:o,style:{cursor:"pointer"},xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-x"},m("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),m("line",{x1:"6",y1:"6",x2:"18",y2:"18"}))):null),m("div",{class:"body flex flex-col"},m("div",{class:"title-node",oncontextmenu:c=>t.showMenu(c,{node:n,path:e}),"data-menu":"node"},m(q,{workbench:t,path:e,disallowEmpty:!0})),m(pe,{workbench:t,path:e.sub(),alwaysShowNew:!0})))}};var qe={view({attrs:i}){let e=i.workbench,t={"":["pick-command"],Edit:["cut","copy","copy-reference","paste","mark-done","insert","delete"],Navigate:["expand","collapse","indent","outdent","move-up","move-down","prev","next"]},n=a(o=>{let r=e.keybindings.getBinding(o.id);return r?Y(r.key).join(" ").toUpperCase():""},"getBindingSymbols");return m("div",{class:"reference"},m("h2",null,"Keyboard Shortcuts"),Object.entries(t).map(([o,r])=>m("div",null,o.length!==0&&m("h3",null,o),m("div",null,r.map(s=>e.commands.commands[s]).map(s=>m("div",{class:"flex item"},m("div",{class:"keybindings text-left"},n(s)),m("div",{class:"grow"},s.title)))))))}};var ve={view({attrs:{input:i,workbench:e}}){return m("div",{class:"search"},m(G,{onpick:a(o=>{e.closeDialog(),e.open(o)},"onpick"),onchange:a(o=>{o.input?o.items=e.search(o.input):o.items=[]},"onchange"),input:i,inputview:(o,r,s)=>m("div",{class:"flex items-center"},m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-search shrink-0 items-center"},m("circle",{cx:"11",cy:"11",r:"8"}),m("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"})),m("input",{type:"text",placeholder:"Search",value:s,onkeydown:o,oninput:r})),itemview:o=>m("div",null,o.name)}))}};var Ve={view({attrs:{workbench:i},state:e}){e.open=e.open===void 0?!0:e.open;let t=a(n=>{e.open?e.open=!1:e.open=!0},"toggle");return m("main",{class:"workbench m-0 flex flex-row absolute inset-0",style:{overflow:"none"}},m("div",{class:"sidebar flex flex-col",style:{width:e.open?"256px":"52px"}},m("div",{class:"sidebar-top",style:{height:"56px"}},m("div",{class:"logo"})),m("div",{class:"grow sidebar-main"},e.open&&i.workspace.bus.root().children.map(n=>m(ze,{node:n,expanded:!0,level:0,workbench:i}))),m("div",{class:"sidebar-bottom"},m("svg",{onclick:t,xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-sidebar"},m("rect",{x:"3",y:"3",width:"18",height:"18",rx:"2",ry:"2"}),m("line",{x1:"9",y1:"3",x2:"9",y2:"21"})))),m("div",{class:"main flex flex-col grow"},m("div",{class:"topbar flex"},m("div",{class:"topbar-item",onclick:()=>i.openToday(),style:{cursor:"pointer",marginLeft:"var(--padding)",marginRight:"var(--padding)",display:"flex",alignItems:"center"}},m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-calendar"},m("rect",{x:"3",y:"4",width:"18",height:"18",rx:"2",ry:"2"}),m("line",{x1:"16",y1:"2",x2:"16",y2:"6"}),m("line",{x1:"8",y1:"2",x2:"8",y2:"6"}),m("line",{x1:"3",y1:"10",x2:"21",y2:"10"})),m("div",null,"Today")),m("div",{class:"topbar-item",onclick:()=>i.openQuickAdd(),style:{cursor:"pointer",marginLeft:"var(--padding)",marginRight:"var(--padding)",display:"flex",alignItems:"center"}},m("svg",{style:{marginRight:"var(--1)"},xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-plus-circle"},m("circle",{cx:"12",cy:"12",r:"10"}),m("line",{x1:"12",y1:"8",x2:"12",y2:"16"}),m("line",{x1:"8",y1:"12",x2:"16",y2:"12"})),m("div",null,"Quick Add")),m("div",{class:"searchbar flex grow"},m("div",null,m("div",{class:"flex",style:{margin:"1px"}},m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-search shrink-0"},m("circle",{cx:"11",cy:"11",r:"8"}),m("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"})),m("input",{type:"text",placeholder:"Search",onkeydown:n=>{if(n.key==="Control"||n.key==="Alt"||n.key==="Shift"||n.key==="Meta")return;let o=n.target.getBoundingClientRect();i.showDialog(()=>m(ve,{workbench:i,input:n.key}),!1,{left:`${o.left-33}px`,top:`${o.top-9}px`,width:`${o.width+33}px`}),n.preventDefault()},style:{border:"0",outline:"0",background:"transparent",paddingTop:"3px"}})))),m("div",{onclick:()=>i.toggleDrawer(),"data-menu":"keyboard-reference","data-align":"right",style:{cursor:"pointer",marginLeft:"var(--padding)",marginRight:"var(--padding)",marginTop:"-2px"}},m("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},m("g",{"clip-path":"url(#clip0_442_8012)"},m("path",{d:"M20 6H4C2.89543 6 2 6.76751 2 7.71429V16.2857C2 17.2325 2.89543 18 4 18H20C21.1046 18 22 17.2325 22 16.2857V7.71429C22 6.76751 21.1046 6 20 6Z",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M6 10H6.01",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M7.5 12H7.51",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M10.5 12H10.51",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M13.5 12H13.51",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M16.5 12H16.51",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M9 10H9.01",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M12 10H12.01",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M15 10H15.01",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M18 10H18.01",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"}),m("path",{d:"M5 15H19",stroke:"currentColor","stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"})),m("defs",null,m("clipPath",{id:"clip0_442_8012"},m("rect",{width:"22",height:"14",fill:"white",transform:"translate(1 5)"}))))),m("div",{onclick:n=>i.showMenu(n),"data-menu":"settings","data-align":"right",style:{cursor:"pointer",marginLeft:"var(--padding)",marginRight:"var(--padding)"}},m("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-menu"},m("line",{x1:"3",y1:"12",x2:"21",y2:"12"}),m("line",{x1:"3",y1:"6",x2:"21",y2:"6"}),m("line",{x1:"3",y1:"18",x2:"21",y2:"18"})))),m("div",{class:"panels flex flex-row grow",style:{position:"relative",overflow:"hidden"}},i.panels.map(n=>m("div",null,m(Ue,{workbench:i,path:n}))),m(We,{open:i.drawer.open},m(qe,{workbench:i}))),m("div",{class:"mobile-nav flex-row"},m("div",null,m("svg",{onclick:()=>{let n=document.querySelector(".sidebar").style;n.display!=="flex"?n.display="flex":n.display="none"},xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-sidebar"},m("rect",{x:"3",y:"3",width:"18",height:"18",rx:"2",ry:"2"}),m("line",{x1:"9",y1:"3",x2:"9",y2:"21"}))),m("div",{onclick:()=>i.openToday()},m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-calendar"},m("rect",{x:"3",y:"4",width:"18",height:"18",rx:"2",ry:"2"}),m("line",{x1:"16",y1:"2",x2:"16",y2:"6"}),m("line",{x1:"8",y1:"2",x2:"8",y2:"6"}),m("line",{x1:"3",y1:"10",x2:"21",y2:"10"}))),m("div",{onclick:()=>i.openQuickAdd()},m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-plus-circle"},m("circle",{cx:"12",cy:"12",r:"10"}),m("line",{x1:"12",y1:"8",x2:"12",y2:"16"}),m("line",{x1:"8",y1:"12",x2:"16",y2:"12"}))),m("div",{onclick:()=>i.showDialog(()=>m(ve,{workbench:i}),!0,{top:"25%",bottom:"100px"})},m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-search shrink-0"},m("circle",{cx:"11",cy:"11",r:"8"}),m("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"}))),m("div",{onclick:n=>i.showMenu(n,void 0,{bottom:"100px",marginTop:"auto"}),"data-menu":"settings"},m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",class:"feather feather-menu"},m("line",{x1:"3",y1:"12",x2:"21",y2:"12"}),m("line",{x1:"3",y1:"6",x2:"21",y2:"6"}),m("line",{x1:"3",y1:"18",x2:"21",y2:"18"}))))),i.popover&&m("div",{class:"popover",style:{position:"absolute",...i.popover.style}},i.popover.body()),m("dialog",{class:i.dialog.backdrop?"popover modal backdrop":"popover modal",style:i.dialog.style?{margin:"0",...i.dialog.style}:{top:"-50%"},oncancel:n=>{if(i.dialog.explicitClose===!0){n.preventDefault();return}i.dialog.body=()=>null},onclick:n=>{let r=n.target.closest("dialog").getBoundingClientRect(),s=n.clientX==0&&n.clientY==0;i.dialog.explicitClose!==!0&&(n.clientXr.right||n.clientYr.bottom)&&!s&&i.closeDialog()}},i.dialog.body()),m("dialog",{class:"menu popover",style:{margin:"0",...i.menu.style},oncancel:n=>{i.menu.body=()=>null},onclick:n=>{let r=n.target.closest("dialog").getBoundingClientRect();(n.clientXr.right||n.clientYr.bottom)&&i.closeMenu()}},i.menu.body()))}},ze={view({attrs:{node:i,workbench:e,expanded:t,level:n},state:o}){o.expanded=o.expanded===void 0?t:o.expanded;let r=i.childCount>0&&n<3,s=a(d=>{r&&(o.expanded?o.expanded=!1:o.expanded=!0,d.stopPropagation())},"toggle"),l=a(d=>{document.querySelector(".mobile-nav").offsetHeight&&(document.querySelector(".sidebar").style.display="none"),e.open(i)},"open");return m("div",null,m("div",{class:"sidebar-item flex"},m("svg",{onclick:s,class:"feather feather-chevron-right shrink-0",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",xmlns:"http://www.w3.org/2000/svg"},r?o.expanded?m("polyline",{points:"6 9 12 15 18 9"}):m("polyline",{points:"9 18 15 12 9 6"}):null),m("div",{class:"sidebar-item-label grow",onclick:l,style:{cursor:"pointer",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},i.name)),o.expanded&&m("div",{class:"sidebar-item-nested"},i.children.filter(d=>d.name!=="").map(d=>m(ze,{workbench:e,node:d,level:n+1}))))}};var P=class{constructor(){this.checked=!1}beforeEditor(){return lt}};a(P,"Checkbox"),P=N([S],P);var lt={view({attrs:{node:i}}){return m("input",{type:"checkbox",style:{marginTop:"0.3rem"},onclick:a(t=>{let n=i.getComponent(P);n.checked=!n.checked,i.changed()},"toggleCheckbox"),checked:i.getComponent(P).checked})}};var z=class{constructor(){}handleIcon(){return m("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},m("polyline",{points:"4 7 4 4 20 4 20 7"}),m("line",{x1:"9",y1:"20",x2:"15",y2:"20"}),m("line",{x1:"12",y1:"4",x2:"12",y2:"20"}))}};a(z,"TextField"),z=N([S],z);var D=class{constructor(){this.log=[],this.showLog=!1}onAttach(e){this.component=e,this.object=e.parent}fromJSON(e){e.startedAt&&(this.startedAt=new Date(e.startedAt)),this.log=(e.log||[]).map(t=>[new Date(t[0]),new Date(t[1])]),this.showLog=e.showLog}toJSON(e){return{startedAt:this.startedAt,log:this.log,showLog:this.showLog}}localTotal(){return this.log.map(this.entryDuration).reduce((e,t)=>e+t,0)}grandTotal(){let e=this.localTotal();return this.object&&this.object.children.forEach(t=>{t.hasComponent(D)&&(e+=t.getComponent(D).grandTotal())}),e}start(){this.startedAt||(this.startedAt=new Date)}stop(){if(!this.startedAt)return;let e=new Date;(e.getTime()-this.startedAt.getTime())/1e3>=60&&this.log.push([this.startedAt,e]),this.startedAt=void 0}formatEntry(e){return e.length!==2?"":`${this.formatDate(e[0])} - ${new Intl.DateTimeFormat("en",{timeStyle:"short"}).format(e[1])}`}entryDuration(e){let t=e[0];return((e[1]||new Date).getTime()-t.getTime())/1e3}formatDate(e){return e?new Intl.DateTimeFormat("en",{dateStyle:"short",timeStyle:"short"}).format(e):""}formatDuration(e){let t=e/60,n=Math.floor(t%60);return t=t/60,`${Math.floor(t%60)}:${n.toLocaleString("en-US",{minimumIntegerDigits:2,useGrouping:!1})}`}afterEditor(){return ct}belowEditor(){return ut}static initialize(e){e.commands.registerCommand({id:"stop-clock",title:"Stop clock",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)),action:t=>{if(!t.node.hasComponent(D)){let n=new D;t.node.addComponent(n)}t.node.getComponent(D).stop(),t.node.changed()}}),e.keybindings.registerBinding({command:"stop-clock",key:"meta+o"}),e.commands.registerCommand({id:"start-clock",title:"Start clock",when:t=>!(!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)),action:t=>{if(!t.node.hasComponent(D)){let n=new D;t.node.addComponent(n)}t.node.getComponent(D).start(),t.node.changed()}}),e.keybindings.registerBinding({command:"start-clock",key:"meta+i"}),e.commands.registerCommand({id:"remove-clock",title:"Remove clock",when:t=>!t.node||t.node.raw.Rel==="Fields"||t.node.parent&&t.node.parent.hasComponent(Document)?!1:!!t.node.hasComponent(D),action:t=>{t.node.removeComponent(D)}})}};a(D,"Clock"),D=N([S],D);var ct={view({attrs:{node:i}}){let e=i.getComponent(D),t=a(()=>{e.showLog=!e.showLog,i.changed()},"toggleLog");return!e.showLog&&e.startedAt?m("div",{tabindex:"1",onclick:t,class:"badge flex flex-row items-center",style:{background:"green",lineHeight:"var(--body-line-height)",paddingLeft:"0.25rem",paddingRight:"0.25rem",borderRadius:"4px",color:"white"}},m("svg",{class:"blink",style:{width:"1rem",height:"1rem",marginRight:"0.25rem"},xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},m("circle",{cx:"12",cy:"12",r:"10"}),m("polyline",{points:"12 6 12 12 16 14"})),m("div",null,e.formatDuration(e.entryDuration([e.startedAt])))):m("div",{tabindex:"1",onclick:t,class:"badge flex flex-row items-center",style:{background:"gray",lineHeight:"var(--body-line-height)",paddingLeft:"0.25rem",paddingRight:"0.25rem",borderRadius:"4px",color:"white"}},m("svg",{style:{width:"1rem",height:"1rem",marginRight:"0.25rem"},xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},m("circle",{cx:"12",cy:"12",r:"10"}),m("polyline",{points:"12 6 12 12 16 14"})),m("div",null,e.formatDuration(e.grandTotal())))}},ut={view({attrs:{node:i}}){let e=i.getComponent(D);if(e.showLog)return m("div",{class:"expanded-node flex flex-row"},m("div",{class:"indent flex"}),m("div",{class:"grow"},e.startedAt&&m("div",{class:"flex flex-row",style:{marginBottom:"2px"}},m("div",{class:"grow"},e.formatDate(e.startedAt)," - ..."),m("div",{class:"flex flex-row items-center",style:{background:"green",lineHeight:"var(--body-line-height)",paddingLeft:"0.25rem",paddingRight:"0.25rem",borderRadius:"4px",color:"white"}},m("svg",{class:"blink",style:{width:"1rem",height:"1rem",marginRight:"0.25rem"},xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},m("circle",{cx:"12",cy:"12",r:"10"}),m("polyline",{points:"12 6 12 12 16 14"})),m("div",null,e.formatDuration(e.entryDuration([e.startedAt]))))),e.log.toReversed().map(t=>m("div",{class:"flex flex-row",style:{marginBottom:"2px"}},m("div",{class:"grow"},e.formatEntry(t)),m("div",{class:"flex flex-row items-center",style:{background:"#aaa",lineHeight:"var(--body-line-height)",paddingLeft:"0.25rem",paddingRight:"0.25rem",borderRadius:"4px",color:"white"}},m("svg",{style:{width:"1rem",height:"1rem",marginRight:"0.25rem"},xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},m("circle",{cx:"12",cy:"12",r:"10"}),m("polyline",{points:"12 6 12 12 16 14"})),m("div",null,e.formatDuration(e.entryDuration(t))))))))}};var ne=class{constructor(){this.auth=null,this.files=new me,window.MiniSearch?this.index=new de:this.index=new he,this.changes={registerNotifier(e){window.reloadNodes=e}}}};a(ne,"BrowserBackend");var de=class{constructor(){this.indexer=new MiniSearch({idField:"ID",fields:["ID","Name","Value","Value.markdown"],storeFields:["ID"],extractField:(e,t)=>t.split(".").reduce((n,o)=>n&&n[o],e)})}index(e){this.indexer.has(e.ID)?this.indexer.replace(e):this.indexer.add(e)}remove(e){try{this.indexer.discard(e)}catch{}}search(e){let t=this.indexer.autoSuggest(e);return t.length===0?[]:this.indexer.search(t[0].suggestion,{prefix:!0,combineWith:"AND"}).map(n=>n.ID)}};a(de,"SearchIndex_MiniSearch");var he=class{constructor(){this.nodes={}}index(e){this.nodes[e.ID]=e.Name}remove(e){delete this.nodes[e]}search(e){let t=[];for(let n in this.nodes)this.nodes[n].includes(e)&&t.push(n);return t}};a(he,"SearchIndex_Dumb");var me=class{async readFile(e){return localStorage.getItem(`treehouse:${e}`)}async writeFile(e,t){localStorage.setItem(`treehouse:${e}`,t)}};a(me,"LocalStorageFileStore");import{encode as pt,decode as ht}from"https://cdn.jsdelivr.net/npm/js-base64@3.7.5/base64.mjs";var fe=class{constructor(e,t,n){this.loginURL=e,this.clientFactory=t,this.auth=this,this.shas={},this.opts=Object.assign({domain:"treehouse.sh",checkDomain:!1,privateRepo:!1},n||{});let o=new ne;this.index=o.index,this.files=o.files}get repoName(){return`${this.user?.userID().toLowerCase()}.${this.opts.domain}`}async initialize(){let e=new URL(location.href).searchParams.get("code");if(e)try{let r=location.search.replace(/\bcode=\w+/,"").replace(/\?$/,"");history.pushState({},"",`${location.pathname}${r}`);let l=await(await fetch(this.loginURL,{method:"POST",mode:"cors",headers:{"content-type":"application/json"},body:JSON.stringify({code:e})})).json();if(l.error)throw l.error;localStorage.setItem("treehouse:gh-token",l.token)}catch(r){this.reset(),console.error(r);return}let t=new URL(location.href).searchParams.get("access_token");if(t)try{let r=location.search.replace(/\baccess_token=\w+/,"").replace(/\?$/,"");history.pushState({},"",`${location.pathname}${r}`),localStorage.setItem("treehouse:gh-token",t)}catch(r){this.reset(),console.error(r);return}try{if(await this.authenticate(),!this.user)throw"authentication failed"}catch(r){console.error(r),this.opts.authFallbackURL&&(location.href=this.opts.authFallbackURL);return}if(this.opts.checkDomain&&this.repoName!==location.hostname.toLowerCase()){location.hostname=this.repoName;return}try{await this.client.rest.repos.get({owner:this.user.userID(),repo:this.repoName})}catch(r){if(r.message!=="Not Found")throw r;console.log("Creating repository...");let s=await this.client.rest.repos.createForAuthenticatedUser({name:this.repoName,private:this.opts.privateRepo});if(s.status!==201){console.error(s);return}}try{await this.client.rest.repos.getContent({owner:this.user.userID(),repo:this.repoName,path:"workspace.json"})}catch(r){if(r.name!=="HttpError")throw r;console.log("Creating workspace.json...");let s=await this.client.rest.repos.createOrUpdateFileContents({owner:this.user.userID(),repo:this.repoName,path:"workspace.json",message:"initial commit",content:btoa(JSON.stringify([]))});if(s.status!==201){console.error(s);return}}this.files=this;let n=mt();await this.readFile("treehouse.lock"),await this.writeFile("treehouse.lock",n);let o=setInterval(async()=>{await this.readFile("treehouse.lock")!==n&&(clearInterval(o),document.dispatchEvent(new CustomEvent("BackendError")),console.warn("lock stolen!"))},5e3)}async loadExtensions(){try{if((await this.client.rest.repos.getContent({owner:this.user?.userID(),repo:this.repoName,path:"",random:Math.random().toString(36).substring(2)})).data.find(t=>t.type==="dir"&&t.name==="ext")){let t=await this.client.rest.repos.getContent({owner:this.user?.userID(),repo:this.repoName,path:"ext",random:Math.random().toString(36).substring(2)});for(let n of t.data)if(n.name.endsWith(".css")){let o=await this.client.rest.repos.getContent({owner:this.user?.userID(),repo:this.repoName,path:n.path,random:Math.random().toString(36).substring(2)}),r=document.createElement("link");r.setAttribute("href",`data:text/css;charset=utf-8;base64,${o.data.content}`),r.setAttribute("rel","stylesheet"),r.setAttribute("type","text/css"),document.head.appendChild(r)}else if(n.name.endsWith(".js")){let o=await this.client.rest.repos.getContent({owner:this.user?.userID(),repo:this.repoName,path:n.path,random:Math.random().toString(36).substring(2)}),r=document.createElement("script");r.setAttribute("type","module"),r.setAttribute("src",`data:text/javascript;charset=utf-8;base64,${o.data.content}`),document.head.appendChild(r)}}}catch{}}async authenticate(){let e=localStorage.getItem("treehouse:gh-token");if(!e)return;this.client=new this.clientFactory({auth:e});let t=await this.client.rest.users.getAuthenticated();!t||t.error||(this.user=new ge(t.data),m&&m.redraw())}currentUser(){return this.user}login(){location.assign(this.loginURL)}reset(){localStorage.removeItem("treehouse:gh-token"),this.user=null,m&&m.redraw()}logout(){this.reset(),location.reload()}async readFile(e){try{let t=await this.client.rest.repos.getContent({owner:this.user?.userID(),repo:this.repoName,path:e,random:Math.random().toString(36).substring(2)});return this.shas[e]=t.data.sha,ht(t.data.content)}catch(t){return t.name!=="HttpError"&&console.error(t),null}}async writeFile(e,t){let n=await this.client.rest.repos.createOrUpdateFileContents({owner:this.user?.userID(),repo:this.repoName,path:e,message:"autosave",content:pt(t),sha:this.shas[e]});this.shas[e]=n.data.content.sha}};a(fe,"GitHubBackend");var ge=class{constructor(e){this.user=e}userID(){return this.user.login}displayName(){return this.user.name}avatarURL(){return this.user.avatar_url}};a(ge,"User");function mt(){let i=Date.now().toString(36),e=Math.random().toString(36).substring(2);return i+e}a(mt,"uniqueID");async function Ar(i,e,t){t.initialize&&await t.initialize();let n=new _(t);window.workbench=n,await n.initialize(),[D,z,x,P,F,H,U,L,$,M].forEach(o=>{o.initialize&&o.initialize(n)}),i.addEventListener("BackendError",()=>{n.showNotice("lockstolen",()=>{location.reload()})}),n.commands.registerCommand({id:"cut",title:"Cut",when:o=>{if(!o.node)return!1;let r=n.getInput(o.path);return r&&r.selectionStart===r.selectionEnd?!0:(n.clipboard=void 0,!1)},action:o=>{n.clipboard={op:"cut",node:o.node}}}),n.keybindings.registerBinding({command:"cut",key:"meta+x"}),n.commands.registerCommand({id:"copy",title:"Copy",when:o=>{if(!o.node)return!1;let r=n.getInput(o.path);return r&&r.selectionStart===r.selectionEnd?!0:(n.clipboard=void 0,!1)},action:o=>{n.clipboard={op:"copy",node:o.node}}}),n.keybindings.registerBinding({command:"copy",key:"meta+c"}),n.commands.registerCommand({id:"copy-reference",title:"Copy as Reference",when:o=>{if(!o.node)return!1;let r=n.getInput(o.path);return r&&r.selectionStart===r.selectionEnd?!0:(n.clipboard=void 0,!1)},action:o=>{n.clipboard={op:"copyref",node:o.node}}}),n.keybindings.registerBinding({command:"copy-reference",key:"shift+ctrl+c"}),n.commands.registerCommand({id:"paste",title:"Paste",when:o=>!!n.clipboard,action:o=>{if(!o.node||o.path.previous&&j(o.path.previous))return;switch(n.clipboard.op){case"copy":n.clipboard.node=n.clipboard.node.duplicate();break;case"copyref":let s=n.workspace.new("");s.refTo=n.clipboard.node,n.clipboard.node=s;break}n.clipboard.node.raw.Rel==="Fields"?(n.clipboard.node.raw.Parent=o.node.parent.id,o.node.parent.addLinked("Fields",n.clipboard.node)):(n.clipboard.node.parent=o.node.parent,n.clipboard.node.siblingIndex=o.node.siblingIndex),m.redraw.sync();let r=o.path.clone();r.pop(),n.focus(r.append(n.clipboard.node)),n.clipboard.op==="cut"&&(n.clipboard=void 0)}}),n.keybindings.registerBinding({command:"paste",key:"meta+v"}),n.commands.registerCommand({id:"view-list",title:"View as List",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{o.node.setAttr("view","list")}}),n.commands.registerCommand({id:"view-table",title:"View as Table",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{o.node.setAttr("view","table"),o.node.children.forEach(r=>{n.workspace.setExpanded(o.path.head,r,!1)})}}),n.commands.registerCommand({id:"view-tabs",title:"View as Tabs",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{o.node.setAttr("view","tabs")}}),n.commands.registerCommand({id:"view-cards",title:"View as Cards",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{o.node.setAttr("view","cards")}}),n.commands.registerCommand({id:"add-checkbox",title:"Add checkbox",when:o=>!(!o.node||o.node.hasComponent(P)||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{let r=new P;o.node.addComponent(r)}}),n.commands.registerCommand({id:"remove-checkbox",title:"Remove checkbox",when:o=>!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)?!1:!!o.node.hasComponent(P),action:o=>{o.node.removeComponent(P)}}),n.commands.registerCommand({id:"create-field",title:"Create Field",action:o=>{if(!o.node||o.node.childCount>0||o.node.componentCount>0||o.path.previous&&j(o.path.previous))return;let r=o.path.clone();r.pop();let s=n.workspace.new(o.node.name,"");s.raw.Parent=o.node.parent.id;let l=new z;s.addComponent(l),o.node.parent.addLinked("Fields",s),r.push(s),o.node.destroy(),m.redraw.sync(),n.focus(r)}}),n.commands.registerCommand({id:"mark-done",title:"Mark Done",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{if(o.node)if(o.node.hasComponent(P)){let r=o.node.getComponent(P);r.checked?o.node.removeComponent(P):(r.checked=!0,o.node.changed())}else{let r=new P;o.node.addComponent(r)}}}),n.keybindings.registerBinding({command:"mark-done",key:"meta+enter"}),n.commands.registerCommand({id:"expand",title:"Expand",action:o=>{o.node&&(n.workspace.setExpanded(o.path.head,o.node,!0),m.redraw())}}),n.keybindings.registerBinding({command:"expand",key:"meta+arrowdown"}),n.commands.registerCommand({id:"collapse",title:"Collapse",action:o=>{o.node&&(n.workspace.setExpanded(o.path.head,o.node,!1),m.redraw())}}),n.keybindings.registerBinding({command:"collapse",key:"meta+arrowup"}),n.commands.registerCommand({id:"indent",title:"Indent",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{let r=o.node,s=o.path.clone(),l=r.prevSibling;for(;l&&j(l);)if(l=l.prevSibling,!l)return;l!==null&&(s.pop(),s.push(l),r.parent=l,s.push(r),n.workspace.setExpanded(o.path.head,l,!0),m.redraw.sync(),n.focus(s))}}),n.keybindings.registerBinding({command:"indent",key:"tab"}),n.commands.registerCommand({id:"outdent",title:"Outdent",when:o=>!(!o.node||o.node.raw.Rel==="Fields"||o.path.previous&&j(o.path.previous)||o.node.parent&&o.node.parent.hasComponent(x)),action:o=>{let r=o.node,s=o.path.previous,l=o.path.clone();s!==null&&s.id!=="@root"&&s.id!==n.workspace.lastOpenedID&&(l.pop(),l.pop(),r.parent=s.parent,l.push(r),r.siblingIndex=s.siblingIndex+1,s.childCount===0&&s.getLinked("Fields").length===0&&n.workspace.setExpanded(o.path.head,s,!1),m.redraw.sync(),n.focus(l))}}),n.keybindings.registerBinding({command:"outdent",key:"shift+tab"}),n.commands.registerCommand({id:"move-up",title:"Move Up",action:o=>{if(!o.node)return;let r=o.node,s=r.parent;if(s!==null&&s.id!=="@root"){let l=s.childCount;if(r.siblingIndex===0){if(!s.prevSibling)return;let d=o.path.clone();d.pop(),d.pop();let c=s.prevSibling;for(;c&&j(c);)if(c=c.prevSibling,!c)return;d.push(c),d.push(r),r.parent=c,r.siblingIndex=c.childCount-1,n.workspace.setExpanded(o.path.head,c,!0),m.redraw.sync(),n.focus(d)}else{if(l===1)return;r.siblingIndex=r.siblingIndex-1,m.redraw.sync()}}}}),n.keybindings.registerBinding({command:"move-up",key:"shift+meta+arrowup"}),n.commands.registerCommand({id:"move-down",title:"Move Down",action:o=>{if(!o.node)return;let r=o.node,s=r.parent;if(s!==null&&s.id!=="@root"){let l=s.childCount;if(r.siblingIndex===l-1){if(!s.nextSibling)return;let d=o.path.clone();d.pop(),d.pop();let c=s.nextSibling;for(;c&&j(c);)if(c=c.nextSibling,!c)return;d.push(c),d.push(r),r.parent=c,r.siblingIndex=0,n.workspace.setExpanded(o.path.head,c,!0),m.redraw.sync(),n.focus(d)}else{if(l===1)return;r.siblingIndex=r.siblingIndex+1,m.redraw.sync()}}}}),n.keybindings.registerBinding({command:"move-down",key:"shift+meta+arrowdown"}),n.commands.registerCommand({id:"insert-child",title:"Insert Child",action:(o,r="",s)=>{if(!o.node||j(o.node))return;let l=n.workspace.new(r);l.parent=o.node,s!==void 0&&(l.siblingIndex=s),n.workspace.setExpanded(o.path.head,o.node,!0),m.redraw.sync(),n.focus(o.path.append(l),r.length)}}),n.commands.registerCommand({id:"insert-before",title:"Insert Before",action:o=>{if(!o.node||o.path.previous&&j(o.path.previous))return;let r=n.workspace.new("");r.parent=o.node.parent,r.siblingIndex=o.node.siblingIndex,m.redraw.sync();let s=o.path.clone();s.pop(),n.focus(s.append(r))}}),n.commands.registerCommand({id:"insert",title:"Insert Node",action:(o,r="")=>{if(!o.node||o.path.previous&&j(o.path.previous))return;let s=n.workspace.new(r);s.parent=o.node.parent,s.siblingIndex=o.node.siblingIndex+1,m.redraw.sync();let l=o.path.clone();l.pop(),n.focus(l.append(s))}}),n.keybindings.registerBinding({command:"insert",key:"shift+enter"}),n.commands.registerCommand({id:"create-reference",title:"Create Reference",action:o=>{if(!o.node||o.path.previous&&j(o.path.previous))return;let r=n.workspace.new("");r.parent=o.node.parent,r.siblingIndex=o.node.siblingIndex+1,r.refTo=o.node,m.redraw.sync();let s=o.path.clone();s.pop(),n.focus(s.append(r))}}),n.commands.registerCommand({id:"delete",title:"Delete Node",action:o=>{if(!o.node||o.node.id.startsWith("@")||o.path.previous&&j(o.path.previous))return;let r=n.workspace.findAbove(o.path);if(o.node.destroy(),m.redraw.sync(),r){let s=0;o.event&&o.event.key==="Backspace"&&(r.node.value?s=r.node.value.length:s=r.node.name.length),r.node.childCount===0&&n.workspace.setExpanded(o.path.head,r.node,!1),n.focus(r,s)}}}),n.keybindings.registerBinding({command:"delete",key:"shift+meta+backspace"}),n.commands.registerCommand({id:"prev",title:"Previous Node",action:o=>{if(!o.node)return;let r=n.workspace.findAbove(o.path);r&&n.focus(r)}}),n.keybindings.registerBinding({command:"prev",key:"arrowup"}),n.commands.registerCommand({id:"next",title:"Next Node",action:o=>{if(!o.node)return;let r=n.workspace.findBelow(o.path);r&&n.focus(r)}}),n.keybindings.registerBinding({command:"next",key:"arrowdown"}),n.commands.registerCommand({id:"pick-command",title:"Command Palette",hidden:!0,when:o=>!n.isDialogOpen(),action:o=>{let r=o.node,s=o.path,l=!1;r||(r=o.path.head,s=new O(o.path.head,o.path.name),l=!0);let d=n.getInput(s),c=d.getBoundingClientRect(),h=i.body.scrollLeft+c.x+d.selectionStart*10+20,p=i.body.scrollTop+c.y-8;d.coordsAtCursor&&(h=d.coordsAtCursor.left-17,p=d.coordsAtCursor.top-16),l&&(h=i.body.scrollLeft+c.x,p=i.body.scrollTop+c.y+c.height),n.showPalette(h,p,n.newContext({node:r}))}}),n.keybindings.registerBinding({command:"pick-command",key:"meta+k"}),n.commands.registerCommand({id:"new-panel",title:"Open in New Panel",action:o=>{o.node&&(n.openNewPanel(o.node),m.redraw())}}),n.commands.registerCommand({id:"close-panel",title:"Close Panel",action:(o,r)=>{n.closePanel(r||o.path),n.context.path=n.mainPanel,m.redraw()}}),n.commands.registerCommand({id:"zoom",title:"Open",action:o=>{n.workspace.lastOpenedID=o.node.id,n.workspace.save(),n.context.path=o.path.append(o.node),n.panels[0]=n.context.path,m.redraw()}}),n.commands.registerCommand({id:"generate-random",hidden:!0,title:"Generate Random Children",action:o=>{o.node&&[...Array(100)].forEach(()=>{let r=n.workspace.new(ft(8));r.parent=o.node})}}),n.menus.registerMenu("node",[{command:"zoom"},{command:"new-panel"},{command:"cut"},{command:"copy"},{command:"paste"},{command:"indent"},{command:"outdent"},{command:"move-up"},{command:"move-down"},{command:"delete"}]),n.menus.registerMenu("settings",[{title:()=>`${n.backend.auth?.currentUser()?.userID()} @ GitHub`,disabled:!0,when:()=>n.authenticated()},{title:()=>"Login with GitHub",when:()=>!n.authenticated(),onclick:()=>{localStorage.getItem("github")?n.backend.auth.login():n.showNotice("github",()=>{n.backend.auth.login()})}},{title:()=>"Reset Demo",when:()=>!n.authenticated(),onclick:()=>{localStorage.clear(),location.reload()}},{title:()=>"Settings",onclick:()=>n.showSettings()},{title:()=>"Documentation",onclick:()=>window.open("https://treehouse.sh/docs/user","_blank")},{title:()=>"Submit Issue",onclick:()=>window.open("https://github.com/treehousedev/treehouse/issues","_blank")},{title:()=>"Logout",when:()=>n.authenticated(),onclick:()=>n.backend.auth.logout()}]),i.addEventListener("keydown",o=>{let r=n.keybindings.evaluateEvent(o);if(r&&n.canExecuteCommand(r.command,n.context)){n.executeCommand(r.command,n.context),o.stopPropagation(),o.preventDefault();return}}),m.mount(e,{view:()=>m(Ve,{workbench:n})})}a(Ar,"setup");function ft(i=10){let e=a((o,r)=>Math.round(Math.random()*(r-o)+o),"random"),t=a(()=>{let o=["got","ability","shop","recall","fruit","easy","dirty","giant","shaking","ground","weather","lesson","almost","square","forward","bend","cold","broken","distant","adjective"];return o[e(0,o.length-1)]},"word");return a(o=>[...Array(o)].map((r,s)=>t()).join(" ").trim(),"words")(e(2,i))}a(ft,"generateName");export{ne as BrowserBackend,fe as GitHubBackend,de as SearchIndex_MiniSearch,Ar as setup}; +//# sourceMappingURL=treehouse.min.js.map diff --git a/lib/treehouse.min.js.map b/lib/treehouse.min.js.map new file mode 100644 index 0000000..af6ddea --- /dev/null +++ b/lib/treehouse.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../../lib/action/keybinds.ts", "../../../lib/action/commands.ts", "../../../lib/action/menus.ts", "../../../lib/ui/menu.tsx", "../../../lib/ui/picker.tsx", "../../../lib/ui/palette.tsx", "../../../lib/model/hooks.ts", "../../../lib/model/components.ts", "../../../lib/com/document.tsx", "../../../lib/com/description.tsx", "../../../lib/ui/node/editor.tsx", "../../../lib/com/template.tsx", "../../../lib/com/tag.tsx", "../../../lib/com/codeblock.tsx", "../../../lib/view/empty.tsx", "../../../lib/ui/node/new.tsx", "../../../lib/com/smartnode.tsx", "../../../lib/view/list.tsx", "../../../lib/view/table.tsx", "../../../lib/view/tabs.tsx", "../../../lib/view/document.tsx", "../../../lib/com/iframe.tsx", "../../../lib/view/cards.tsx", "../../../lib/view/views.ts", "../../../lib/ui/outline.tsx", "../../../lib/ui/quickadd.tsx", "../../../lib/ui/settings.tsx", "../../../lib/ui/notices.tsx", "../../../lib/workbench/workbench.ts", "../../../lib/workbench/util.js", "../../../lib/workbench/path.ts", "../../../lib/model/module/bus.ts", "../../../lib/model/module/node.ts", "../../../lib/workbench/workspace.ts", "../../../lib/ui/drawer.tsx", "../../../lib/ui/panel.tsx", "../../../lib/ui/reference.tsx", "../../../lib/ui/search.tsx", "../../../lib/ui/app.tsx", "../../../lib/com/checkbox.tsx", "../../../lib/com/textfield.tsx", "../../../lib/com/clock.tsx", "../../../lib/backend/browser.ts", "../../../lib/backend/github.ts", "../../../lib/mod.ts"], + "sourcesContent": ["\nconst isMac = (navigator.userAgent.toLowerCase().indexOf(\"mac\") !== -1);\n\nexport function bindingSymbols(key?: string): string[] {\n if (!key) return [];\n const symbols = {\n \"backspace\": \"\u232B\",\n \"shift\": \"\u21E7\",\n \"meta\": \"\u2318\",\n \"tab\": \"\u21B9\",\n \"ctrl\": \"\u2303\",\n \"arrowup\": \"\u2191\",\n \"arrowdown\": \"\u2193\",\n \"arrowleft\": \"\u2190\",\n \"arrowright\": \"\u2192\",\n \"enter\": \"\u23CE\"\n };\n const keys = key.toLowerCase().split(\"+\");\n return keys.map(filterKeyForNonMacMeta).map(k => (Object.keys(symbols).includes(k)) ? symbols[k] : k);\n}\n\n// if key is meta and not on a mac, change it to ctrl,\n// otherwise return the key as is\nfunction filterKeyForNonMacMeta(key: string): string {\n return (!isMac && key === \"meta\") ? \"ctrl\": key;\n}\n\nexport interface Binding {\n command: string;\n key: string;\n //when\n //args\n}\n\nexport class KeyBindings {\n bindings: Binding[];\n\n constructor() {\n this.bindings = [];\n }\n\n registerBinding(binding: Binding) {\n this.bindings.push(binding);\n }\n\n getBinding(commandId: string): Binding|null {\n for (const b of this.bindings) {\n if (b.command === commandId) {\n return b;\n }\n }\n return null;\n }\n\n evaluateEvent(event: KeyboardEvent): Binding|null {\n bindings: for (const b of this.bindings) {\n let modifiers = b.key.toLowerCase().split(\"+\");\n let key = modifiers.pop();\n if (key !== event.key.toLowerCase()) {\n continue;\n }\n for (const checkMod of [\"shift\", \"ctrl\", \"alt\", \"meta\"]) {\n let hasMod = modifiers.includes(checkMod);\n if (!isMac) {\n if (checkMod === \"meta\") continue;\n if (checkMod === \"ctrl\") {\n hasMod = modifiers.includes(\"meta\") || modifiers.includes(\"ctrl\");\n }\n }\n // @ts-ignore\n const modState = event[`${filterKeyForNonMacMeta(checkMod)}Key`];\n if (!modState && hasMod) {\n continue bindings;\n }\n if (modState && !hasMod) {\n continue bindings;\n }\n }\n return b;\n }\n return null;\n }\n}", "\nexport interface Command {\n id: string;\n title?: string;\n category?: string;\n icon?: string;\n hidden?: boolean;\n action: Function;\n when?: Function;\n}\n\nexport class CommandRegistry {\n commands: {[index: string]: Command}\n\n constructor() {\n this.commands = {};\n }\n\n registerCommand(cmd: Command) {\n this.commands[cmd.id] = cmd;\n }\n\n canExecuteCommand(id: string, ...rest: any): boolean {\n if (this.commands[id]) {\n if (this.commands[id].when && !this.commands[id].when(...rest)) {\n return false;\n }\n return true;\n }\n return false;\n }\n\n executeCommand(id: string, ...rest: any): Promise {\n return new Promise((resolve) => {\n const ret = this.commands[id].action(...rest);\n resolve(ret);\n });\n }\n}", "\nexport interface MenuItem {\n command?: string;\n //alt?: string;\n when?: Function;\n title?: Function;\n onclick?: Function;\n disabled?: boolean;\n //group\n //submenu\n}\n\nexport class MenuRegistry {\n menus: {[index: string]: MenuItem[]};\n\n constructor() {\n this.menus = {};\n }\n\n registerMenu(id: string, items: MenuItem[]) {\n this.menus[id] = items;\n }\n}", "import { bindingSymbols } from \"../action/keybinds.ts\";\n\nfunction isDisabled(workbench, item, cmd, ctx) {\n if (cmd) {\n return item.disabled || !workbench.canExecuteCommand(cmd.id, ctx);\n }\n return item.disabled;\n}\n\nexport const Menu: m.Component = {\n view({attrs: {workbench, x, y, items, align, commands, ctx}}) {\n const onclick = (item, cmd) => (e) => {\n e.stopPropagation();\n if (isDisabled(workbench, item, cmd, ctx)) {\n return;\n }\n workbench.closeMenu();\n if (item.onclick) {\n item.onclick();\n }\n if (cmd) {\n workbench.executeCommand(cmd.id, ctx);\n }\n };\n return (\n
    \n {items.filter(i => !i.when || i.when()).map(i => {\n let title = \"\";\n let binding = undefined;\n let cmd = undefined;\n if (i.command) {\n cmd = commands.find(c => c.id === i.command);\n binding = workbench.keybindings.getBinding(cmd.id);\n title = cmd.title;\n }\n if (i.title) {\n title = i.title();\n }\n return (\n
  • \n
    {title}
    \n {binding &&
    {bindingSymbols(binding.key).join(\" \").toUpperCase()}
    }\n
  • \n )\n })}\n
\n ) \n }\n};\n\n/*
  • Indent
    shift+A
  • \n
  • Open in new panel
    shift+meta+Backspace
  • \n
    \n
  • Show list view
  • \n
  • Move
  • \n
  • Delete node
  • \n
    */\n", "\nexport interface Attrs {\n input: string;\n inputview: (onkeydown: Function, oninput: Function) => any;\n itemview: (item: any, idx: number) => any;\n onpick: (item: any) => void;\n onchange: (State) => void;\n}\n\nexport interface State {\n selected: number;\n input: string;\n items: any[];\n}\n\nexport const Picker: m.Component = {\n onupdate({ state, dom }) {\n const items = dom.querySelector(\".items\").children;\n if (state.selected !== undefined && items.length > 0) {\n items[state.selected].scrollIntoView({ block: \"nearest\" });\n }\n },\n\n oncreate({ attrs, state, dom }) {\n if (attrs.inputview) {\n dom.querySelector(\"input\")?.focus();\n }\n if (state.selected === undefined) {\n state.selected = 0;\n }\n },\n\n view({ attrs, state }) {\n \n state.selected = (state.selected === undefined) ? 0 : state.selected;\n state.input = (state.input === undefined) ? (attrs.input || \"\") : state.input;\n if (state.items === undefined) {\n state.items = [];\n attrs.onchange(state);\n }\n\n const onkeydown = (e) => {\n const mod = (a, b) => ((a % b) + b) % b;\n if (e.key === \"ArrowDown\") {\n if (state.selected === undefined) {\n state.selected = 0;\n return false;\n }\n state.selected = mod(state.selected + 1, state.items.length);\n return false;\n }\n if (e.key === \"ArrowUp\") {\n if (state.selected === undefined) {\n state.selected = 0;\n }\n state.selected = mod(state.selected - 1, state.items.length);\n return false;\n }\n if (e.key === \"Enter\") {\n if (state.selected !== undefined) {\n attrs.onpick(state.items[state.selected]);\n }\n return false;\n }\n }\n const oninput = (e) => {\n state.input = e.target.value;\n state.selected = 0;\n attrs.onchange(state);\n }\n return (\n
    \n {attrs.inputview(onkeydown, oninput, state.input)}\n
    \n {state.items.map((item, idx) => (\n
    attrs.onpick(item)}\n onmouseover={() => state.selected = idx}>\n {attrs.itemview(item, idx)}\n
    \n ))}\n
    \n
    \n )\n }\n}\n", "import { bindingSymbols } from \"../action/keybinds.ts\";\nimport { Picker } from \"./picker.tsx\";\n\nexport const CommandPalette: m.Component = {\n\n view({ attrs: { workbench, ctx } }) {\n const getTitle = (cmd) => {\n const title = cmd.title || cmd.id;\n return title.replace('-', ' ').replace(/(^|\\s)\\S/g, t => t.toUpperCase());\n }\n const sort = (a, b) => {\n return getTitle(a).localeCompare(getTitle(b));\n }\n const onpick = (cmd) => {\n workbench.closeDialog();\n workbench.commands.executeCommand(cmd.id, ctx);\n }\n const onchange = (state) => {\n state.items = cmds.filter(cmd => {\n const value = cmd.title || cmd.id;\n return value.toLowerCase().includes(state.input.toLowerCase());\n })\n }\n const getBindingSymbols = (cmd) => {\n const binding = workbench.keybindings.getBinding(cmd.id);\n return binding ? bindingSymbols(binding.key).join(\" \").toUpperCase() : \"\";\n }\n\n const cmds = Object.values(workbench.commands.commands)\n .filter(cmd => !cmd.hidden)\n .filter(cmd => workbench.canExecuteCommand(cmd.id, ctx))\n .sort(sort);\n\n return (\n
    \n \n
    \n \n
    \n }\n itemview={(cmd) => \n
    \n
    {getTitle(cmd)}
    \n
    {getBindingSymbols(cmd)}
    \n
    \n } />\n
    \n )\n }\n}\n\n", "/**\n * Hooks are single method interfaces implemented by component values. There are\n * some system hook interfaces defined here as well as utilities for working with\n * system and app hooks.\n * \n * @module\n */\nimport { Node } from \"./mod.ts\";\n\n// triggered on parent set or import (if has parent), or addcomponent\nexport interface AttachListener {\n onAttach(node: Node): void;\n}\n\n// called on accessing children\nexport interface ChildProvider {\n objectChildren(node: Node, children: Node[]): Node[];\n}\n\nexport function hasHook(node: Node, hook: string): boolean {\n return node.value && node.value[hook] instanceof Function;\n}\n\nexport function triggerHook(node: Node, hook: string, ...args: any[]): any {\n if (hasHook(node, hook)) {\n return node.value[hook].apply(node.value, args);\n }\n}\n\nexport function objectHas(obj: Node, hook: string): boolean {\n for (const com of obj.components) {\n if (hasHook(com, hook)) return true;\n }\n return false;\n}\n\nexport function objectCall(obj: Node, hook: string, ...args: any[]): any {\n for (const com of obj.components) {\n if (hasHook(com, hook)) {\n return com.value[hook].apply(com.value, args);\n }\n }\n}\n\nexport function componentsWith(obj: Node, hook: string, ...args: any[]): any[] {\n const ret = [];\n for (const com of obj.components) {\n if (hasHook(com, hook)) {\n ret.push(com.value);\n }\n }\n return ret;\n}\n\n\n\n// shorthand for nodes that have child provider hook.\n// use this to determine if some commands should be\n// prevented since visible children will not be impacted.\nexport function objectManaged(obj: Node): boolean {\n return objectHas(obj, \"objectChildren\");\n}", "/**\n * Components are classes that can be used for component values in component nodes.\n * These classes need to be registered so they can be properly \"hydrated\" from \n * marshaled form (usually JSON) back into class instances.\n * \n * @module\n */\n\nconst registry: Record = {};\n\nexport function component(target: any) {\n registry[componentName(target)] = target;\n}\n\nexport function componentName(target: any): string {\n if (target.prototype === undefined) {\n target = target.constructor;\n }\n return `treehouse.${target.name}`;\n}\n\nexport function getComponent(com: any): any {\n if (typeof com === \"string\") {\n return registry[com];\n }\n return registry[componentName(com)];\n}\n\nexport function inflateToComponent(com: any, obj: any): any {\n const o = new (getComponent(com));\n if (o[\"fromJSON\"] instanceof Function) {\n o.fromJSON(obj);\n } else {\n Object.defineProperties(o, Object.getOwnPropertyDescriptors(obj));\n }\n return o;\n}\n\nexport function duplicate(obj: any): any {\n if (obj === undefined) {\n return undefined;\n }\n const com = getComponent(obj);\n if (!com) {\n return structuredClone(obj);\n }\n const src = JSON.parse(JSON.stringify(obj)||\"\");\n const dst = new obj.constructor();\n if (dst[\"fromJSON\"] instanceof Function) {\n dst.fromJSON(src);\n } else {\n Object.defineProperties(dst, Object.getOwnPropertyDescriptors(src));\n }\n return dst;\n}", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\n@component\nexport class Document {\n object?: Node;\n\n constructor() {\n }\n\n onAttach(node: Node) {\n this.object = node.parent;\n this.object.setAttr(\"view\", \"document\");\n }\n\n handleIcon(collapsed: boolean = false): any {\n return (\n \n {/* {collapsed?:null} */}\n \n \n \n \n \n \n );\n }\n\n toJSON(key: string): any {\n return {};\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"make-document\",\n title: \"Make Document\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n const doc = new Document();\n ctx.node.addComponent(doc);\n ctx.node.changed();\n workbench.executeCommand(\"zoom\", ctx);\n }\n });\n }\n}\n", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\nimport { objectManaged } from \"../model/hooks.ts\";\n\n@component\nexport class Description {\n text: string;\n\n constructor() {\n this.text = \"\";\n }\n\n editor() {\n return DescriptionEditor;\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"add-description\",\n title: \"Add Description\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return false;\n if (ctx.node.hasComponent(Description)) return false;\n return true;\n },\n action: (ctx: Context, name: string) => {\n const desc = new Description();\n ctx.node.addComponent(desc);\n ctx.node.changed();\n }\n });\n workbench.commands.registerCommand({\n id: \"remove-description\",\n title: \"Remove Description\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return false;\n if (ctx.node.hasComponent(Description)) return true;\n return false;\n },\n action: (ctx: Context, name: string) => {\n ctx.node.removeComponent(Description);\n ctx.node.changed();\n }\n });\n }\n}\n\nconst DescriptionEditor = {\n view({attrs: {node}}) {\n const oninput = (e) => {\n const desc = node.getComponent(Description);\n desc.text = e.target.value;\n node.changed();\n }\n const onblur = (e) => {\n const desc = node.getComponent(Description);\n if (desc.text === \"\") {\n node.removeComponent(Description);\n }\n node.changed();\n }\n\n return \n }\n}", "import { objectCall, objectHas } from \"../../model/hooks.ts\";\nimport { Document } from \"../../com/document.tsx\";\nimport { Description } from \"../../com/description.tsx\";\n\nexport const NodeEditor: m.Component = {\n view ({attrs: {workbench, path, onkeydown, oninput, disallowEmpty, editValue, placeholder}, state}) {\n const node = path.node;\n let prop = (editValue) ? \"value\" : \"name\";\n \n const display = () => {\n if (prop === \"name\") {\n return objectHas(node, \"displayName\") ? objectCall(node, \"displayName\", node) : node.name;\n }\n return node[prop] || \"\";\n }\n const onfocus = () => {\n state.initialValue = node[prop];\n workbench.context.node = node;\n workbench.context.path = path;\n }\n const getter = () => {\n return node[prop];\n }\n const setter = (v, finished) => {\n if (!node.isDestroyed) {\n if (disallowEmpty && v.length === 0) {\n node[prop] = state.initialValue;\n } else {\n node[prop] = v;\n }\n }\n if (finished) {\n workbench.context.node = null;\n }\n }\n\n if (node.raw.Rel === \"Fields\") {\n placeholder = (editValue) ? \"Value\" : \"Field\";\n }\n \n let id = `input-${path.id}-${node.id}`;\n if (prop === \"value\") {\n id = id+\"-value\";\n }\n let editor = TextAreaEditor;\n if (node.parent && node.parent.hasComponent(Document) && window.Editor) {\n editor = CodeMirrorEditor;\n }\n let desc = undefined;\n if (node.hasComponent(Description)) {\n desc = node.getComponent(Description);\n }\n return (\n
    \n {m(editor, {id, getter, setter, display, onkeydown, onfocus, oninput, placeholder, workbench, path})} \n {(desc) ? m(desc.editor(), {node}) :null}\n
    \n )\n }\n}\n\n\ninterface Attrs {\n id?: string;\n onkeydown?: Function;\n onfocus?: Function;\n onblur?: Function;\n onmount?: Function;\n getter: Function;\n setter: Function;\n display?: Function;\n}\n\ninterface State {\n editing: boolean;\n buffer: string;\n}\n\nexport const CodeMirrorEditor: m.Component = {\n oncreate({dom,state,attrs: {id, onkeydown, onfocus, onblur, oninput, getter, setter, display, placeholder}}) {\n const value = (state.editing) \n ? state.buffer \n : (display) ? display() : getter();\n\n const defaultKeydown = (e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n }\n }\n const startEdit = (e) => {\n if (onfocus) onfocus(e);\n state.editing = true;\n state.buffer = getter();\n }\n const finishEdit = (e) => {\n // safari can trigger blur more than once\n // for a given element, namely when clicking\n // into devtools. this prevents the second \n // blur setting node name to undefined/empty.\n if (state.editing) {\n state.editing = false;\n setter(state.buffer, true);\n state.buffer = undefined;\n }\n if (onblur) onblur(e);\n }\n const edit = (e) => {\n state.buffer = e.target.value;\n setter(state.buffer, false);\n if (oninput) {\n oninput(e);\n }\n }\n\n state.editor = new window.Editor(dom, value, placeholder);\n state.editor.onblur = finishEdit;\n state.editor.onfocus = startEdit;\n state.editor.oninput = edit;\n state.editor.onkeydown = onkeydown||defaultKeydown;\n dom.editor = state.editor;\n dom.id = id;\n },\n onupdate({dom,state,attrs: {getter, display}}) {\n state.editor.value = (state.editing) \n ? state.buffer \n : (display) ? display() : getter();\n },\n view () {\n return (\n
    \n )\n }\n}\n\nexport const TextAreaEditor: m.Component = {\n oncreate({dom,attrs}) {\n const textarea = dom.querySelector(\"textarea\");\n const initialHeight = textarea.offsetHeight;\n const span = dom.querySelector(\"span\");\n this.updateHeight = () => {\n span.style.width = `${Math.max(textarea.offsetWidth, 100)}px`;\n span.innerHTML = textarea.value.replace(\"\\n\", \"
    \");\n let height = span.offsetHeight;\n if (height === 0 && initialHeight > 0) {\n height = initialHeight;\n }\n textarea.style.height = (height > 0) ? `${height}px` : `var(--body-line-height)`;\n }\n textarea.addEventListener(\"input\", () => this.updateHeight());\n textarea.addEventListener(\"blur\", () => span.innerHTML = \"\");\n setTimeout(() => this.updateHeight(), 50);\n if (attrs.onmount) attrs.onmount(textarea);\n },\n onupdate() {\n this.updateHeight();\n },\n view ({attrs: {id, onkeydown, onfocus, onblur, oninput, getter, setter, display, placeholder, path, workbench}, state}) {\n const value = (state.editing) \n ? state.buffer \n : (display) ? display() : getter();\n \n const defaultKeydown = (e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n }\n }\n const startEdit = (e) => {\n if (onfocus) onfocus(e);\n state.editing = true;\n state.buffer = getter();\n }\n const finishEdit = (e) => {\n // safari can trigger blur more than once\n // for a given element, namely when clicking\n // into devtools. this prevents the second \n // blur setting node name to undefined/empty.\n if (state.editing) {\n state.editing = false;\n setter(state.buffer, true);\n state.buffer = undefined;\n }\n if (onblur) onblur(e);\n }\n const edit = (e) => {\n state.buffer = e.target.value;\n setter(state.buffer, false);\n if (oninput) {\n oninput(e);\n }\n }\n const handlePaste = (e) => {\n const textData = e.clipboardData.getData('Text');\n if (textData.length > 0) {\n e.preventDefault();\n e.stopPropagation();\n\n const lines = textData.split('\\n').map(line => line.trim()).filter(line => line.length > 0);\n state.buffer = lines.shift();\n setter(state.buffer, true);\n\n let node = path.node;\n for (const line of lines) {\n const newNode = workbench.workspace.new(line);\n newNode.parent = node.parent;\n newNode.siblingIndex = node.siblingIndex + 1;\n m.redraw.sync();\n const p = path.clone();\n p.pop();\n workbench.focus(p.append(newNode));\n node = newNode;\n }\n }\n }\n \n return (\n
    \n {value}\n \n
    \n )\n }\n}", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\n@component\nexport class Template {\n object?: Node;\n\n constructor() {\n }\n\n onAttach(node: Node) {\n this.object = node.parent;\n }\n\n handleIcon(collapsed: boolean = false): any {\n return (\n \n {collapsed?:null}\n \n \n \n );\n }\n\n toJSON(key: string): any {\n return {};\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"make-template\",\n title: \"Make Template\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n const tmpl = new Template();\n ctx.node.addComponent(tmpl);\n ctx.node.changed();\n }\n });\n }\n\n static findNode(ws: Workspace, name: string): Node|null {\n let node = null;\n ws.mainNode().walk((n) => {\n if (n.value instanceof Template && n.value.object.name === name) {\n node = n.value.object;\n return true;\n }\n return false;\n }, {includeComponents: true});\n return node;\n }\n}\n", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\nimport { Workbench, Workspace } from \"../workbench/mod.ts\";\nimport { Path } from \"../workbench/path.ts\";\nimport { Template } from \"./template.tsx\";\nimport { Picker } from \"../ui/picker.tsx\";\n\n@component\nexport class Tag {\n name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n afterEditor() {\n return TagBadge;\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"add-tag\",\n title: \"Add tag\",\n hidden: true,\n action: (ctx: Context, name: string) => {\n if (!ctx.node) return;\n const tag = new Tag(name);\n ctx.node.addComponent(tag);\n const tmpl = Template.findNode(workbench.workspace, name);\n if (tmpl) {\n tmpl.fields.map(f => f.duplicate()).forEach(f => {\n ctx.node.addLinked(\"Fields\", f);\n f.raw.Parent = ctx.node.raw.ID;\n });\n tmpl.children.map(c => c.duplicate()).forEach(c => {\n ctx.node.addChild(c);\n c.raw.Parent = ctx.node.raw.ID;\n });\n }\n ctx.node.changed();\n }\n });\n }\n\n static findAll(ws: Workspace): string[] {\n const tags = new Set();\n ws.mainNode().walk((n) => {\n if (n.value instanceof Tag) {\n tags.add(n.value.name);\n }\n return false;\n }, {includeComponents: true});\n return [...tags];\n }\n\n static findTagged(ws: Workspace, name: string): Node[] {\n const nodes = [];\n ws.mainNode().walk((n) => {\n if (n.value instanceof Tag && n.value.name === name) {\n nodes.push(n.parent);\n }\n return false;\n }, {includeComponents: true});\n return nodes;\n }\n\n static showPopover(bench: Workbench, path: Path, node: Node, inputview: Function, closer: Function) {\n const tags = Tag.findAll(bench.workspace);\n const trigger = bench.getInput(path);\n const rect = trigger.getBoundingClientRect();\n let x = document.body.scrollLeft + rect.x + (trigger.selectionStart * 10) + 20;\n let y = document.body.scrollTop + rect.y + rect.height;\n bench.showPopover(() => (\n {\n closer();\n bench.getInput(path).blur();\n node.name = node.name.replace(/\\s*#\\w*/, \"\");\n bench.executeCommand(\"add-tag\", {node, path}, item.name);\n }}\n onchange={(state) => {\n if (node.name.includes(\"#\")) {\n state.input = node.name.split(\"#\")[1];\n } else {\n state.input = \"\";\n }\n const filtered = [...tags]\n .filter(t => t.toLowerCase().startsWith(state.input.toLowerCase()))\n .map(t => {return {name: t}});\n if ((filtered[0] && filtered[0].name != state.input && state.input != \"\") || filtered.length === 0) {\n filtered.unshift({name: state.input, prefix: \"Create tag: \"});\n }\n state.items = filtered;\n }}\n inputview={inputview}\n itemview={(item) => \n
    \n
    {item.prefix||\"\"}{item.name}
    \n
    \n }\n />\n ), {top: `${y}px`, left: `${x}px`});\n }\n}\n\nconst TagBadge = {\n view({attrs: {node, component}}) {\n const onkeydown = (e) => {\n if (e.key === \"Backspace\") {\n node.removeComponent(component);\n node.changed();\n }\n };\n return (\n
    \n \n
    {component.name}
    \n
    \n )\n }\n}", "import { component } from \"../model/components.ts\";\nimport { Workbench, Context } from \"../workbench/mod.ts\";\nexport interface CodeExecutor {\n // executes the source and returns an output string.\n // exceptions in execution should be caught and returned as a string.\n execute(source: string, options: ExecuteOptions): Promise;\n\n canExecute(options: ExecuteOptions): boolean;\n}\n\nexport interface ExecuteOptions {\n language: string;\n}\n\n// defaultExecutor can be replaced with an external service, etc\nexport let defaultExecutor: CodeExecutor = {\n async execute(\n source: string,\n options: ExecuteOptions\n ): Promise {\n if (options.language !== \"javascript\") {\n return `Unsupported language: ${options.language}`;\n }\n let output = window.eval(source);\n //return JSON.stringify(output);\n return output.toString();\n },\n\n canExecute(options: ExecuteOptions): boolean {\n if (options.language === \"javascript\") {\n return true;\n }\n return false;\n },\n};\n\n@component\nexport class CodeBlock {\n code: string;\n language: string;\n detectLanguage: boolean;\n\n constructor(language?: string) {\n this.code = \"\";\n this.language = \"\";\n this.detectLanguage = true;\n\n if (language) {\n this.language = language;\n this.detectLanguage = false;\n }\n }\n\n childrenView() {\n return CodeEditorWithOutput;\n }\n\n handleIcon(collapsed: boolean = false): any {\n return (\n \n \n \n \n );\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"make-code-block\",\n title: \"Make Code Block\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document))\n return false;\n return true;\n },\n action: (ctx: Context, language?: string) => {\n const com = new CodeBlock(language);\n if (ctx?.node) {\n ctx.node.addComponent(com);\n ctx.node.changed();\n workbench.workspace.setExpanded(\n ctx.path.head,\n ctx.path.node,\n true\n );\n }\n },\n });\n }\n}\n\nconst CodeEditor = {\n oncreate(vnode) {\n const {\n dom,\n attrs: { path },\n } = vnode;\n const snippet = path.node.getComponent(CodeBlock);\n\n //@ts-ignore\n dom.jarEditor = new window.CodeJar(dom, (editor) => {\n // highlight.js does not trim old tags,\n // let's do it by this hack.\n editor.textContent = editor.textContent;\n //@ts-ignore\n window.hljs.highlightBlock(editor);\n\n if (snippet.detectLanguage) {\n //@ts-ignore\n snippet.language = window.hljs.highlightAuto(editor.textContent).language || \"\";\n }\n \n });\n dom.jarEditor.updateCode(snippet.code);\n dom.jarEditor.onUpdate((code) => {\n snippet.code = code;\n path.node.changed();\n });\n },\n\n view({ attrs: { workbench, path } }) {\n // this cancels the keydown on the outline node\n // so you can use arrow keys normally\n const onkeydown = (e) => e.stopPropagation();\n\n return
    ;\n },\n};\n\nconst Output = {\n view({ dom, state, attrs: { path } }) {\n const snippet = path.node.getComponent(CodeBlock);\n\n let handleClick = async () => {\n state.output = \"Running...\";\n try {\n const res = await defaultExecutor.execute(snippet.code, {\n language: snippet.language,\n });\n\n // Update output using m.prop to ensure it's persistent across re-renders\n state.output = res; // Call m.prop with the new value\n } catch (error) {\n state.output = error.toString();\n }\n };\n return (\n
    \n

    {state.output ? \"Output: \" + state.output : \"\"}

    \n \n
    \n );\n },\n};\n\nclass CodeEditorWithOutput {\n view(vnode) {\n return [m(CodeEditor, vnode.attrs), m(Output, vnode.attrs)];\n }\n}\n", "import { NewNode } from \"../ui/node/new.tsx\";\nimport { OutlineNode } from \"../ui/outline.tsx\";\n\nexport default {\n view({attrs: {workbench, path}}) {\n return (\n
    \n
    \n )\n }\n}", "\nexport const NewNode = {\n view({attrs: {workbench, path}}) {\n const keydown = (e) => {\n if (e.key === \"Tab\") {\n e.stopPropagation();\n e.preventDefault();\n if (node.childCount > 0) {\n const lastchild = path.node.children[path.node.childCount-1];\n workbench.executeCommand(\"insert-child\", {node: lastchild, path});\n }\n } else {\n workbench.executeCommand(\"insert-child\", {node: path.node, path}, e.target.value);\n }\n }\n return (\n
    \n \n \n \n \n
    \n \n
    \n
    \n )\n }\n}", "import { Workbench } from \"../workbench/mod.ts\";\nimport { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\nfunction debounce(func, timeout = 1000){\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => { func.apply(this, args); }, timeout);\n };\n}\n\n@component\nexport class SmartNode {\n workbench: Workbench;\n component?: Node;\n object?: Node;\n results?: Node[];\n query: string;\n\n lastQuery?: string;\n lastResultCount?: number;\n initialSearch: boolean;\n\n constructor() {\n this.workbench = window.workbench;\n this.searchDebounce = debounce(this.search.bind(this));\n this.query = \"\";\n this.initialSearch = false;\n }\n\n handleIcon(collapsed: boolean = false): any {\n return (\n \n {collapsed?:null}\n \n \n \n \n \n );\n }\n\n belowEditor() {\n return SmartFilter;\n }\n\n onAttach(node: Node) {\n this.component = node;\n this.object = node.parent;\n node.bus.observe((n: Node) => {\n if (!node.isDestroyed) {\n this.searchDebounce();\n }\n });\n }\n\n search() {\n if (!this.object) return;\n if (!this.query) {\n this.lastQuery = \"\";\n this.results = [];\n return;\n }\n this.initialSearch = true;\n\n const results = this.workbench.search(this.query)\n .filter(n => n.id !== this.object.id && n.id !== this.component.id);\n \n if (results.length !== this.lastResultCount || this.query !== this.lastQuery) {\n if (this.results) {\n // clean up old results\n this.results.forEach((n) => n.destroy());\n }\n this.results = results.map(n => {\n const ref = this.object.bus.make(\"\");\n ref.raw.Parent = \"@tmp\"; // cleaned up next import\n ref.refTo = n;\n return ref;\n });\n this.lastQuery = this.query;\n this.lastResultCount = results.length;\n }\n }\n\n objectChildren(node: Node, children: Node[]): Node[] {\n if (!this.results && this.query && !this.initialSearch) {\n this.search();\n }\n return this.results || [];\n }\n\n toJSON(key: string): any {\n return {\n query: this.query\n };\n }\n\n fromJSON(obj: any) {\n this.query = obj.query || \"\";\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"make-smart-node\",\n title: \"Make Smart Node\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.childCount > 0) return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n workbench.defocus();\n const search = new SmartNode();\n ctx.node.addComponent(search);\n workbench.workspace.setExpanded(ctx.path.head, ctx.node, true);\n if (ctx.node.name === \"\") {\n setTimeout(() => {\n // defocusing will overwrite this from buffer\n // without a delay\n ctx.node.name = \"Unnamed Smart Node\";\n m.redraw();\n document.querySelector(`#node-${ctx.path.id}-${ctx.node.id} input`).focus();\n }, 10);\n }\n }\n });\n }\n}\n\n\nconst SmartFilter = {\n view({attrs: {node, component, expanded}}) {\n if (!expanded) return;\n\n const oninput = (e) => {\n component.query = e.target.value;\n component.search();\n node.changed();\n }\n return (\n
    \n
    \n \n
    \n )\n }\n}", "import { NewNode } from \"../ui/node/new.tsx\";\nimport { OutlineNode } from \"../ui/outline.tsx\";\nimport { SmartNode } from \"../com/smartnode.tsx\";\n\nexport default {\n view({attrs: {workbench, path, alwaysShowNew}}) {\n let node = path.node;\n if (path.node.refTo) {\n node = path.node.refTo;\n }\n let showNew = false;\n if ((node.childCount === 0 && node.getLinked(\"Fields\").length === 0) || alwaysShowNew) {\n showNew = true;\n }\n // TODO: find some way to not hardcode this rule\n if (node.hasComponent(SmartNode)) {\n showNew = false;\n }\n return (\n
    \n
    \n {(node.getLinked(\"Fields\").length > 0) &&\n node.getLinked(\"Fields\").map(n => )\n }\n
    \n
    \n {(node.childCount > 0) && node.children.map(n => )}\n {showNew && }\n
    \n
    \n )\n }\n}", "import { NodeEditor, TextAreaEditor } from \"../ui/node/editor.tsx\";\nimport { OutlineNode } from \"../ui/outline.tsx\";\n\nexport default {\n view({attrs: {workbench, path}, state}) {\n const node = path.node;\n state.fields = (state.fields === undefined) ? new Set() : state.fields;\n node.children.forEach(n => {\n n.getLinked(\"Fields\").forEach(f => state.fields.add(f.name));\n });\n const getFieldEditor = (node, field) => {\n const fields = node.getLinked(\"Fields\").filter(f => f.name === field);\n if (fields.length === 0) return \"\";\n return \n }\n return (\n \n \n \n \n {[...state.fields].map(f => )}\n \n \n \n {node.children.map(n => (\n \n \n {[...state.fields].map(f => )}\n \n ))}\n \n
    Title{f}
    {getFieldEditor(n, f)}
    \n )\n }\n}", "\nimport { getNodeView } from \"../view/views.ts\";\n\nexport default {\n view({ attrs: { workbench, path }, state }) {\n const node = path.node;\n state.tabs = (state.tabs === undefined) ? new Set() : state.tabs;\n state.selectedTab = (state.selectedTab === undefined) ? \"\" : state.selectedTab;\n node.children.forEach(n => {\n state.tabs.add(n.raw);\n if (state.selectedTab === \"\") state.selectedTab = n.raw.ID;\n });\n const handleTabClick = (id) => {\n state.selectedTab = id;\n };\n const selectedNode = node.children.find(n => n.id === state.selectedTab);\n return (\n
    \n
    \n {[...state.tabs].map(n =>
    handleTabClick(n.ID)}>{n.Name}
    )}\n
    \n
    \n
    \n {m(getNodeView(selectedNode), {workbench, path: path.append(selectedNode)})}\n
    \n
    \n )\n }\n}\n", "import { NewNode } from \"../ui/node/new.tsx\";\nimport { OutlineNode } from \"../ui/outline.tsx\";\n\nexport default {\n view({attrs: {workbench, path, alwaysShowNew}}) {\n let node = path.node;\n if (path.node.refTo) {\n node = path.node.refTo;\n }\n let showNew = false;\n if ((node.childCount === 0 && node.getLinked(\"Fields\").length === 0) || alwaysShowNew) {\n showNew = true;\n }\n return (\n
    \n
    \n {(node.getLinked(\"Fields\").length > 0) &&\n node.getLinked(\"Fields\").map(n => )\n }\n
    \n
    \n {(node.childCount > 0) && node.children.map(n => )}\n {showNew && }\n
    \n
    \n )\n }\n}", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\n@component\nexport class InlineFrame {\n url: string;\n\n constructor() {\n this.url = \"https://example.com\";\n }\n\n childrenView() {\n return InlineFrameView;\n }\n handleIcon(collapsed: boolean = false): any {\n return (\n \n \n \n \n \n );\n }\n\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"make-iframe\",\n title: \"Make Inline Frame\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n const frame = new InlineFrame();\n if (ctx.node.name.startsWith(\"http://\") ||\n ctx.node.name.startsWith(\"https://\")) {\n frame.url = ctx.node.name;\n ctx.node.addComponent(frame);\n workbench.defocus();\n ctx.node.name = ctx.node.name.replaceAll(\"https://\", \"\").replaceAll(\"http://\");\n workbench.workspace.setExpanded(ctx.path.head, ctx.node, true);\n workbench.focus(ctx.path);\n }\n \n }\n });\n }\n\n}\n\nconst InlineFrameView = {\n view({attrs: {path}}) {\n const iframe = path.node.getComponent(InlineFrame);\n return (\n
    \n \n
    \n )\n }\n}", "import { Node } from \"../model/mod.ts\";\nimport { InlineFrame } from \"../com/iframe.tsx\";\n\nfunction tryFields(n: Node, fields: string[]): any|null {\n for (const field of fields) {\n const value = n.componentField(field);\n if (value) {\n return value;\n }\n }\n return null;\n}\n\nexport default {\n view({attrs: {workbench, path}}) {\n const node = path.node;\n return (\n
    \n {node.children.map(n => {\n const linkURL = tryFields(n, [\"linkURL\"]);\n const dateTime = tryFields(n, [\"updatedAt\", \"createdAt\"]);\n const userName = tryFields(n, [\"updatedBy\", \"createdBy\", \"username\"]); \n const thumbnailURL = tryFields(n, [\"thumbnailURL\", \"coverURL\"]);\n const frame = n.getComponent(InlineFrame);\n \n let thumbnail = ;\n if (frame) {\n thumbnail = (\n
    \n \n
    \n )\n }\n return (\n
    \n
    \n {(linkURL)\n ? {thumbnail}\n : thumbnail\n }\n
    \n
    \n {(linkURL)\n ? {n.name}\n : n.name}\n
    \n {userName &&
    \n {userName}\n
    }\n {dateTime &&
    \n {timeAgo(dateTime)}\n
    }\n
    \n )})}\n
    \n )\n }\n}\n\nfunction timeAgo(date) {\n if (!(date instanceof Date)) {\n throw new Error(\"Input must be a valid Date object.\");\n }\n \n const now = new Date();\n const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);\n\n const intervals = {\n year: 31536000,\n month: 2592000,\n week: 604800,\n day: 86400,\n hour: 3600,\n minute: 60,\n second: 1\n };\n\n for (const [unit, secondsInUnit] of Object.entries(intervals)) {\n const count = Math.floor(seconds / secondsInUnit);\n if (count > 0) {\n return `${count} ${unit}${count > 1 ? 's' : ''} ago`;\n }\n }\n\n return 'just now';\n}", "import empty from \"./empty.tsx\";\nimport list from \"./list.tsx\";\nimport table from \"./table.tsx\";\nimport tabs from \"./tabs.tsx\";\nimport document from \"./document.tsx\";\nimport cards from \"./cards.tsx\";\n\nexport const views = {\n list,\n table,\n tabs,\n document,\n cards\n}\n\n// deprecated. use getNodeView\nexport function getView(name) {\n return views[name] || empty;\n}\n\nexport function getNodeView(node) {\n return views[node.getAttr(\"view\") || \"list\"] || empty;\n}\n\nwindow.registerView = (name, view) => {\n views[name] = view;\n workbench.commands.registerCommand({\n id: `view-${name}`,\n title: `View as ${toTitleCase(name)}`,\n action: (ctx: Context) => {\n if (!ctx.node) return;\n ctx.node.setAttr(\"view\", name);\n }\n });\n}\n\nfunction toTitleCase(str) {\n return str.replace(\n /\\w\\S*/g,\n function(txt) {\n return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();\n }\n );\n}", "\nimport { Workbench, Path } from \"../workbench/mod.ts\";\nimport { objectCall, componentsWith, objectHas } from \"../model/hooks.ts\";\nimport { NodeEditor } from \"./node/editor.tsx\";\n\n// bunch of components I wish weren't direct dependencies\nimport { Checkbox } from \"../com/checkbox.tsx\";\nimport { Document } from \"../com/document.tsx\";\nimport { Tag } from \"../com/tag.tsx\";\nimport { CodeBlock } from \"../com/codeblock.tsx\";\n\nimport { getNodeView } from \"../view/views.ts\";\n\nexport interface Attrs {\n path: Path;\n workbench: Workbench;\n}\n\nexport interface State {\n hover: boolean;\n tagPopover?: Popover;\n}\n\ninterface Popover {\n onkeydown: Function;\n oninput: Function;\n}\n\nexport const OutlineEditor: m.Component = {\n view ({attrs: {workbench, path, alwaysShowNew}}) {\n return objectHas(path.node, \"childrenView\")\n ? m(componentsWith(path.node, \"childrenView\")[0].childrenView(), {workbench, path})\n : m(getNodeView(path.node), {workbench, path, alwaysShowNew});\n }\n}\n\n// handles: expanded state, node menu+handle, children\nexport const OutlineNode: m.Component = {\n view ({attrs, state, children}) {\n let {path, workbench} = attrs;\n let node = path.node;\n\n let isRef = false;\n let handleNode = node;\n if (node.refTo) {\n isRef = true;\n node = handleNode.refTo;\n }\n\n let isCut = false;\n if (workbench.clipboard && workbench.clipboard.op === \"cut\") {\n if (workbench.clipboard.node.id === node.id) {\n isCut = true;\n }\n }\n\n const expanded = workbench.workspace.getExpanded(path.head, handleNode);\n const placeholder = objectHas(node, \"handlePlaceholder\") ? objectCall(node, \"handlePlaceholder\") : '';\n\n const hover = (e) => {\n state.hover = true;\n e.stopPropagation();\n }\n \n const unhover = (e) => {\n state.hover = false;\n e.stopPropagation();\n }\n \n\n const cancelTagPopover = () => {\n if (state.tagPopover) {\n workbench.closePopover();\n state.tagPopover = undefined;\n }\n }\n\n const oninput = (e) => {\n if (state.tagPopover) {\n state.tagPopover.oninput(e);\n if (!e.target.value.includes(\"#\")) {\n cancelTagPopover();\n }\n } else {\n if (e.target.value.includes(\"#\")) {\n state.tagPopover = {};\n // Don't love that we're hard depending on Tag\n Tag.showPopover(workbench, path, node, (onkeydown, oninput) => {\n state.tagPopover = {onkeydown, oninput};\n }, cancelTagPopover);\n }\n }\n }\n\n const onkeydown = (e) => {\n if (state.tagPopover) {\n if (e.key === \"Escape\") {\n cancelTagPopover();\n return;\n }\n if (state.tagPopover.onkeydown(e) === false) {\n e.stopPropagation();\n return false;\n }\n }\n const anyModifiers = e.shiftKey || e.metaKey || e.altKey || e.ctrlKey;\n switch (e.key) {\n case \"ArrowUp\":\n if (e.target.selectionStart !== 0 && !anyModifiers) {\n e.stopPropagation()\n }\n break;\n case \"ArrowDown\":\n if (e.target.selectionStart !== e.target.value.length && e.target.selectionStart !== 0 && !anyModifiers) {\n e.stopPropagation()\n }\n break;\n case \"Backspace\":\n // cursor at beginning of empty text\n if (e.target.value === \"\") {\n e.preventDefault();\n e.stopPropagation();\n if (node.childCount > 0) {\n return;\n }\n workbench.executeCommand(\"delete\", {node, path, event: e});\n return;\n }\n // cursor at beginning of non-empty text\n if (e.target.value !== \"\" && e.target.selectionStart === 0 && e.target.selectionEnd === 0) {\n e.preventDefault();\n e.stopPropagation();\n if (node.childCount > 0) {\n return;\n }\n \n // TODO: make this work as a command?\n const above = workbench.workspace.findAbove(path);\n if (!above) {\n return;\n }\n const oldName = above.node.name;\n above.node.name = oldName+e.target.value;\n node.destroy();\n m.redraw.sync();\n workbench.focus(above, oldName.length);\n \n return;\n }\n break;\n case \"Enter\":\n e.preventDefault();\n if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return;\n \n // first check if node should become a code block\n // todo: this should be a hook or some loose coupled system\n if (e.target.value.startsWith(\"```\") && !node.hasComponent(CodeBlock)) {\n const lang = e.target.value.slice(3);\n if (lang) {\n workbench.executeCommand(\"make-code-block\", {node, path}, lang);\n e.stopPropagation();\n return;\n }\n }\n\n // cursor at end of text\n if (e.target.selectionStart === e.target.value.length) {\n if (node.childCount > 0 && workbench.workspace.getExpanded(path.head, node)) {\n workbench.executeCommand(\"insert-child\", {node, path}, \"\", 0);\n } else {\n workbench.executeCommand(\"insert\", {node, path});\n }\n e.stopPropagation();\n return;\n }\n // cursor at beginning of text\n if (e.target.selectionStart === 0) {\n workbench.executeCommand(\"insert-before\", {node, path});\n e.stopPropagation();\n return;\n }\n // cursor in middle of text\n if (e.target.selectionStart > 0 && e.target.selectionStart < e.target.value.length) {\n workbench.executeCommand(\"insert\", {node, path}, e.target.value.slice(e.target.selectionStart)).then(() => {\n node.name = e.target.value.slice(0, e.target.selectionStart);\n });\n e.stopPropagation();\n return;\n }\n break;\n }\n }\n\n const open = (e) => {\n e.preventDefault();\n e.stopPropagation();\n \n workbench.executeCommand(\"zoom\", {node, path});\n \n // clear text selection that happens after from double click\n if (document.selection && document.selection.empty) {\n document.selection.empty();\n } else if (window.getSelection) {\n window.getSelection().removeAllRanges();\n }\n }\n\n const toggle = (e) => {\n // TODO: hook or something so to not hardcode\n if (node.hasComponent(Document)) {\n open(e);\n return;\n }\n if (expanded) {\n workbench.executeCommand(\"collapse\", {node: handleNode, path});\n } else {\n workbench.executeCommand(\"expand\", {node: handleNode, path});\n }\n e.stopPropagation();\n }\n\n const subCount = (n) => {\n return n.childCount + n.getLinked(\"Fields\").length;\n }\n\n const showHandle = () => {\n if (node.id === workbench.context?.node?.id || state.hover) {\n return true;\n }\n if (node.name.length > 0) return true;\n if (placeholder.length > 0) return true;\n }\n\n return (\n
    \n
    \n workbench.showMenu(e, {node: handleNode, path})}\n oncontextmenu={(e) => workbench.showMenu(e, {node: handleNode, path})} \n data-menu=\"node\"\n viewBox=\"0 0 16 16\">\n {state.hover && }\n \n
    workbench.showMenu(e, {node: handleNode, path})} data-menu=\"node\" style={{ display: showHandle() ? 'block' : 'none' }}>\n {(objectHas(node, \"handleIcon\"))\n ? objectCall(node, \"handleIcon\", subCount(node) > 0 && !expanded)\n : \n {(subCount(node) > 0 && !expanded)?:null}\n ,\n {(isRef)?:null}\n \n }\n
    \n {(node.raw.Rel === \"Fields\") \n ?
    \n
    \n \n
    \n \n
    \n :
    \n {objectHas(node, \"beforeEditor\") && componentsWith(node, \"beforeEditor\").map(component => m(component.beforeEditor(), {node, component}))}\n \n {objectHas(node, \"afterEditor\") && componentsWith(node, \"afterEditor\").map(component => m(component.afterEditor(), {node, component}))}\n
    \n }\n
    \n {objectHas(node, \"belowEditor\") && componentsWith(node, \"belowEditor\").map(component => m(component.belowEditor(), {node, component, expanded}))}\n {(expanded === true) &&\n
    \n
    \n
    \n {objectHas(node, \"childrenView\")\n ? m(componentsWith(node, \"childrenView\")[0].childrenView(), {workbench, path})\n : m(getNodeView(node), {workbench, path})}\n
    \n
    \n }\n
    \n )\n }\n};\n\n\n\n\n\n\n\n", "import { OutlineEditor } from \"./outline.tsx\";\nimport { Path } from \"../workbench/mod.ts\";\n\nexport const QuickAdd = {\n view({attrs: {workbench, node}}) {\n const path = new Path(node, \"quickadd\");\n return (\n
    \n

    Quick Add

    \n \n
    \n \n \n
    \n
    \n )\n }\n}", "\nexport const Settings = {\n view({attrs: {workbench}, state}) {\n const currentTheme = workbench.workspace.settings.theme;\n state.selectedTheme = (state.selectedTheme === undefined) ? currentTheme : state.selectedTheme;\n const oninput = (e) => {\n state.selectedTheme = e.target.value;\n }\n return (\n
    \n

    Settings

    \n
    \n
    Theme
    \n
    \n \n
    \n
    \n
    \n \n \n
    \n
    \n )\n }\n}", "\nexport const LockStolenMessage = {\n view() {\n return (\n
    \n

    Refresh to view latest updates

    \n

    \n Your notes were updated in another browser session. Refresh the page to view the latest version.\n

    \n
    \n \n \n
    \n
    \n )\n }\n}\n\nexport const FirstTimeMessage = {\n view({attrs: {workbench}}) {\n return (\n
    \n

    Treehouse is under active development

    \n

    This is a preview based on our main branch, which is actively being developed.

    \n

    If you find a bug, please report it via \n  \n  > Submit Issue.\n

    \n

    \n Data is stored using localstorage, which you can reset via \n  \n  > Reset Demo.\n

    \n
    \n \n \n
    \n
    \n )\n }\n}\n\nexport const GitHubMessage = {\n view({attrs: {workbench, finished}}) {\n return (\n
    \n

    Login with GitHub

    \n

    The GitHub backend is experimental so use at your own risk!

    \n

    To store your workbench we will create a public repository called

    <username>.treehouse.sh
    if it doesn't already exist. You can manually make this repository private via GitHub if you want.

    \n

    You can Logout via the \n  \n  \n menu in the top right to return to the localstorage backend.\n

    \n
    \n \n \n
    \n
    \n )\n }\n}\n", "import { Backend } from \"../backend/mod.ts\";\nimport { KeyBindings } from \"../action/keybinds.ts\";\nimport { CommandRegistry } from \"../action/commands.ts\";\nimport { MenuRegistry } from \"../action/menus.ts\";\nimport { Node } from \"../model/mod.ts\";\nimport { objectHas, objectCall } from \"../model/hooks.ts\";\n\nimport { Menu } from \"../ui/menu.tsx\";\nimport { CommandPalette } from \"../ui/palette.tsx\";\nimport { QuickAdd } from \"../ui/quickadd.tsx\";\nimport { Settings } from \"../ui/settings.tsx\";\nimport { FirstTimeMessage, GitHubMessage, LockStolenMessage } from \"../ui/notices.tsx\";\n\nimport { Workspace, Context, Path } from \"./mod.ts\";\nimport { Tag } from \"../com/tag.tsx\";\n\nexport interface Clipboard {\n op: \"cut\"|\"copy\"|\"copyref\";\n node: Node;\n}\n\nexport interface Drawer {\n open: boolean;\n}\n\n/**\n * Workbench is the top-level controller for the Treehouse frontend.\n * \n * It manages the user action registries, open panels, open workspace,\n * user context, and provides an API used by UI components to \n * trigger various pop-overs, work with quick add, or anything else\n * not provided by the backend or workspace.\n */\nexport class Workbench {\n commands: CommandRegistry;\n keybindings: KeyBindings;\n menus: MenuRegistry;\n\n backend: Backend;\n workspace: Workspace;\n \n context: Context;\n panels: Path[];\n clipboard?: Clipboard;\n drawer: Drawer;\n\n popover: any;\n dialog: any;\n menu: any;\n\n constructor(backend: Backend) {\n this.commands = new CommandRegistry();\n this.keybindings = new KeyBindings();\n this.menus = new MenuRegistry();\n\n this.backend = backend;\n this.workspace = new Workspace(backend.files, backend.changes);\n\n this.context = {node: null};\n this.panels = [];\n this.drawer = { open: false };\n\n this.dialog = {body: () => null};\n this.menu = {body: () => null};\n \n }\n\n get mainPanel(): Path {\n return this.panels[0];\n }\n\n async initialize() {\n await this.workspace.load();\n\n this.workspace.rawNodes.forEach(n => this.backend.index.index(n));\n this.workspace.observe((n => {\n this.workspace.save();\n if (n.isDestroyed) {\n this.backend.index.remove(n.id);\n } else {\n this.backend.index.index(n.raw);\n n.components.forEach(com => this.backend.index.index(com.raw));\n }\n }));\n\n \n if (this.workspace.lastOpenedID) {\n this.openNewPanel(this.workspace.find(this.workspace.lastOpenedID) || this.workspace.mainNode());\n } else {\n this.openNewPanel(this.workspace.mainNode());\n }\n\n if (this.backend.loadExtensions) {\n await this.backend.loadExtensions();\n }\n \n if (this.workspace.settings.theme) {\n const css = document.createElement(\"link\");\n // TODO: figure out better way to couple themes than hardcoded hotlinked URL\n css.setAttribute(\"href\", `https://treehouse.sh/style/themes/${this.workspace.settings.theme}.css`);\n css.setAttribute(\"rel\", \"stylesheet\");\n css.setAttribute(\"type\", \"text/css\");\n document.head.appendChild(css);\n }\n\n m.redraw();\n \n }\n\n authenticated(): boolean {\n return this.backend.auth && this.backend.auth.currentUser();\n }\n\n openQuickAdd() {\n let node = this.workspace.find(\"@quickadd\");\n if (!node) {\n node = this.workspace.new(\"@quickadd\");\n }\n this.showDialog(() => m(QuickAdd, {workbench: this, node}), true);\n setTimeout(() => {\n document.querySelector(\"main > dialog .new-node input\").focus();\n }, 1);\n }\n\n commitQuickAdd() {\n const node = this.workspace.find(\"@quickadd\");\n if (!node) return;\n const today = this.todayNode();\n node.children.forEach(n => n.parent = today);\n }\n\n clearQuickAdd() {\n const node = this.workspace.find(\"@quickadd\");\n if (!node) return;\n node.children.forEach(n => n.destroy());\n }\n\n // TODO: goto workspace\n todayNode(): Node {\n const today = new Date();\n const dayNode = today.toUTCString().split(today.getFullYear())[0];\n const weekNode = `Week ${String(getWeekOfYear(today)).padStart(2, \"0\")}`;\n const yearNode = `${today.getFullYear()}`;\n const todayPath = [\"@calendar\", yearNode, weekNode, dayNode].join(\"/\");\n let todayNode = this.workspace.find(todayPath);\n if (!todayNode) {\n todayNode = this.workspace.new(todayPath);\n }\n return todayNode;\n }\n\n openToday() {\n this.open(this.todayNode());\n }\n\n open(n: Node) {\n // TODO: not sure this is still necessary\n if (!this.workspace.expanded[n.id]) {\n this.workspace.expanded[n.id] = {};\n }\n\n this.workspace.lastOpenedID = n.id;\n this.workspace.save();\n const p = new Path(n);\n this.panels[0] = p\n this.context.path = p;\n }\n\n openNewPanel(n: Node) {\n // TODO: not sure this is still necessary\n if (!this.workspace.expanded[n.id]) {\n this.workspace.expanded[n.id] = {};\n }\n\n this.workspace.lastOpenedID = n.id;\n this.workspace.save();\n const p = new Path(n);\n this.panels.push(p);\n this.context.path = p;\n }\n\n closePanel(panel: Path) {\n this.panels = this.panels.filter(p => p.name !== panel.name);\n }\n\n defocus() {\n const input = this.getInput(this.context.path);\n if (input) {\n input.blur();\n }\n this.context.node = null;\n this.context.path = null;\n }\n\n focus(path: Path, pos?: number = 0) {\n const input = this.getInput(path);\n if (input) {\n this.context.path = path;\n input.focus();\n if (pos !== undefined) {\n input.setSelectionRange(pos,pos);\n }\n } else {\n console.warn(\"unable to find input for\", path);\n }\n }\n\n getInput(path: Path): any {\n let id = `input-${path.id}-${path.node.id}`;\n // kludge:\n if (path.node.raw.Rel === \"Fields\") {\n if (path.node.name !== \"\") {\n id = id+\"-value\"; \n }\n }\n const el = document.getElementById(id);\n if (el.editor) {\n return el.editor;\n }\n return el;\n }\n\n canExecuteCommand(id: string, ctx: any, ...rest: any): boolean {\n ctx = this.newContext(ctx);\n return this.commands.canExecuteCommand(id, ctx, ...rest);\n }\n \n executeCommand(id: string, ctx: any, ...rest: any): Promise {\n ctx = this.newContext(ctx);\n console.log(id, ctx, ...rest);\n return this.commands.executeCommand(id, ctx, ...rest);\n }\n\n newContext(ctx: any): Context {\n return Object.assign({}, this.context, ctx);\n }\n\n showMenu(event: Event, ctx: any, style?: {}) {\n event.stopPropagation();\n event.preventDefault();\n const trigger = event.target.closest(\"*[data-menu]\");\n const rect = trigger.getBoundingClientRect();\n if (!style) {\n const align = trigger.dataset[\"align\"] || \"left\";\n style = {\n top: `${document.body.scrollTop+rect.y+rect.height}px`\n }\n if (align === \"right\") {\n style.marginLeft = \"auto\";\n style.marginRight = `${document.body.offsetWidth - rect.right}px`;\n } else {\n style.marginLeft = `${document.body.scrollLeft+rect.x}px`;\n style.marginRight = \"auto\";\n }\n }\n const items = this.menus.menus[trigger.dataset[\"menu\"]];\n const cmds = items.filter(i => i.command).map(i => this.commands.commands[i.command]);\n if (!items) return;\n this.menu = {body: () => m(Menu, { \n workbench: this,\n ctx: this.newContext(ctx), \n items: items,\n commands: cmds,\n }), style};\n m.redraw();\n setTimeout(() => {\n // this next frame timeout is so any current dialog can close before attempting\n // to showModal on already open dialog, which causes exception.\n document.querySelector(\"main > dialog.menu\").showModal();\n }, 0);\n }\n\n closeMenu() {\n document.querySelector(\"main > dialog.menu\").close();\n workbench.menu.body = () => null;\n }\n\n showPalette(x: number, y: number, ctx: Context) {\n this.showDialog(() => m(CommandPalette, {workbench: this, ctx}), false, {left: `${x}px`, top: `${y}px`});\n }\n\n showNotice(notice, finished) {\n this.showDialog(() => m({\n \"firsttime\": FirstTimeMessage,\n \"github\": GitHubMessage,\n \"lockstolen\": LockStolenMessage,\n }[notice], {workbench: this, finished}), true, undefined, (notice===\"lockstolen\")?true:false);\n }\n\n toggleDrawer() {\n this.drawer.open = !this.drawer.open;\n m.redraw();\n }\n\n showSettings() {\n this.showDialog(() => m(Settings, {workbench: this}), true);\n }\n\n showPopover(body: any, style?: {}) {\n this.popover = {body, style};\n m.redraw();\n }\n\n closePopover() {\n this.popover = null;\n m.redraw();\n }\n\n showDialog(body: any, backdrop?: boolean, style?: {}, explicitClose?: boolean) {\n this.dialog = {body, backdrop, style, explicitClose};\n m.redraw();\n setTimeout(() => {\n // this next frame timeout is so any current dialog can close before attempting\n // to showModal on already open dialog, which causes exception.\n document.querySelector(\"main > dialog.modal\").showModal();\n }, 0);\n }\n\n isDialogOpen(): boolean {\n return document.querySelector(\"main > dialog.modal\").hasAttribute(\"open\");\n }\n\n closeDialog() {\n document.querySelector(\"main > dialog.modal\").close();\n this.dialog.body = () => null;\n }\n\n search(query: string): Node[] {\n if (!query) return [];\n // this regex selects spaces NOT inside quotes\n let splitQuery = query.split(/\\s+(?=(?:[^\\'\"]*[\\'\"][^\\'\"]*[\\'\"])*[^\\'\"]*$)/);\n let textQuery = splitQuery.filter(term => !term.includes(\":\")).join(\" \");\n let fieldQuery = Object.fromEntries(splitQuery.filter(term => term.includes(\":\")).map(term => term.toLowerCase().split(\":\")));\n if (!textQuery && Object.keys(fieldQuery).length > 0) {\n // when text query is empty, no results will show up,\n // but we index field names, so this works for now.\n textQuery = Object.keys(fieldQuery)[0];\n }\n const passFieldQuery = (node: Node): boolean => {\n // kludgy filter on fields\n if (Object.keys(fieldQuery).length > 0) {\n const fields = {};\n for (const f of node.getLinked(\"Fields\")) {\n fields[f.name.toLowerCase()] = f.value.toLowerCase();\n }\n for (const f in fieldQuery) {\n const field = fields[f.replace(/['\"]/g, \"\")];\n if (!field || field !== fieldQuery[f].replace(/['\"]/g, \"\")) {\n return false;\n }\n }\n }\n return true;\n }\n // simple, limited search for tag implementation\n if (textQuery.startsWith(\"#\")) {\n return Tag.findTagged(this.workspace, textQuery.replace(\"#\", \"\")).filter(passFieldQuery);\n }\n let resultCache = {};\n this.backend.index.search(textQuery).forEach(id => {\n let node = window.workbench.workspace.find(id);\n if (!node) {\n return;\n }\n // if component/field value, get the parent\n if (node.value) {\n node = node.parent;\n // parent might not actually exist\n if (!node.raw) return;\n }\n if (!passFieldQuery(node)) {\n return;\n }\n resultCache[node.id] = node;\n });\n return Object.values(resultCache);\n }\n\n\n}\n\n\nfunction getWeekOfYear(date) {\n var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));\n var dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));\n return Math.ceil((((d - yearStart) / 86400000) + 1)/7);\n}", "\n/**\n* Secure Hash Algorithm (SHA1)\n* http://www.webtoolkit.info/\n**/\nexport function SHA1(msg) {\n function rotate_left(n,s) {\n var t4 = ( n<>>(32-s));\n return t4;\n };\n function lsb_hex(val) {\n var str='';\n var i;\n var vh;\n var vl;\n for( i=0; i<=6; i+=2 ) {\n vh = (val>>>(i*4+4))&0x0f;\n vl = (val>>>(i*4))&0x0f;\n str += vh.toString(16) + vl.toString(16);\n }\n return str;\n };\n function cvt_hex(val) {\n var str='';\n var i;\n var v;\n for( i=7; i>=0; i-- ) {\n v = (val>>>(i*4))&0x0f;\n str += v.toString(16);\n }\n return str;\n };\n function Utf8Encode(string) {\n string = string.replace(/\\r\\n/g,'\\n');\n var utftext = '';\n for (var n = 0; n < string.length; n++) {\n var c = string.charCodeAt(n);\n if (c < 128) {\n utftext += String.fromCharCode(c);\n }\n else if((c > 127) && (c < 2048)) {\n utftext += String.fromCharCode((c >> 6) | 192);\n utftext += String.fromCharCode((c & 63) | 128);\n }\n else {\n utftext += String.fromCharCode((c >> 12) | 224);\n utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n utftext += String.fromCharCode((c & 63) | 128);\n }\n }\n return utftext;\n };\n var blockstart;\n var i, j;\n var W = new Array(80);\n var H0 = 0x67452301;\n var H1 = 0xEFCDAB89;\n var H2 = 0x98BADCFE;\n var H3 = 0x10325476;\n var H4 = 0xC3D2E1F0;\n var A, B, C, D, E;\n var temp;\n msg = Utf8Encode(msg);\n var msg_len = msg.length;\n var word_array = new Array();\n for( i=0; i>>29 );\n word_array.push( (msg_len<<3)&0x0ffffffff );\n for ( blockstart=0; blockstart n.id)].join(\":\"));\n }\n\n get node(): Node {\n return this.nodes[this.nodes.length-1];\n }\n\n get previous(): Node|null {\n if (this.nodes.length < 2) return null;\n return this.nodes[this.nodes.length-2];\n }\n\n get head(): Node {\n return this.nodes[0];\n }\n}\n\n", "import { RawNode, Node as INode, Bus as IBus, WalkFunc, ObserverFunc, WalkOptions } from \"../mod.ts\";\nimport { componentName, getComponent, inflateToComponent } from \"../components.ts\";\nimport { triggerHook, hasHook } from \"../hooks.ts\";\nimport { Node } from \"./mod.ts\";\n\nexport class Bus {\n nodes: Record;\n observers: ObserverFunc[];\n\n constructor() {\n this.nodes = {\"@root\": {\n ID: \"@root\",\n Name: \"@root\",\n Linked: {Children: [], Components: []},\n Attrs: {}\n }};\n this.observers = [];\n }\n\n changed(n: INode) {\n this.observers.forEach(cb => cb(n));\n }\n\n /* Bus interface */\n\n import(nodes: RawNode[]) {\n for (const n of nodes) {\n if (n.Value && getComponent(n.Name)) {\n n.Value = inflateToComponent(n.Name, n.Value);\n n.Rel = \"Components\"; // kludge\n }\n this.nodes[n.ID] = n;\n }\n for (const n of nodes) {\n // clear stored tmp nodes\n if (n.Parent === \"@tmp\") {\n delete this.nodes[n.ID];\n continue;\n }\n // clear nodes with no parent that aren't system\n if (!n.ID.startsWith(\"@\") && n.Parent === undefined) {\n delete this.nodes[n.ID];\n continue;\n }\n // clear nodes with non-existant parent\n if (n.Parent && !this.nodes[n.Parent]) {\n delete this.nodes[n.ID];\n continue;\n }\n const node = this.find(n.ID);\n if (node) {\n // check orphan\n if (node.parent && !node.parent.raw) {\n delete this.nodes[n.ID];\n continue;\n }\n // trigger attach\n triggerHook(node, \"onAttach\", node);\n }\n }\n }\n\n export(): RawNode[] {\n const nodes: RawNode[] = [];\n for (const n of Object.values(this.nodes)) {\n nodes.push(n);\n }\n return nodes;\n }\n\n make(name: string, value?: any): INode {\n let parent: INode|null = null;\n if (name.includes(\"/\")) {\n const parts = name.split(\"/\");\n parent = this.find(parts[0]);\n for (let i = 1; i < parts.length-1; i++) {\n if (parent === null) {\n throw \"unable to get root\";\n }\n \n let child = parent.find(parts[i]);\n if (!child) {\n child = this.make(parts.slice(0, i+1).join(\"/\"));\n }\n parent = child;\n }\n name = parts[parts.length-1];\n }\n const id = (name.startsWith(\"@\"))?name:uniqueId();\n this.nodes[id] = {\n ID: id,\n Name: name,\n Value: value,\n Linked: {Children: [], Components: []},\n Attrs: {}\n };\n const node = new Node(this, id);\n if (parent) {\n node.parent = parent;\n }\n return node;\n }\n\n // destroys node but not linked nodes\n destroy(n: INode) {\n const p = n.parent;\n if (p !== null && !p.isDestroyed) {\n let rel = n.raw.Rel || \"Children\";\n if (p.raw.Linked[rel].includes(n.id)) {\n p.raw.Linked[rel].splice(n.siblingIndex, 1);\n }\n }\n delete this.nodes[n.id];\n if (p) {\n this.changed(p);\n }\n }\n\n roots(): INode[] {\n return Object.values(this.nodes).filter(n => n.Parent === undefined).map(n => new Node(this, n.ID));\n }\n\n root(name?: string): INode|null {\n name = name || \"@root\"\n const node = this.roots().find(root => root.name === name);\n if (node === undefined) return null;\n return node;\n }\n\n find(path:string): INode|null {\n const byId = this.nodes[path];\n if (byId) return new Node(this, byId.ID);\n const parts = path.split(\"/\");\n if (parts.length === 1 && parts[0].startsWith(\"@\")) {\n // did not find @id by ID so return null\n return null;\n }\n let cur = this.root(parts[0]);\n if (!cur && this.nodes[parts[0]]) {\n cur = new Node(this, this.nodes[parts[0]].ID);\n }\n if (cur) {\n parts.shift();\n } else {\n cur = this.root(\"@root\"); \n }\n if (!cur) {\n return null;\n }\n const findChild = (n: INode, name: string): INode|undefined => {\n if (n.refTo) {\n n = n.refTo;\n }\n return n.children.find(child => child.name === name);\n }\n for (const name of parts) {\n const child = findChild(cur, name);\n if (!child) return null;\n cur = child;\n }\n return cur;\n }\n\n walk(fn: WalkFunc, opts?: WalkOptions) {\n for (const root of this.roots()) {\n if (root.walk(fn, opts)) return;\n }\n }\n\n observe(fn: ObserverFunc) {\n this.observers.push(fn);\n }\n}\n\nconst uniqueId = () => {\n const dateString = Date.now().toString(36);\n const randomness = Math.random().toString(36).substring(2);\n return dateString + randomness;\n};\n", "import { RawNode, Node as INode, Bus as IBus, WalkFunc, WalkOptions } from \"../mod.ts\";\nimport { componentName, getComponent, duplicate } from \"../components.ts\";\nimport { triggerHook, hasHook } from \"../hooks.ts\";\nimport { Bus } from \"./mod.ts\";\n\nexport class Node {\n _id: string;\n _bus: Bus;\n\n constructor(bus: Bus, id: string) {\n this._bus = bus;\n this._id = id;\n }\n\n [Symbol.for(\"Deno.customInspect\")]() {\n return `Node[${this.id}:${this.name}]`;\n }\n\n /* Node interface */\n\n get id(): string {\n return this._id;\n }\n\n get bus(): IBus {\n return this._bus;\n }\n\n get raw(): RawNode {\n const raw = this._bus.nodes[this.id];\n if (!raw) throw `use of non-existent node ${this.id}`;\n return raw;\n }\n\n\n get name(): string {\n if (this.refTo) {\n return this.refTo.name;\n }\n return this.raw.Name;\n }\n\n set name(val: string) {\n if (this.refTo) {\n this.refTo.name = val;\n } else {\n this.raw.Name = val;\n }\n this.changed();\n }\n\n get value(): any {\n if (this.refTo) {\n return this.refTo.value;\n }\n return this.raw.Value;\n }\n\n set value(val: string) {\n if (this.refTo) {\n this.refTo.value = val;\n } else {\n this.raw.Value = val;\n }\n this.changed();\n }\n\n get parent(): INode|null {\n if (!this.raw.Parent) return null;\n if (!this._bus.nodes[this.raw.Parent]) return null;\n return new Node(this._bus, this.raw.Parent);\n }\n\n set parent(n: INode|null) {\n const p = this.parent;\n if (p !== null) {\n p.raw.Linked.Children.splice(this.siblingIndex, 1);\n }\n if (n !== null) {\n this.raw.Parent = n.id;\n n.raw.Linked.Children.push(this.id);\n triggerHook(n, \"onAttach\", n);\n } else {\n this.raw.Parent = undefined;\n }\n this.changed();\n }\n\n get refTo(): INode|null {\n const id = this.raw.Attrs[\"refTo\"];\n if (!id) return null;\n const refTo = this._bus.nodes[id];\n if (!refTo) return null;\n return new Node(this._bus, id);\n }\n\n set refTo(n: INode|null) {\n if (!n) {\n delete this.raw.Attrs[\"refTo\"];\n this.changed();\n return;\n }\n this.raw.Attrs[\"refTo\"] = n.id;\n this.changed();\n }\n\n get siblingIndex(): number {\n const p = this.parent;\n if (p === null) return 0;\n let rel = this.raw.Rel || \"Children\";\n return p.raw.Linked[rel].findIndex(id => id === this.id);\n }\n\n set siblingIndex(i: number) {\n const p = this.parent;\n if (p === null) return;\n let rel = this.raw.Rel || \"Children\";\n p.raw.Linked[rel].splice(this.siblingIndex, 1);\n p.raw.Linked[rel].splice(i, 0, this.id);\n p.changed();\n }\n\n get prevSibling(): INode|null {\n const p = this.parent;\n if (p === null) return null;\n if (this.siblingIndex === 0) return null;\n let rel = this.raw.Rel || \"Children\";\n return p.getLinked(rel)[this.siblingIndex-1];\n }\n\n get nextSibling(): INode|null {\n const p = this.parent;\n if (p === null) return null;\n if (this.siblingIndex === p.children.length-1) return null;\n let rel = this.raw.Rel || \"Children\";\n return p.getLinked(rel)[this.siblingIndex+1];\n }\n\n get ancestors(): INode[] {\n const anc = [];\n let p = this.parent;\n while (p !== null) {\n anc.push(p);\n p = p.parent;\n }\n return anc;\n }\n\n get isDestroyed(): boolean {\n return !this._bus.nodes.hasOwnProperty(this.id);\n }\n\n get path(): string {\n let cur: INode|null = this;\n const path = [];\n while (cur) {\n path.unshift(cur.name);\n cur = cur.parent;\n }\n return path.join(\"/\");\n }\n\n get children(): INode[] {\n if (this.refTo) return this.refTo.children;\n let children: INode[] = [];\n if (this.raw.Linked.Children) {\n children = this.raw.Linked.Children.map(id => new Node(this._bus, id));\n };\n for (const com of this.components) {\n if (hasHook(com, \"objectChildren\")) {\n return triggerHook(com, \"objectChildren\", this, children);\n }\n }\n return children;\n }\n\n get childCount(): number {\n if (this.refTo) return this.refTo.childCount;\n for (const com of this.components) {\n if (hasHook(com, \"objectChildren\")) {\n return triggerHook(com, \"objectChildren\", this, null).length;\n }\n }\n if (!this.raw.Linked.Children) return 0;\n return this.raw.Linked.Children.length;\n }\n\n addChild(node: INode) {\n if (this.refTo) {\n this.refTo.addChild(node);\n return;\n }\n this.raw.Linked.Children.push(node.id);\n this.changed();\n } \n\n removeChild(node: INode) {\n if (this.refTo) {\n this.refTo.removeChild(node);\n return;\n }\n const children = this.raw.Linked.Children.filter(id => id === node.id);\n this.raw.Linked.Children = children;\n this.changed();\n }\n\n get fields(): INode[] {\n if (!this.raw.Linked.Fields) return [];\n return this.raw.Linked.Fields.map(id => new Node(this._bus, id));\n }\n\n get fieldCount(): number {\n if (!this.raw.Linked.Fields) return 0;\n return this.raw.Linked.Fields.length;\n }\n\n get components(): INode[] {\n if (!this.raw.Linked.Components) return [];\n return this.raw.Linked.Components.map(id => new Node(this._bus, id));\n }\n\n get componentCount(): number {\n if (!this.raw.Linked.Components) return 0;\n return this.raw.Linked.Components.length;\n }\n\n componentField(name: string): any|null {\n for (const com of this.components) {\n if (Object.keys(com.value||{}).includes(name)) {\n return com.value[name];\n }\n }\n return null;\n }\n\n addComponent(obj: any) {\n const node = this.bus.make(componentName(obj), obj);\n node.raw.Parent = this.id;\n node.raw.Rel = \"Components\" // kludge\n this.raw.Linked.Components.push(node.id);\n triggerHook(node, \"onAttach\", node);\n this.changed();\n } \n\n removeComponent(obj: any) {\n let coms;\n if (obj.name && getComponent(obj)) {\n coms = this.components.filter(n => n.name === componentName(obj));\n } else {\n coms = this.components.filter(n => n.value === obj);\n }\n if (coms.length > 0) {\n coms[0].destroy();\n }\n this.changed();\n }\n \n hasComponent(type: any): boolean {\n const coms = this.components.filter(n => n.name === componentName(type));\n if (coms.length > 0) {\n return true;\n }\n return false;\n }\n\n getComponent(type: any): any|null {\n const coms = this.components.filter(n => n.name === componentName(type));\n if (coms.length > 0) {\n return coms[0].value;\n }\n return null;\n }\n // getComponentsInChildren\n // getComponentsInParents\n\n getLinked(rel: string): INode[] {\n if (!this.raw.Linked[rel]) return [];\n return this.raw.Linked[rel].map(id => new Node(this._bus, id));\n }\n\n addLinked(rel: string, node: INode) {\n if (!this.raw.Linked[rel]) {\n this.raw.Linked[rel] = [];\n }\n node.raw.Rel = rel; // kludge\n this.raw.Linked[rel].push(node.id);\n this.changed();\n } \n\n removeLinked(rel: string, node: INode) {\n if (!this.raw.Linked[rel]) {\n this.raw.Linked[rel] = [];\n }\n const linked = this.raw.Linked[rel].filter(id => id === node.id);\n this.raw.Linked[rel] = linked;\n this.changed();\n }\n\n moveLinked(rel: string, node: INode, idx: number) {\n if (!this.raw.Linked[rel]) {\n this.raw.Linked[rel] = [];\n }\n const oldIdx = this.raw.Linked[rel].findIndex(id => id === node.id);\n if (oldIdx === -1) return;\n const linked = this.raw.Linked[rel];\n linked.splice(idx, 0, linked.splice(oldIdx, 1)[0]);\n this.raw.Linked[rel] = linked;\n this.changed();\n }\n\n getAttr(name: string): string {\n return this.raw.Attrs[name] || \"\";\n }\n\n setAttr(name: string, value: string) {\n this.raw.Attrs[name] = value;\n this.changed();\n }\n\n find(path: string): INode|null {\n return this.bus.find([this.path, path].join(\"/\"));\n }\n\n walk(fn: WalkFunc, opts?: WalkOptions): boolean {\n opts = opts || {\n followRefs: false,\n includeComponents: false\n };\n if (fn(this)) {\n return true;\n }\n let children = this.children;\n if (this.refTo && opts.followRefs) {\n if (fn(this.refTo)) {\n return true;\n }\n children = this.refTo.children;\n }\n for (const child of children) {\n if (child.walk(fn, opts)) return true;\n }\n if (opts.includeComponents) {\n for (const com of this.components) {\n if (com.walk(fn, opts)) return true;\n }\n }\n return false;\n }\n\n destroy() {\n if (this.isDestroyed) return;\n if (this.refTo) {\n this._bus.destroy(this);\n return;\n }\n const nodes: INode[] = [];\n this.walk((n: INode): boolean => {\n nodes.push(n);\n return false;\n }, {\n followRefs: false,\n includeComponents: true\n });\n nodes.reverse().forEach(n => this._bus.destroy(n));\n }\n\n duplicate(): INode {\n const n = this._bus.make(this.name, duplicate(this.value));\n n.raw.Rel = this.raw.Rel;\n this.fields.map(f => f.duplicate()).forEach(f => {\n n.addLinked(\"Fields\", f);\n f.raw.Parent = n.raw.ID;\n });\n this.components.map(c => c.duplicate()).forEach(c => {\n n.addLinked(\"Components\", c);\n c.raw.Parent = n.raw.ID;\n });\n this.children.map(c => c.duplicate()).forEach(c => {\n n.addChild(c);\n c.raw.Parent = n.raw.ID;\n });\n return n;\n }\n\n changed() {\n this._bus.changed(this);\n }\n \n // duplicate?\n}\n", "import { FileStore, ChangeNotifier } from \"../backend/mod.ts\";\nimport { Bus, Node, RawNode } from \"../model/mod.ts\";\nimport { Path } from \"./mod.ts\";\nimport * as module from \"../model/module/mod.ts\";\n\n\n/**\n * Workspace is a container for nodes and manages marshaling them using\n * the FileStore backend API. It also keeps track of what nodes have been\n * expanded and what node was last opened. It serializes as JSON with a\n * version indicator and will handle migrations of old versions to the \n * latest when loading. Saving is currently debounced here so this applies\n * to all backends. \n */\nexport class Workspace {\n fs: FileStore;\n bus: Bus;\n\n lastOpenedID: string;\n expanded: { [key: string]: { [key: string]: boolean } }; // [rootid][id]\n settings: {};\n\n constructor(fs: FileStore, changes?: ChangeNotifier) {\n this.fs = fs;\n this.bus = new module.Bus();\n this.expanded = {};\n this.settings = {};\n\n if (changes) {\n changes.registerNotifier(this.reload.bind(this));\n }\n\n this.writeDebounce = debounce(async (path, contents) => {\n try {\n await this.fs.writeFile(path, contents);\n console.log(\"Saved workspace.\");\n } catch (e: Error) {\n console.error(e);\n document.dispatchEvent(new CustomEvent(\"BackendError\"));\n }\n });\n }\n\n get rawNodes(): RawNode[] {\n return this.bus.export();\n }\n\n observe(fn: (n: Node) => void) {\n this.bus.observe(fn);\n }\n\n async save(immediate?: boolean) {\n const contents = JSON.stringify({\n version: 1,\n lastopen: this.lastOpenedID,\n expanded: this.expanded,\n nodes: this.rawNodes,\n settings: this.settings\n }, null, 2);\n if (immediate) {\n await this.fs.writeFile(\"workspace.json\", contents);\n } else {\n this.writeDebounce(\"workspace.json\", contents);\n }\n }\n\n migrateRawNode(n: RawNode): RawNode {\n if (n.Name === \"treehouse.SearchNode\") {\n n.Name = \"treehouse.SmartNode\";\n }\n return n;\n }\n\n async reload(nodeIDs: string[]) {\n let doc = JSON.parse(await this.fs.readFile(\"workspace.json\") || \"{}\");\n if (doc.nodes) {\n doc.nodes = doc.nodes.filter(n => nodeIDs.includes(n.ID));\n doc.nodes = doc.nodes.map(this.migrateRawNode);\n this.bus.import(doc.nodes);\n m.redraw();\n console.log(`Reloaded ${doc.nodes.length} nodes.`);\n }\n }\n\n async load() {\n let doc = JSON.parse(await this.fs.readFile(\"workspace.json\") || \"{}\");\n if (doc.nodes) {\n doc.nodes = doc.nodes.map(this.migrateRawNode);\n this.bus.import(doc.nodes);\n console.log(`Loaded ${doc.nodes.length} nodes.`);\n }\n if (doc.expanded) {\n // Only import the node keys that still exist\n // in the workspace.\n for (const n in doc.expanded) {\n for (const i in doc.expanded[n]) {\n if (this.bus.find(i)) {\n if (!this.expanded[n]) this.expanded[n] = {};\n this.expanded[n][i] = doc.expanded[n][i];\n }\n }\n }\n }\n if (doc.lastopen) {\n this.lastOpenedID = doc.lastopen;\n }\n if (doc.settings) {\n this.settings = Object.assign(this.settings, doc.settings);\n }\n }\n\n mainNode(): Node {\n let main = this.bus.find(\"@workspace\");\n if (!main) {\n console.info(\"Building missing workspace node.\")\n const root = this.bus.find(\"@root\");\n const ws = this.bus.make(\"@workspace\");\n ws.name = \"Workspace\";\n ws.parent = root;\n const cal = this.bus.make(\"@calendar\");\n cal.name = \"Calendar\";\n cal.parent = ws;\n const home = this.bus.make(\"Home\");\n home.parent = ws;\n main = ws;\n }\n return main;\n }\n\n find(path: string): Node | null {\n return this.bus.find(path)\n }\n\n new(name: string, value?: any): Node {\n return this.bus.make(name, value);\n }\n\n // TODO: take single Path\n getExpanded(head: Node, n: Node): boolean {\n if (!this.expanded[head.id]) {\n this.expanded[head.id] = {};\n }\n let expanded = this.expanded[head.id][n.id];\n if (expanded === undefined) {\n expanded = false;\n }\n return expanded;\n }\n\n // TODO: take single Path\n setExpanded(head: Node, n: Node, b: boolean) {\n if (!this.expanded[head.id]) {\n this.expanded[head.id] = {};\n }\n this.expanded[head.id][n.id] = b;\n this.save();\n }\n\n findAbove(path: Path): Path | null {\n if (path.node.id === path.head.id) {\n return null;\n }\n const p = path.clone();\n p.pop(); // pop to parent\n let prev = path.node.prevSibling;\n if (!prev) {\n // if not a field and parent has fields, return last field\n const fieldCount = path.previous.getLinked(\"Fields\").length;\n if (path.node.raw.Rel !== \"Fields\" && fieldCount > 0) {\n return p.append(path.previous.getLinked(\"Fields\")[fieldCount - 1]);\n }\n // if no prev sibling, and no fields, return parent\n return p;\n }\n const lastSubIfExpanded = (p: Path): Path => {\n const expanded = this.getExpanded(path.head, p.node);\n if (!expanded) {\n // if not expanded, return input path\n return p;\n }\n const fieldCount = p.node.getLinked(\"Fields\").length;\n if (p.node.childCount === 0 && fieldCount > 0) {\n const lastField = p.node.getLinked(\"Fields\")[fieldCount - 1];\n // if expanded, no children, has fields, return last field or its last sub if expanded\n return lastSubIfExpanded(p.append(lastField));\n }\n if (p.node.childCount === 0) {\n // expanded, no fields, no children\n return p;\n }\n const lastChild = p.node.children[p.node.childCount - 1];\n // return last child or its last sub if expanded\n return lastSubIfExpanded(p.append(lastChild));\n }\n // return prev sibling or its last child if expanded\n return lastSubIfExpanded(p.append(prev));\n }\n\n findBelow(path: Path): Path | null {\n // TODO: find a way to indicate pseudo \"new\" node for expanded leaf nodes\n const p = path.clone();\n if (this.getExpanded(path.head, path.node) && path.node.getLinked(\"Fields\").length > 0) {\n // if expanded and fields, return first field\n return p.append(path.node.getLinked(\"Fields\")[0]);\n }\n if (this.getExpanded(path.head, path.node) && path.node.childCount > 0) {\n // if expanded and children, return first child\n return p.append(path.node.children[0]);\n }\n const nextSiblingOrParentNextSibling = (p: Path): Path | null => {\n const next = p.node.nextSibling;\n if (next) {\n p.pop(); // pop to parent\n // if next sibling, return that\n return p.append(next);\n }\n const parent = p.previous;\n if (!parent) {\n // if no parent, return null\n return null;\n }\n if (p.node.raw.Rel === \"Fields\" && parent.childCount > 0) {\n p.pop(); // pop to parent\n // if field and parent has children, return first child\n return p.append(parent.children[0]);\n }\n p.pop(); // pop to parent\n // return parents next sibling or parents parents next sibling\n return nextSiblingOrParentNextSibling(p);\n }\n // return next sibling or parents next sibling\n return nextSiblingOrParentNextSibling(p);\n }\n\n}\n\n\nfunction debounce(func, timeout = 3000) {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => { func.apply(this, args); }, timeout);\n };\n}\n", "export const Drawer = {\n view({ attrs, children }) {\n const open = attrs.open;\n return (\n
    \n {children}\n
    \n )\n }\n};\n", "import { OutlineEditor } from \"./outline.tsx\";\nimport { NodeEditor } from \"./node/editor.tsx\";\n\nexport const Panel = {\n view({ attrs }) {\n const path = attrs.path;\n const workbench = attrs.workbench;\n const node = path.node;\n\n const close = (e) => {\n workbench.executeCommand(\"close-panel\", {}, path);\n }\n const goBack = (e) => {\n let node = path.pop();\n // if there was a duplicate id in the path,\n // pop again\n if (node === path.node) {\n path.pop();\n }\n }\n const maximize = (e) => {\n // todo: should be a command\n workbench.panels = [path];\n workbench.context.path = path;\n }\n function calcHeight(value = \"\") {\n let numberOfLineBreaks = (value.match(/\\n/g) || []).length;\n // min-height + lines x line-height + padding + border\n let newHeight = 20 + numberOfLineBreaks * 20;\n return newHeight;\n }\n let viewClass = \"\";\n if (node.getAttr(\"view\")) {\n viewClass = `${node.getAttr(\"view\")}-panel`\n }\n return
    \n
    \n {(path.length > 1) ?\n
    \n \n \n \n
    \n : null}\n\n
    \n {(node.parent && node.parent.id !== \"@root\") ? workbench.open(node.parent)}>{node.parent.name} :  }\n
    \n\n {(workbench.panels.length > 1) ?\n
    \n \n \n
    \n : null}\n
    \n\n
    \n
    workbench.showMenu(e, { node, path })} data-menu=\"node\">\n \n
    \n \n
    \n
    \n }\n};\n", "import { bindingSymbols } from \"../action/keybinds.ts\";\n\nexport const KeyboardReference = {\n view({ attrs }) {\n const workbench = attrs.workbench;\n const shortcuts = {\n \"\": [\n \"pick-command\",\n ],\n \"Edit\": [\n \"cut\",\n \"copy\",\n \"copy-reference\",\n \"paste\",\n \"mark-done\",\n \"insert\",\n \"delete\",\n ],\n \"Navigate\": [\n \"expand\",\n \"collapse\",\n \"indent\",\n \"outdent\",\n \"move-up\",\n \"move-down\",\n \"prev\",\n \"next\",\n ],\n };\n\n const getBindingSymbols = (cmd) => {\n const binding = workbench.keybindings.getBinding(cmd.id);\n return binding ? bindingSymbols(binding.key).join(\" \").toUpperCase() : \"\";\n };\n\n return (\n
    \n

    Keyboard Shortcuts

    \n\n {Object.entries(shortcuts).map(([header, ids]) => {\n return (\n
    \n {(header.length !== 0) &&

    {header}

    }\n
    \n {ids.map(id => workbench.commands.commands[id]).map(cmd => (\n
    \n
    {getBindingSymbols(cmd)}
    \n
    {cmd.title}
    \n
    \n ))}\n
    \n
    \n );\n })}\n
    \n )\n }\n}", "import { Picker } from \"./picker.tsx\";\n\nexport const Search: m.Component = {\n\n view({ attrs: { input, workbench } }) {\n \n const onpick = (node) => {\n workbench.closeDialog();\n workbench.open(node);\n }\n const onchange = (state) => {\n if (state.input) {\n state.items = workbench.search(state.input);\n } else {\n state.items = [];\n } \n }\n\n return (\n
    \n \n
    \n \n \n
    \n }\n itemview={(result) =>
    {result.name}
    } />\n
    \n )\n }\n}\n", "import { Drawer as DrawerComponent } from \"./drawer.tsx\";\nimport { Panel as PanelComponent } from \"./panel.tsx\";\nimport { KeyboardReference } from './reference.tsx';\nimport { Search } from \"./search.tsx\";\nimport { Notice } from \"./notices.tsx\";\n\nexport const App: m.Component = {\n view ({attrs: {workbench}, state}) {\n state.open = (state.open === undefined) ? true : state.open;\n const toggle = (e) => {\n if (state.open) {\n state.open = false;\n } else {\n state.open = true;\n }\n }\n return (\n
    \n\n
    \n
    \n
    \n
    \n
    \n {state.open && workbench.workspace.bus.root().children.map(node => )}\n
    \n
    \n \n
    \n
    \n\n
    \n
    \n
    workbench.openToday()} style={{cursor: \"pointer\", marginLeft: \"var(--padding)\", marginRight: \"var(--padding)\", display: \"flex\", alignItems: \"center\"}}>\n \n \n {/* {(new Date()).getDate()} */}\n \n
    Today
    \n
    \n
    workbench.openQuickAdd()} style={{cursor: \"pointer\", marginLeft: \"var(--padding)\", marginRight: \"var(--padding)\", display: \"flex\", alignItems: \"center\"}}>\n \n
    Quick Add
    \n
    \n\n
    \n
    \n
    \n \n {\n if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') return;\n const input = e.target.getBoundingClientRect();\n workbench.showDialog(() => , false, {\n // TODO: make these not so hardcoded offsets\n left: `${input.left-33}px`,\n top: `${input.top-9}px`,\n width: `${input.width+33}px`\n });\n e.preventDefault();\n }} \n style={{\n border: \"0\", \n outline: \"0\", \n background: \"transparent\", \n paddingTop: \"3px\"\n }} />\n
    \n
    \n
    \n \n
    workbench.toggleDrawer()} data-menu=\"keyboard-reference\" data-align=\"right\" style={{cursor: \"pointer\", marginLeft: \"var(--padding)\", marginRight: \"var(--padding)\", marginTop: \"-2px\"}}>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n\n
    workbench.showMenu(e)} data-menu=\"settings\" data-align=\"right\" style={{cursor: \"pointer\", marginLeft: \"var(--padding)\", marginRight: \"var(--padding)\"}}>\n \n
    \n
    \n\n
    \n {workbench.panels.map(path =>
    )}\n \n \n \n
    \n\n
    \n
    \n {\n const sidebarStyle = document.querySelector(\".sidebar\").style;\n if (sidebarStyle.display !== \"flex\") {\n sidebarStyle.display = \"flex\";\n } else {\n sidebarStyle.display = \"none\";\n }\n }} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-sidebar\">\n
    \n
    workbench.openToday()}>\n \n \n \n
    \n
    workbench.openQuickAdd()}>\n \n
    \n
    workbench.showDialog(() => , true, {top: \"25%\", bottom: \"100px\"})}>\n \n
    \n
    workbench.showMenu(e, undefined, {bottom: \"100px\", marginTop: \"auto\"})} data-menu=\"settings\">\n \n
    \n
    \n
    \n\n \n {workbench.popover && \n
    \n {workbench.popover.body()}\n
    \n }\n\n\n {\n if (workbench.dialog.explicitClose === true) {\n e.preventDefault();\n return;\n }\n // resets body\n workbench.dialog.body = () => null;\n }}\n onclick={e => {\n const dialog = e.target.closest(\"dialog\");\n const rect = dialog.getBoundingClientRect();\n const zeroClick = (e.clientX == 0 && e.clientY == 0); // clicking select dropdown gives 0,0\n if ((workbench.dialog.explicitClose !== true) && (\n e.clientX < rect.left ||\n e.clientX > rect.right ||\n e.clientY < rect.top ||\n e.clientY > rect.bottom\n ) && !zeroClick) {\n workbench.closeDialog();\n }\n }}>\n {workbench.dialog.body()}\n \n\n {\n // resets body\n workbench.menu.body = () => null;\n }}\n onclick={e => {\n const dialog = e.target.closest(\"dialog\");\n const rect = dialog.getBoundingClientRect();\n if (e.clientX < rect.left ||\n e.clientX > rect.right ||\n e.clientY < rect.top ||\n e.clientY > rect.bottom\n ) {\n workbench.closeMenu();\n }\n }}>\n {workbench.menu.body()}\n \n
    \n )\n }\n};\n\nconst NavNode: m.Component = {\n view ({attrs: {node, workbench, expanded, level}, state}) {\n state.expanded = (state.expanded === undefined) ? expanded : state.expanded;\n const expandable = (node.childCount > 0 && level < 3);\n const toggle = (e) => {\n if (!expandable) return;\n if (state.expanded) {\n state.expanded = false;\n } else {\n state.expanded = true;\n }\n e.stopPropagation();\n }\n const open = (e) => {\n const mobileNav = document.querySelector(\".mobile-nav\");\n if (mobileNav.offsetHeight) {\n document.querySelector(\".sidebar\").style.display = \"none\";\n }\n workbench.open(node);\n }\n return (\n
    \n
    \n \n {(expandable)\n ?(state.expanded)\n ? \n : \n :null}\n \n \n
    \n {node.name}\n
    \n
    \n {state.expanded && \n
    \n {node.children.filter(n => n.name !== \"\").map(n => )}\n
    \n }\n
    \n )\n }\n};\n", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\n@component\nexport class Checkbox {\n checked: boolean;\n\n constructor() {\n this.checked = false;\n }\n\n beforeEditor() {\n return CheckboxEditor;\n }\n}\n\nconst CheckboxEditor = {\n view({attrs: {node}}) {\n const toggleCheckbox = (e) => {\n const checkbox = node.getComponent(Checkbox);\n checkbox.checked = !checkbox.checked;\n node.changed();\n }\n return \n }\n}", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\n\n@component\nexport class TextField {\n constructor() {\n \n }\n\n handleIcon(): any {\n return \n }\n}", "import { component } from \"../model/components.ts\";\nimport { Node } from \"../model/mod.ts\";\nimport { Workbench, Context } from \"../workbench/mod.ts\";\n\n@component\nexport class Clock {\n startedAt?: Date;\n log: Date[][];\n showLog: boolean;\n\n component?: Node;\n object?: Node;\n\n constructor() {\n this.log = [];\n this.showLog = false;\n }\n\n onAttach(node: Node) {\n this.component = node;\n this.object = node.parent;\n }\n\n\n fromJSON(obj: any) {\n if (obj.startedAt) {\n this.startedAt = new Date(obj.startedAt);\n }\n this.log = (obj.log||[]).map(entry => [new Date(entry[0]), new Date(entry[1])]);\n this.showLog = obj.showLog;\n }\n\n toJSON(key: string): any {\n return {\n startedAt: this.startedAt, \n log: this.log,\n showLog: this.showLog\n };\n }\n\n localTotal(): number {\n return this.log.map(this.entryDuration).reduce((acc, val) => acc+val, 0);\n }\n\n grandTotal(): number {\n let total = this.localTotal();\n if (this.object) {\n this.object.children.forEach(child => {\n if (child.hasComponent(Clock)) {\n total += child.getComponent(Clock).grandTotal();\n }\n });\n }\n return total;\n }\n\n start() {\n if (this.startedAt) return;\n this.startedAt = new Date();\n }\n\n stop() {\n if (!this.startedAt) return;\n let now = new Date();\n let diff = now.getTime() - this.startedAt.getTime();\n if (diff/1000 >= 60) {\n // only log if more than a minute\n this.log.push([this.startedAt, now]);\n }\n this.startedAt = undefined;\n }\n\n formatEntry(entry: Date[]): string {\n if (entry.length !== 2) return \"\";\n return `${this.formatDate(entry[0])} - ${new Intl.DateTimeFormat(\"en\", {\n timeStyle: \"short\",\n }).format(entry[1])}`;\n }\n\n // duration in seconds\n entryDuration(entry: Date[]): number {\n const a = entry[0];\n const b = entry[1] || new Date();\n return (b.getTime() - a.getTime()) / 1000;\n }\n\n formatDate(d?: Date): string {\n if (!d) {\n return \"\";\n }\n return new Intl.DateTimeFormat(\"en\", {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(d);\n }\n\n formatDuration(seconds: number): string {\n let dur = seconds / 60;\n let min = Math.floor(dur % 60);\n dur = dur / 60;\n let hrs = Math.floor(dur % 60);\n return `${hrs}:${min.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false})}`;\n }\n\n afterEditor() {\n return ClockBadge;\n }\n\n belowEditor() {\n return ClockLog;\n }\n\n static initialize(workbench: Workbench) {\n workbench.commands.registerCommand({\n id: \"stop-clock\",\n title: \"Stop clock\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n if (!ctx.node.hasComponent(Clock)) {\n const clock = new Clock();\n ctx.node.addComponent(clock);\n }\n ctx.node.getComponent(Clock).stop();\n ctx.node.changed();\n }\n });\n workbench.keybindings.registerBinding({command: \"stop-clock\", key: \"meta+o\" });\n workbench.commands.registerCommand({\n id: \"start-clock\",\n title: \"Start clock\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n if (!ctx.node.hasComponent(Clock)) {\n const clock = new Clock();\n ctx.node.addComponent(clock);\n }\n ctx.node.getComponent(Clock).start();\n ctx.node.changed();\n }\n });\n workbench.keybindings.registerBinding({command: \"start-clock\", key: \"meta+i\" });\n workbench.commands.registerCommand({\n id: \"remove-clock\",\n title: \"Remove clock\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n if (ctx.node.hasComponent(Clock)) return true;\n return false;\n },\n action: (ctx: Context) => {\n ctx.node.removeComponent(Clock);\n }\n });\n }\n}\n\nconst ClockBadge = {\n view({attrs: {node}}) {\n const clock = node.getComponent(Clock);\n const toggleLog = () => {\n clock.showLog = !clock.showLog;\n node.changed();\n }\n if (!clock.showLog && clock.startedAt) {\n return (\n
    \n \n
    {clock.formatDuration(clock.entryDuration([clock.startedAt]))}
    \n
    \n )\n }\n return (\n
    \n \n
    {clock.formatDuration(clock.grandTotal())}
    \n
    \n )\n }\n}\n\nconst ClockLog = {\n view({attrs: {node}}) {\n const clock = node.getComponent(Clock);\n if (!clock.showLog) return;\n return (\n
    \n
    \n
    \n {clock.startedAt &&\n
    \n
    {clock.formatDate(clock.startedAt)} - ...
    \n
    \n \n
    {clock.formatDuration(clock.entryDuration([clock.startedAt]))}
    \n
    \n
    \n }\n {clock.log.toReversed().map(entry => (\n
    \n
    {clock.formatEntry(entry)}
    \n
    \n \n
    {clock.formatDuration(clock.entryDuration(entry))}
    \n
    \n
    \n ))}\n
    \n
    \n )\n }\n}\n", "import {RawNode} from \"../model/mod.ts\";\nimport { SearchIndex, FileStore, ChangeNotifier } from \"./mod.ts\";\n\nexport class BrowserBackend {\n auth: null;\n index: SearchIndex;\n files: FileStore;\n changes?: ChangeNotifier;\n\n constructor() {\n this.auth = null;\n this.files = new LocalStorageFileStore();\n if (window.MiniSearch) {\n this.index = new SearchIndex_MiniSearch();\n } else {\n this.index = new SearchIndex_Dumb();\n }\n this.changes = {\n registerNotifier(cb: (nodeIDs: string[]) => void) {\n window.reloadNodes = cb;\n }\n };\n }\n}\n\nexport class SearchIndex_MiniSearch {\n indexer: any; // MiniSearch\n\n constructor() {\n this.indexer = new MiniSearch({\n idField: \"ID\",\n fields: ['ID', 'Name', 'Value', 'Value.markdown'], // fields to index for full-text search\n storeFields: ['ID'], // fields to return with search results\n extractField: (document, fieldName) => {\n return fieldName.split('.').reduce((doc, key) => doc && doc[key], document);\n }\n });\n }\n\n index(node: RawNode) {\n if (this.indexer.has(node.ID)) {\n this.indexer.replace(node); \n } else {\n this.indexer.add(node);\n }\n }\n\n remove(id: string) {\n try {\n this.indexer.discard(id);\n } catch {}\n }\n\n search(query: string): string[] {\n const suggested = this.indexer.autoSuggest(query);\n if (suggested.length === 0) return [];\n return this.indexer.search(suggested[0].suggestion, {\n prefix: true,\n combineWith: 'AND',\n }).map(doc => doc.ID);\n }\n}\n\n\nexport class SearchIndex_Dumb {\n nodes: Record;\n\n constructor() {\n this.nodes = {};\n }\n\n index(node: RawNode) {\n this.nodes[node.ID] = node.Name;\n }\n\n remove(id: string) {\n delete this.nodes[id];\n }\n\n search(query: string): string[] {\n const results: string[] = [];\n for (const id in this.nodes) {\n if (this.nodes[id].includes(query)) {\n results.push(id);\n }\n }\n return results;\n }\n}\n\n\n\nexport class LocalStorageFileStore {\n async readFile(path: string): Promise {\n return localStorage.getItem(`treehouse:${path}`);\n }\n\n async writeFile(path: string, contents: string) {\n localStorage.setItem(`treehouse:${path}`, contents);\n }\n}", "\nimport { Authenticator, SearchIndex, FileStore, ChangeNotifier } from \"./mod.ts\";\nimport { BrowserBackend } from \"./browser.ts\";\nimport { encode, decode } from 'https://cdn.jsdelivr.net/npm/js-base64@3.7.5/base64.mjs';\n\nexport interface Options {\n domain: string; // domain used with username subdomain to produce repo name\n checkDomain: boolean; // redirect to user domain if it is not current location\n authFallbackURL?: string; // URL to redirect to if auth fails\n privateRepo?: boolean; // if the user workspace repo should be created private\n}\n\nexport class GitHubBackend {\n auth: Authenticator;\n\n index: SearchIndex;\n files: FileStore;\n changes?: ChangeNotifier;\n\n loginURL: string;\n clientFactory: any; // Octokit class\n client: any; // Octokit instance\n user: User|null;\n shas: Record; // path => sha\n \n opts: Options;\n\n constructor(loginURL: string, octokit: any, opts?: Options) {\n this.loginURL = loginURL;\n this.clientFactory = octokit;\n this.auth = this;\n this.shas = {};\n\n this.opts = Object.assign({\n domain: \"treehouse.sh\",\n checkDomain: false,\n privateRepo: false\n }, opts || {});\n\n // fallbacks\n const localbackend = new BrowserBackend();\n this.index = localbackend.index;\n this.files = localbackend.files;\n\n \n }\n\n get repoName(): string {\n return `${this.user?.userID().toLowerCase()}.${this.opts.domain}`;\n }\n\n async initialize() {\n // delegate authorize callback to loginURL\n const code = new URL(location.href).searchParams.get(\"code\");\n if (code) {\n try {\n // remove ?code=... from URL\n const querystring = location.search.replace(/\\bcode=\\w+/, \"\").replace(/\\?$/, \"\");\n history.pushState({}, \"\", `${location.pathname}${querystring}`);\n \n const response = await fetch(this.loginURL, {\n method: \"POST\",\n mode: \"cors\",\n headers: {\"content-type\": \"application/json\"},\n body: JSON.stringify({ code })\n });\n \n const result = await response.json();\n if (result.error) {\n throw result.error;\n }\n \n localStorage.setItem(\"treehouse:gh-token\", result.token);\n \n } catch (e: Error) {\n this.reset();\n console.error(e);\n return;\n }\n }\n\n // capture access token if provided directly\n const token = new URL(location.href).searchParams.get(\"access_token\");\n if (token) {\n try {\n // remove ?access_token=... from URL\n const querystring = location.search.replace(/\\baccess_token=\\w+/, \"\").replace(/\\?$/, \"\");\n history.pushState({}, \"\", `${location.pathname}${querystring}`);\n \n localStorage.setItem(\"treehouse:gh-token\", token);\n } catch (e: Error) {\n this.reset();\n console.error(e);\n return;\n }\n }\n\n try {\n await this.authenticate();\n if (!this.user) {\n throw \"authentication failed\";\n }\n } catch (e: Error) {\n console.error(e);\n if (this.opts.authFallbackURL) {\n location.href = this.opts.authFallbackURL;\n }\n return;\n }\n \n // check domain if set to\n if (this.opts.checkDomain && this.repoName !== location.hostname.toLowerCase()) {\n location.hostname = this.repoName;\n return;\n }\n\n // check if repo exists\n try {\n await this.client.rest.repos.get({\n owner: this.user.userID(), \n repo: this.repoName\n });\n } catch (e: Error) {\n if (e.message !== \"Not Found\") {\n throw e;\n }\n // create if not\n console.log(\"Creating repository...\");\n const resp = await this.client.rest.repos.createForAuthenticatedUser({name: this.repoName, private: this.opts.privateRepo});\n if (resp.status !== 201) {\n console.error(resp);\n return;\n }\n }\n\n // check for workspace.json now\n try {\n await this.client.rest.repos.getContent({\n owner: this.user.userID(), \n repo: this.repoName,\n path: \"workspace.json\"\n });\n } catch (e: Error) {\n if (e.name !== \"HttpError\") {\n throw e;\n }\n // create empty if not\n console.log(\"Creating workspace.json...\");\n const resp = await this.client.rest.repos.createOrUpdateFileContents({\n owner: this.user.userID(), \n repo: this.repoName,\n path: \"workspace.json\", \n message: \"initial commit\", \n content: btoa(JSON.stringify([]))\n });\n if (resp.status !== 201) {\n console.error(resp);\n return;\n }\n }\n \n // satisfy filestore interface with methods on this\n this.files = this;\n \n // create and regularly check a session lockfile\n const sessID = uniqueID();\n await this.readFile(\"treehouse.lock\");\n await this.writeFile(\"treehouse.lock\", sessID);\n const lockCheck = setInterval(async () => {\n const lockFile = await this.readFile(\"treehouse.lock\");\n if (lockFile !== sessID) {\n clearInterval(lockCheck);\n document.dispatchEvent(new CustomEvent(\"BackendError\"));\n console.warn(\"lock stolen!\");\n }\n }, 5000);\n }\n\n async loadExtensions() {\n try {\n const dirCheck = await this.client.rest.repos.getContent({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: \"\",\n random: Math.random().toString(36).substring(2)\n });\n if (dirCheck.data.find(o => o.type === \"dir\" && o.name === \"ext\")) {\n const dirList = await this.client.rest.repos.getContent({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: \"ext\",\n random: Math.random().toString(36).substring(2)\n });\n for (const file of dirList.data) {\n if (file.name.endsWith(\".css\")) {\n // Load CSS \n const resp = await this.client.rest.repos.getContent({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: file.path,\n random: Math.random().toString(36).substring(2)\n });\n const css = document.createElement(\"link\");\n css.setAttribute(\"href\", `data:text/css;charset=utf-8;base64,${resp.data.content}`);\n css.setAttribute(\"rel\", \"stylesheet\");\n css.setAttribute(\"type\", \"text/css\");\n document.head.appendChild(css);\n } else if (file.name.endsWith(\".js\")) {\n // Load JavaScript\n const resp = await this.client.rest.repos.getContent({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: file.path,\n random: Math.random().toString(36).substring(2)\n });\n const js = document.createElement(\"script\");\n js.setAttribute(\"type\", \"module\");\n js.setAttribute(\"src\", `data:text/javascript;charset=utf-8;base64,${resp.data.content}`);\n document.head.appendChild(js);\n }\n }\n }\n \n } catch (e: Error) {}\n \n }\n \n async authenticate() {\n const token = localStorage.getItem(\"treehouse:gh-token\");\n if (!token) {\n return;\n }\n\n this.client = new this.clientFactory({auth: token});\n const resp = await this.client.rest.users.getAuthenticated();\n if (!resp || resp.error) {\n return;\n }\n this.user = new User(resp.data);\n\n if(m)m.redraw();\n }\n\n currentUser(): User|null {\n return this.user;\n }\n\n login() {\n location.assign(this.loginURL);\n }\n\n reset() {\n localStorage.removeItem(\"treehouse:gh-token\");\n this.user = null;\n\n if(m)m.redraw();\n }\n \n logout() {\n this.reset();\n location.reload();\n }\n\n\n async readFile(path: string): Promise {\n try {\n const resp = await this.client.rest.repos.getContent({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: path,\n random: Math.random().toString(36).substring(2)\n });\n this.shas[path] = resp.data.sha;\n return decode(resp.data.content);\n } catch (e: Error) {\n if (e.name !== \"HttpError\") {\n console.error(e);\n }\n return null;\n }\n }\n\n async writeFile(path: string, contents: string) {\n const resp = await this.client.rest.repos.createOrUpdateFileContents({\n owner: this.user?.userID(), \n repo: this.repoName, \n path: path, \n message: \"autosave\", \n content: encode(contents), \n sha: this.shas[path]\n });\n this.shas[path] = resp.data.content.sha;\n }\n}\n\nexport class User {\n user: any; // github user object\n\n constructor(user: any) {\n this.user = user;\n }\n\n userID(): string {\n return this.user.login;\n }\n\n displayName(): string {\n return this.user.name;\n }\n\n avatarURL(): string {\n return this.user.avatar_url;\n }\n}\n\nfunction uniqueID() {\n const dateString = Date.now().toString(36);\n const randomness = Math.random().toString(36).substring(2);\n return dateString + randomness;\n};", "/**\n * A configurable, embeddable frontend for a graph/outline based note-taking tool.\n * \n * Treehouse can be embedded on a page and given a backend for a fully functional\n * SPA. The backend adapter provides hooks to integrate with various backends.\n * \n * Typical usage involves including resource dependencies on the page then running:\n * \n * ```ts\n * import {setup, BrowserBackend} from \"https://treehouse.sh/lib/treehouse.min.js\";\n * setup(document, document.body, new BrowserBackend());\n * ```\n * \n * In this case using the built-in BrowserBackend to store state in localStorage.\n * For more information see the [Quickstart Guide](https://treehouse.sh/docs/quickstart/).\n * \n * @module\n */\nimport { Path, Workbench } from \"./workbench/mod.ts\";\nimport { App } from \"./ui/app.tsx\";\nimport { Backend } from \"./backend/mod.ts\";\nimport { CodeBlock } from \"./com/codeblock.tsx\";\nimport { InlineFrame } from \"./com/iframe.tsx\";\nimport { SmartNode } from \"./com/smartnode.tsx\";\nimport { Checkbox } from \"./com/checkbox.tsx\";\nimport { TextField } from \"./com/textfield.tsx\";\nimport { Clock } from \"./com/clock.tsx\";\nimport { Tag } from \"./com/tag.tsx\";\nimport { Template } from \"./com/template.tsx\";\nimport { Document } from \"./com/document.tsx\";\nimport { Description } from \"./com/description.tsx\";\nimport { objectManaged } from \"./model/hooks.ts\";\n\nexport { BrowserBackend, SearchIndex_MiniSearch } from \"./backend/browser.ts\";\nexport { GitHubBackend } from \"./backend/github.ts\";\n\n\n/**\n * setup initializes and mounts a workbench UI with a given backend adapter to a document.\n * More specifically, first it initializes the given backend, then creates and initializes\n * a Workbench instance with that backend, then it mounts the App component to the given\n * target element. It will also add some event listeners to the document and currently\n * this is where it registers all the built-in commands and their keybindings, as well\n * as menus. \n */\nexport async function setup(document: Document, target: HTMLElement, backend: Backend) {\n if (backend.initialize) {\n await backend.initialize();\n }\n\n const workbench = new Workbench(backend);\n window.workbench = workbench;\n\n await workbench.initialize();\n\n // TODO: better way to initialize components? \n [\n Clock,\n TextField,\n Document,\n Checkbox,\n Tag,\n Template,\n SmartNode,\n Description,\n InlineFrame,\n CodeBlock,\n ].forEach(com => {\n if (com.initialize) {\n com.initialize(workbench);\n }\n });\n\n\n // pretty specific to github backend right now\n document.addEventListener(\"BackendError\", () => {\n workbench.showNotice(\"lockstolen\", () => {\n location.reload();\n });\n });\n\n workbench.commands.registerCommand({\n id: \"cut\",\n title: \"Cut\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n \n // no text is selected\n const input = workbench.getInput(ctx.path);\n if (input && input.selectionStart === input.selectionEnd) {\n return true;\n }\n\n // builtin copy is being performed,\n // clear clipboard so it doesn't override on paste\n workbench.clipboard = undefined;\n\n return false;\n },\n action: (ctx: Context) => {\n workbench.clipboard = {op: \"cut\", node: ctx.node};\n }\n });\n workbench.keybindings.registerBinding({ command: \"cut\", key: \"meta+x\" });\n\n workbench.commands.registerCommand({\n id: \"copy\",\n title: \"Copy\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n\n // no text is selected\n const input = workbench.getInput(ctx.path);\n if (input && input.selectionStart === input.selectionEnd) {\n return true;\n }\n\n // builtin copy is being performed,\n // clear clipboard so it doesn't override on paste\n workbench.clipboard = undefined;\n\n return false;\n },\n action: (ctx: Context) => {\n workbench.clipboard = {op: \"copy\", node: ctx.node};\n }\n });\n workbench.keybindings.registerBinding({ command: \"copy\", key: \"meta+c\" });\n\n workbench.commands.registerCommand({\n id: \"copy-reference\",\n title: \"Copy as Reference\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n \n // no text is selected\n const input = workbench.getInput(ctx.path);\n if (input && input.selectionStart === input.selectionEnd) {\n return true;\n }\n\n // builtin copy is being performed,\n // clear clipboard so it doesn't override on paste\n workbench.clipboard = undefined;\n\n return false;\n },\n action: (ctx: Context) => {\n workbench.clipboard = {op: \"copyref\", node: ctx.node};\n }\n });\n workbench.keybindings.registerBinding({ command: \"copy-reference\", key: \"shift+ctrl+c\" });\n\n workbench.commands.registerCommand({\n id: \"paste\",\n title: \"Paste\",\n when: (ctx: Context) => {\n if (workbench.clipboard) {\n return true;\n }\n return false;\n },\n action: (ctx: Context) => {\n if (!ctx.node) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return;\n switch (workbench.clipboard.op) {\n case \"copy\":\n workbench.clipboard.node = workbench.clipboard.node.duplicate();\n break;\n case \"copyref\":\n const ref = workbench.workspace.new(\"\");\n ref.refTo = workbench.clipboard.node;\n workbench.clipboard.node = ref;\n break;\n }\n if (workbench.clipboard.node.raw.Rel === \"Fields\") {\n workbench.clipboard.node.raw.Parent = ctx.node.parent.id;\n ctx.node.parent.addLinked(\"Fields\", workbench.clipboard.node);\n } else {\n workbench.clipboard.node.parent = ctx.node.parent;\n workbench.clipboard.node.siblingIndex = ctx.node.siblingIndex; \n }\n m.redraw.sync();\n const p = ctx.path.clone();\n p.pop();\n workbench.focus(p.append(workbench.clipboard.node));\n if (workbench.clipboard.op === \"cut\") {\n workbench.clipboard = undefined;\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"paste\", key: \"meta+v\" });\n\n\n\n workbench.commands.registerCommand({\n id: \"view-list\",\n title: \"View as List\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n ctx.node.setAttr(\"view\", \"list\");\n }\n });\n\n workbench.commands.registerCommand({\n id: \"view-table\",\n title: \"View as Table\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n ctx.node.setAttr(\"view\", \"table\");\n ctx.node.children.forEach(child => {\n workbench.workspace.setExpanded(ctx.path.head, child, false);\n });\n }\n });\n\n workbench.commands.registerCommand({\n id: \"view-tabs\",\n title: \"View as Tabs\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n ctx.node.setAttr(\"view\", \"tabs\");\n }\n });\n\n workbench.commands.registerCommand({\n id: \"view-cards\",\n title: \"View as Cards\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n ctx.node.setAttr(\"view\", \"cards\");\n }\n });\n\n\n workbench.commands.registerCommand({\n id: \"add-checkbox\",\n title: \"Add checkbox\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.hasComponent(Checkbox)) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n const checkbox = new Checkbox();\n ctx.node.addComponent(checkbox);\n }\n });\n\n workbench.commands.registerCommand({\n id: \"remove-checkbox\",\n title: \"Remove checkbox\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n if (ctx.node.hasComponent(Checkbox)) return true;\n return false;\n },\n action: (ctx: Context) => {\n ctx.node.removeComponent(Checkbox);\n }\n });\n\n workbench.commands.registerCommand({\n id: \"create-field\",\n title: \"Create Field\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n if (ctx.node.childCount > 0) return;\n if (ctx.node.componentCount > 0) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return;\n const path = ctx.path.clone();\n path.pop(); // drop node\n const field = workbench.workspace.new(ctx.node.name, \"\");\n field.raw.Parent = ctx.node.parent.id;\n const text = new TextField();\n field.addComponent(text);\n ctx.node.parent.addLinked(\"Fields\", field);\n path.push(field);\n ctx.node.destroy();\n m.redraw.sync();\n workbench.focus(path);\n }\n });\n\n workbench.commands.registerCommand({\n id: \"mark-done\",\n title: \"Mark Done\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n if (!ctx.node) return;\n if (ctx.node.hasComponent(Checkbox)) {\n const checkbox = ctx.node.getComponent(Checkbox);\n if (!checkbox.checked) {\n checkbox.checked = true;\n ctx.node.changed();\n } else {\n ctx.node.removeComponent(Checkbox);\n }\n } else {\n const checkbox = new Checkbox();\n ctx.node.addComponent(checkbox);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"mark-done\", key: \"meta+enter\" });\n\n\n\n workbench.commands.registerCommand({\n id: \"expand\",\n title: \"Expand\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n workbench.workspace.setExpanded(ctx.path.head, ctx.node, true);\n m.redraw();\n }\n });\n workbench.keybindings.registerBinding({ command: \"expand\", key: \"meta+arrowdown\" });\n workbench.commands.registerCommand({\n id: \"collapse\",\n title: \"Collapse\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n workbench.workspace.setExpanded(ctx.path.head, ctx.node, false);\n m.redraw();\n }\n });\n workbench.keybindings.registerBinding({ command: \"collapse\", key: \"meta+arrowup\" });\n workbench.commands.registerCommand({\n id: \"indent\",\n title: \"Indent\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n const node = ctx.node; // redraw seems to unset ctx.node\n const path = ctx.path.clone();\n let prev = node.prevSibling;\n while (prev && objectManaged(prev)) {\n prev = prev.prevSibling;\n if (!prev) return;\n }\n if (prev !== null) {\n path.pop(); // drop node\n path.push(prev);\n node.parent = prev;\n path.push(node);\n workbench.workspace.setExpanded(ctx.path.head, prev, true);\n m.redraw.sync();\n workbench.focus(path);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"indent\", key: \"tab\" });\n workbench.commands.registerCommand({\n id: \"outdent\",\n title: \"Outdent\",\n when: (ctx: Context) => {\n if (!ctx.node) return false;\n if (ctx.node.raw.Rel === \"Fields\") return false;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return false;\n if (ctx.node.parent && ctx.node.parent.hasComponent(Document)) return false;\n return true;\n },\n action: (ctx: Context) => {\n const node = ctx.node; // redraw seems to unset ctx.node\n const parent = ctx.path.previous;\n const path = ctx.path.clone();\n if (parent !== null && parent.id !== \"@root\" && parent.id !== workbench.workspace.lastOpenedID) {\n path.pop(); // drop node\n path.pop(); // drop parent\n node.parent = parent.parent;\n path.push(node);\n node.siblingIndex = parent.siblingIndex + 1;\n if (parent.childCount === 0 && parent.getLinked(\"Fields\").length === 0) {\n workbench.workspace.setExpanded(ctx.path.head, parent, false);\n }\n m.redraw.sync();\n workbench.focus(path);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"outdent\", key: \"shift+tab\" });\n workbench.commands.registerCommand({\n id: \"move-up\",\n title: \"Move Up\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n const node = ctx.node; // redraw seems to unset ctx.node\n const parent = node.parent;\n if (parent !== null && parent.id !== \"@root\") {\n const children = parent.childCount;\n if (node.siblingIndex === 0) {\n if (!parent.prevSibling) {\n return;\n }\n const p = ctx.path.clone();\n p.pop(); // drop node\n p.pop(); // drop parent\n let parentSib = parent.prevSibling;\n while (parentSib && objectManaged(parentSib)) {\n parentSib = parentSib.prevSibling;\n if (!parentSib) return;\n }\n p.push(parentSib);\n p.push(node);\n node.parent = parentSib;\n node.siblingIndex = parentSib.childCount - 1;\n workbench.workspace.setExpanded(ctx.path.head, parentSib, true);\n m.redraw.sync();\n workbench.focus(p);\n } else {\n if (children === 1) {\n return;\n }\n node.siblingIndex = node.siblingIndex - 1;\n m.redraw.sync();\n }\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"move-up\", key: \"shift+meta+arrowup\" });\n workbench.commands.registerCommand({\n id: \"move-down\",\n title: \"Move Down\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n const node = ctx.node; // redraw seems to unset ctx.node\n const parent = node.parent;\n if (parent !== null && parent.id !== \"@root\") {\n const children = parent.childCount;\n // if last child\n if (node.siblingIndex === children - 1) {\n if (!parent.nextSibling) {\n return;\n }\n const p = ctx.path.clone();\n p.pop(); // drop node\n p.pop(); // drop parent\n let parentSib = parent.nextSibling;\n while (parentSib && objectManaged(parentSib)) {\n parentSib = parentSib.nextSibling;\n if (!parentSib) return;\n }\n p.push(parentSib);\n p.push(node);\n node.parent = parentSib;\n node.siblingIndex = 0;\n workbench.workspace.setExpanded(ctx.path.head, parentSib, true);\n m.redraw.sync();\n workbench.focus(p);\n } else {\n if (children === 1) {\n return;\n }\n node.siblingIndex = node.siblingIndex + 1;\n m.redraw.sync();\n }\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"move-down\", key: \"shift+meta+arrowdown\" });\n workbench.commands.registerCommand({\n id: \"insert-child\",\n title: \"Insert Child\",\n action: (ctx: Context, name: string = \"\", siblingIndex?: number) => {\n if (!ctx.node) return;\n if (objectManaged(ctx.node)) return;\n const node = workbench.workspace.new(name);\n node.parent = ctx.node;\n if (siblingIndex !== undefined) {\n node.siblingIndex = siblingIndex;\n }\n workbench.workspace.setExpanded(ctx.path.head, ctx.node, true);\n m.redraw.sync();\n workbench.focus(ctx.path.append(node), name.length);\n }\n });\n workbench.commands.registerCommand({\n id: \"insert-before\",\n title: \"Insert Before\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return;\n const node = workbench.workspace.new(\"\");\n node.parent = ctx.node.parent;\n node.siblingIndex = ctx.node.siblingIndex;\n m.redraw.sync();\n const p = ctx.path.clone();\n p.pop();\n workbench.focus(p.append(node));\n }\n });\n workbench.commands.registerCommand({\n id: \"insert\",\n title: \"Insert Node\",\n action: (ctx: Context, name: string = \"\") => {\n if (!ctx.node) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return;\n const node = workbench.workspace.new(name);\n node.parent = ctx.node.parent;\n node.siblingIndex = ctx.node.siblingIndex + 1;\n m.redraw.sync();\n const p = ctx.path.clone();\n p.pop();\n workbench.focus(p.append(node));\n }\n });\n workbench.keybindings.registerBinding({ command: \"insert\", key: \"shift+enter\" });\n workbench.commands.registerCommand({\n id: \"create-reference\",\n title: \"Create Reference\",\n action: (ctx: Context) => {\n // TODO: prevent creating reference to reference\n if (!ctx.node) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return;\n const node = workbench.workspace.new(\"\");\n node.parent = ctx.node.parent;\n node.siblingIndex = ctx.node.siblingIndex + 1;\n node.refTo = ctx.node;\n m.redraw.sync();\n const p = ctx.path.clone();\n p.pop();\n workbench.focus(p.append(node));\n }\n });\n workbench.commands.registerCommand({\n id: \"delete\",\n title: \"Delete Node\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n if (ctx.node.id.startsWith(\"@\")) return;\n if (ctx.path.previous && objectManaged(ctx.path.previous)) return; // should probably provide feedback or disable delete\n const above = workbench.workspace.findAbove(ctx.path);\n ctx.node.destroy();\n m.redraw.sync();\n if (above) {\n let pos = 0;\n if (ctx.event && ctx.event.key === \"Backspace\") {\n if (above.node.value) {\n pos = above.node.value.length;\n } else {\n pos = above.node.name.length;\n }\n }\n if (above.node.childCount === 0) {\n // TODO: use subCount\n workbench.workspace.setExpanded(ctx.path.head, above.node, false);\n }\n workbench.focus(above, pos);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"delete\", key: \"shift+meta+backspace\" });\n workbench.commands.registerCommand({\n id: \"prev\",\n title: \"Previous Node\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n const above = workbench.workspace.findAbove(ctx.path);\n if (above) {\n workbench.focus(above);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"prev\", key: \"arrowup\" });\n workbench.commands.registerCommand({\n id: \"next\",\n title: \"Next Node\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n const below = workbench.workspace.findBelow(ctx.path);\n if (below) {\n workbench.focus(below);\n }\n }\n });\n workbench.keybindings.registerBinding({ command: \"next\", key: \"arrowdown\" });\n workbench.commands.registerCommand({\n id: \"pick-command\",\n title: \"Command Palette\",\n hidden: true,\n when: (ctx: Context) => {\n if (workbench.isDialogOpen()) return false;\n return true;\n },\n action: (ctx: Context) => {\n let node = ctx.node;\n let path = ctx.path;\n let posBelow = false;\n if (!node) {\n // no node is selected, use panel node\n node = ctx.path.head;\n path = new Path(ctx.path.head, ctx.path.name);\n posBelow = true;\n }\n const trigger = workbench.getInput(path);\n const rect = trigger.getBoundingClientRect();\n let x = document.body.scrollLeft + rect.x + (trigger.selectionStart * 10) + 20;\n let y = document.body.scrollTop + rect.y - 8;\n if (trigger.coordsAtCursor) {\n x = trigger.coordsAtCursor.left-17;\n y = trigger.coordsAtCursor.top-16;\n }\n if (posBelow) {\n x = document.body.scrollLeft + rect.x;\n y = document.body.scrollTop + rect.y + rect.height;\n }\n workbench.showPalette(x, y, workbench.newContext({ node }));\n }\n });\n workbench.keybindings.registerBinding({ command: \"pick-command\", key: \"meta+k\" });\n workbench.commands.registerCommand({\n id: \"new-panel\",\n title: \"Open in New Panel\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n workbench.openNewPanel(ctx.node);\n m.redraw();\n }\n });\n workbench.commands.registerCommand({\n id: \"close-panel\",\n title: \"Close Panel\",\n action: (ctx: Context, panel?: Path) => {\n workbench.closePanel(panel || ctx.path);\n workbench.context.path = workbench.mainPanel;\n m.redraw();\n }\n });\n workbench.commands.registerCommand({\n id: \"zoom\",\n title: \"Open\",\n action: (ctx: Context) => {\n workbench.workspace.lastOpenedID = ctx.node.id;\n workbench.workspace.save();\n workbench.context.path = ctx.path.append(ctx.node);\n workbench.panels[0] = workbench.context.path;\n m.redraw();\n }\n });\n workbench.commands.registerCommand({\n id: \"generate-random\",\n hidden: true,\n title: \"Generate Random Children\",\n action: (ctx: Context) => {\n if (!ctx.node) return;\n [...Array(100)].forEach(() => {\n const node = workbench.workspace.new(generateName(8));\n node.parent = ctx.node;\n });\n }\n });\n\n\n\n workbench.menus.registerMenu(\"node\", [\n { command: \"zoom\" },\n { command: \"new-panel\" },\n { command: \"cut\" },\n { command: \"copy\" },\n { command: \"paste\" },\n { command: \"indent\" },\n { command: \"outdent\" },\n { command: \"move-up\" },\n { command: \"move-down\" },\n { command: \"delete\" },\n // {command: \"add-checkbox\"}, \n // {command: \"remove-checkbox\"},\n // {command: \"mark-done\"},\n // {command: \"add-page\"},\n // {command: \"remove-page\"},\n // {command: \"generate-random\"},\n // {command: \"create-reference\"},\n ]);\n\n workbench.menus.registerMenu(\"settings\", [\n { title: () => `${workbench.backend.auth?.currentUser()?.userID()} @ GitHub`, disabled: true, when: () => workbench.authenticated() },\n {\n title: () => \"Login with GitHub\", when: () => !workbench.authenticated(), onclick: () => {\n if (!localStorage.getItem(\"github\")) {\n workbench.showNotice(\"github\", () => {\n workbench.backend.auth.login()\n })\n } else {\n workbench.backend.auth.login()\n }\n }\n },\n {\n title: () => \"Reset Demo\", when: () => !workbench.authenticated(), onclick: () => {\n localStorage.clear();\n location.reload();\n }\n },\n { title: () => \"Settings\", onclick: () => workbench.showSettings() },\n { title: () => \"Documentation\", onclick: () => window.open(\"https://treehouse.sh/docs/user\", \"_blank\") },\n { title: () => \"Submit Issue\", onclick: () => window.open(\"https://github.com/treehousedev/treehouse/issues\", \"_blank\") },\n { title: () => \"Logout\", when: () => workbench.authenticated(), onclick: () => workbench.backend.auth.logout() },\n ]);\n\n document.addEventListener(\"keydown\", (e) => {\n const binding = workbench.keybindings.evaluateEvent(e);\n if (binding && workbench.canExecuteCommand(binding.command, workbench.context)) {\n workbench.executeCommand(binding.command, workbench.context);\n e.stopPropagation();\n e.preventDefault();\n return;\n }\n });\n\n\n m.mount(target, { view: () => m(App, { workbench }) });\n}\n\n\n\nfunction generateName(length = 10) {\n const random = (min: any, max: any) => {\n return Math.round(Math.random() * (max - min) + min)\n };\n const word = () => {\n const words = [\n 'got',\n 'ability',\n 'shop',\n 'recall',\n 'fruit',\n 'easy',\n 'dirty',\n 'giant',\n 'shaking',\n 'ground',\n 'weather',\n 'lesson',\n 'almost',\n 'square',\n 'forward',\n 'bend',\n 'cold',\n 'broken',\n 'distant',\n 'adjective'\n ];\n return words[random(0, words.length - 1)];\n };\n const words = (length) => (\n [...Array(length)]\n .map((_, i) => word())\n .join(' ')\n .trim()\n );\n return words(random(2, length))\n}\n"], + "mappings": "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", + "names": ["isMac", "bindingSymbols", "key", "symbols", "filterKeyForNonMacMeta", "k", "__name", "KeyBindings", "binding", "commandId", "b", "event", "bindings", "modifiers", "checkMod", "hasMod", "modState", "CommandRegistry", "cmd", "id", "rest", "resolve", "ret", "__name", "MenuRegistry", "id", "items", "__name", "isDisabled", "workbench", "item", "cmd", "ctx", "__name", "Menu", "x", "y", "items", "align", "commands", "onclick", "e", "i", "title", "binding", "c", "bindingSymbols", "Picker", "state", "dom", "items", "attrs", "onkeydown", "__name", "e", "mod", "a", "b", "oninput", "item", "idx", "CommandPalette", "workbench", "ctx", "getTitle", "__name", "cmd", "t", "sort", "a", "b", "onpick", "onchange", "state", "cmds", "getBindingSymbols", "binding", "bindingSymbols", "Picker", "onkeydown", "oninput", "hasHook", "node", "hook", "__name", "triggerHook", "args", "objectHas", "obj", "com", "objectCall", "componentsWith", "ret", "objectManaged", "registry", "component", "target", "componentName", "__name", "getComponent", "com", "inflateToComponent", "obj", "o", "duplicate", "src", "dst", "Document", "node", "collapsed", "key", "workbench", "ctx", "doc", "__name", "__decorateClass", "component", "Description", "DescriptionEditor", "workbench", "ctx", "objectManaged", "name", "desc", "__name", "__decorateClass", "component", "node", "oninput", "e", "onblur", "NodeEditor", "workbench", "path", "onkeydown", "oninput", "disallowEmpty", "editValue", "placeholder", "state", "node", "prop", "display", "__name", "objectHas", "objectCall", "onfocus", "getter", "setter", "v", "finished", "id", "editor", "TextAreaEditor", "Document", "CodeMirrorEditor", "desc", "Description", "dom", "onblur", "value", "defaultKeydown", "e", "startEdit", "finishEdit", "edit", "attrs", "textarea", "initialHeight", "span", "height", "textData", "lines", "line", "newNode", "p", "Template", "node", "collapsed", "key", "workbench", "ctx", "tmpl", "ws", "name", "n", "__name", "__decorateClass", "component", "Tag", "name", "TagBadge", "workbench", "ctx", "tag", "tmpl", "Template", "f", "c", "ws", "tags", "nodes", "n", "bench", "path", "node", "inputview", "closer", "trigger", "rect", "x", "y", "Picker", "item", "state", "filtered", "t", "__name", "__decorateClass", "component", "e", "defaultExecutor", "source", "options", "CodeBlock", "language", "CodeEditorWithOutput", "collapsed", "workbench", "ctx", "com", "__name", "__decorateClass", "component", "CodeEditor", "vnode", "dom", "path", "snippet", "editor", "code", "e", "Output", "state", "handleClick", "res", "error", "empty_default", "workbench", "path", "NewNode", "workbench", "path", "__name", "e", "lastchild", "debounce", "func", "timeout", "timer", "args", "__name", "SmartNode", "collapsed", "SmartFilter", "node", "n", "results", "ref", "children", "key", "obj", "workbench", "ctx", "search", "__decorateClass", "component", "expanded", "oninput", "e", "list_default", "workbench", "path", "alwaysShowNew", "node", "showNew", "SmartNode", "n", "OutlineNode", "NewNode", "table_default", "workbench", "path", "state", "node", "n", "f", "getFieldEditor", "__name", "field", "fields", "NodeEditor", "OutlineNode", "tabs_default", "workbench", "path", "state", "node", "n", "handleTabClick", "__name", "id", "selectedNode", "getNodeView", "document_default", "workbench", "path", "alwaysShowNew", "node", "showNew", "n", "OutlineNode", "NewNode", "InlineFrame", "InlineFrameView", "collapsed", "workbench", "ctx", "frame", "__name", "__decorateClass", "component", "path", "iframe", "tryFields", "n", "fields", "field", "value", "__name", "cards_default", "workbench", "path", "node", "linkURL", "dateTime", "userName", "thumbnailURL", "frame", "InlineFrame", "thumbnail", "timeAgo", "date", "now", "seconds", "intervals", "unit", "secondsInUnit", "count", "views", "list_default", "table_default", "tabs_default", "document_default", "cards_default", "getNodeView", "node", "views", "empty_default", "__name", "name", "view", "toTitleCase", "ctx", "str", "txt", "OutlineEditor", "workbench", "path", "alwaysShowNew", "objectHas", "componentsWith", "getNodeView", "OutlineNode", "attrs", "state", "children", "node", "isRef", "handleNode", "isCut", "expanded", "placeholder", "objectCall", "hover", "__name", "e", "unhover", "cancelTagPopover", "oninput", "Tag", "onkeydown", "anyModifiers", "above", "oldName", "CodeBlock", "lang", "open", "toggle", "Document", "subCount", "n", "showHandle", "NodeEditor", "component", "QuickAdd", "workbench", "node", "path", "Path", "OutlineEditor", "Settings", "workbench", "state", "currentTheme", "__name", "e", "LockStolenMessage", "FirstTimeMessage", "workbench", "GitHubMessage", "finished", "Workbench", "backend", "CommandRegistry", "KeyBindings", "MenuRegistry", "Workspace", "n", "com", "css", "node", "QuickAdd", "today", "dayNode", "weekNode", "getWeekOfYear", "todayPath", "todayNode", "p", "Path", "panel", "input", "path", "pos", "id", "el", "ctx", "rest", "event", "style", "trigger", "rect", "align", "items", "cmds", "i", "Menu", "x", "y", "CommandPalette", "notice", "finished", "FirstTimeMessage", "GitHubMessage", "LockStolenMessage", "Settings", "body", "backdrop", "explicitClose", "query", "splitQuery", "textQuery", "term", "fieldQuery", "passFieldQuery", "__name", "fields", "f", "field", "Tag", "resultCache", "date", "d", "dayNum", "yearStart", "SHA1", "msg", "rotate_left", "n", "s", "t4", "__name", "lsb_hex", "val", "str", "i", "vh", "vl", "cvt_hex", "v", "Utf8Encode", "string", "utftext", "c", "blockstart", "j", "W", "H0", "H1", "H2", "H3", "H4", "A", "B", "C", "D", "E", "temp", "msg_len", "word_array", "Path", "head", "name", "node", "p", "SHA1", "n", "__name", "Bus", "n", "cb", "nodes", "getComponent", "inflateToComponent", "node", "triggerHook", "name", "value", "parent", "parts", "i", "child", "id", "uniqueId", "Node", "p", "rel", "root", "path", "byId", "cur", "findChild", "__name", "fn", "opts", "dateString", "randomness", "Node", "bus", "id", "raw", "val", "n", "p", "triggerHook", "rel", "i", "anc", "cur", "path", "children", "com", "hasHook", "node", "name", "obj", "componentName", "coms", "getComponent", "type", "linked", "idx", "oldIdx", "value", "fn", "opts", "child", "nodes", "duplicate", "f", "c", "__name", "Workspace", "fs", "changes", "Bus", "debounce", "path", "contents", "e", "fn", "immediate", "n", "nodeIDs", "doc", "i", "main", "root", "ws", "cal", "home", "name", "value", "head", "expanded", "b", "p", "prev", "fieldCount", "lastSubIfExpanded", "__name", "lastField", "lastChild", "nextSiblingOrParentNextSibling", "next", "parent", "func", "timeout", "timer", "args", "Drawer", "attrs", "children", "open", "Panel", "attrs", "path", "workbench", "node", "close", "__name", "e", "goBack", "maximize", "calcHeight", "value", "viewClass", "NodeEditor", "OutlineEditor", "KeyboardReference", "attrs", "workbench", "shortcuts", "getBindingSymbols", "__name", "cmd", "binding", "bindingSymbols", "header", "ids", "id", "Search", "input", "workbench", "Picker", "__name", "node", "state", "onkeydown", "oninput", "value", "result", "App", "workbench", "state", "toggle", "__name", "e", "node", "NavNode", "input", "Search", "path", "Panel", "Drawer", "KeyboardReference", "sidebarStyle", "rect", "zeroClick", "expanded", "level", "expandable", "open", "n", "Checkbox", "CheckboxEditor", "__name", "__decorateClass", "component", "node", "e", "checkbox", "TextField", "__name", "__decorateClass", "component", "Clock", "node", "obj", "entry", "key", "acc", "val", "total", "child", "now", "a", "d", "seconds", "dur", "min", "ClockBadge", "ClockLog", "workbench", "ctx", "clock", "__name", "__decorateClass", "component", "toggleLog", "BrowserBackend", "LocalStorageFileStore", "SearchIndex_MiniSearch", "SearchIndex_Dumb", "cb", "__name", "document", "fieldName", "doc", "key", "node", "id", "query", "suggested", "results", "path", "contents", "encode", "decode", "GitHubBackend", "loginURL", "octokit", "opts", "localbackend", "BrowserBackend", "code", "querystring", "result", "e", "token", "resp", "sessID", "uniqueID", "lockCheck", "o", "dirList", "file", "css", "js", "User", "path", "decode", "contents", "encode", "__name", "user", "dateString", "randomness", "setup", "document", "target", "backend", "workbench", "Workbench", "Clock", "TextField", "Document", "Checkbox", "Tag", "Template", "SmartNode", "Description", "InlineFrame", "CodeBlock", "com", "ctx", "input", "objectManaged", "ref", "p", "child", "checkbox", "path", "field", "text", "node", "prev", "parent", "children", "parentSib", "name", "siblingIndex", "above", "pos", "below", "posBelow", "Path", "trigger", "rect", "x", "y", "panel", "generateName", "e", "binding", "App", "__name", "length", "random", "min", "max", "word", "words", "_", "i"] +} diff --git a/lib/ui/app.tsx b/lib/ui/app.tsx deleted file mode 100644 index 2118a7a..0000000 --- a/lib/ui/app.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import { Drawer as DrawerComponent } from "./drawer.tsx"; -import { Panel as PanelComponent } from "./panel.tsx"; -import { KeyboardReference } from './reference.tsx'; -import { Search } from "./search.tsx"; -import { Notice } from "./notices.tsx"; - -export const App: m.Component = { - view ({attrs: {workbench}, state}) { - state.open = (state.open === undefined) ? true : state.open; - const toggle = (e) => { - if (state.open) { - state.open = false; - } else { - state.open = true; - } - } - return ( -
    - -
    - ) - } -}; - -const NavNode: m.Component = { - view ({attrs: {node, workbench, expanded, level}, state}) { - state.expanded = (state.expanded === undefined) ? expanded : state.expanded; - const expandable = (node.childCount > 0 && level < 3); - const toggle = (e) => { - if (!expandable) return; - if (state.expanded) { - state.expanded = false; - } else { - state.expanded = true; - } - e.stopPropagation(); - } - const open = (e) => { - const mobileNav = document.querySelector(".mobile-nav"); - if (mobileNav.offsetHeight) { - document.querySelector(".sidebar").style.display = "none"; - } - workbench.open(node); - } - return ( -
    - - {state.expanded && - - } -
    - ) - } -}; diff --git a/lib/ui/drawer.tsx b/lib/ui/drawer.tsx deleted file mode 100644 index b87be9d..0000000 --- a/lib/ui/drawer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const Drawer = { - view({ attrs, children }) { - const open = attrs.open; - return ( -
    - {children} -
    - ) - } -}; diff --git a/lib/ui/menu.tsx b/lib/ui/menu.tsx deleted file mode 100644 index 884e786..0000000 --- a/lib/ui/menu.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { bindingSymbols } from "../action/keybinds.ts"; - -function isDisabled(workbench, item, cmd, ctx) { - if (cmd) { - return item.disabled || !workbench.canExecuteCommand(cmd.id, ctx); - } - return item.disabled; -} - -export const Menu: m.Component = { - view({attrs: {workbench, x, y, items, align, commands, ctx}}) { - const onclick = (item, cmd) => (e) => { - e.stopPropagation(); - if (isDisabled(workbench, item, cmd, ctx)) { - return; - } - workbench.closeMenu(); - if (item.onclick) { - item.onclick(); - } - if (cmd) { - workbench.executeCommand(cmd.id, ctx); - } - }; - return ( - - ) - } -}; - -/*
  • Indent
    shift+A
  • -
  • Open in new panel
    shift+meta+Backspace
  • -
    -
  • Show list view
  • -
  • Move
  • -
  • Delete node
  • -
    */ diff --git a/lib/ui/mod.ts b/lib/ui/mod.ts deleted file mode 100644 index f1b60e4..0000000 --- a/lib/ui/mod.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * UI module contains the React-style view components that make up the UI. - * They implement the Mithril.js component API and use JSX. - * - * @module - */ \ No newline at end of file diff --git a/lib/ui/node/editor.tsx b/lib/ui/node/editor.tsx deleted file mode 100644 index 7ca35dc..0000000 --- a/lib/ui/node/editor.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { objectCall, objectHas } from "../../model/hooks.ts"; -import { Document } from "../../com/document.tsx"; -import { Description } from "../../com/description.tsx"; - -export const NodeEditor: m.Component = { - view ({attrs: {workbench, path, onkeydown, oninput, disallowEmpty, editValue, placeholder}, state}) { - const node = path.node; - let prop = (editValue) ? "value" : "name"; - - const display = () => { - if (prop === "name") { - return objectHas(node, "displayName") ? objectCall(node, "displayName", node) : node.name; - } - return node[prop] || ""; - } - const onfocus = () => { - state.initialValue = node[prop]; - workbench.context.node = node; - workbench.context.path = path; - } - const getter = () => { - return node[prop]; - } - const setter = (v, finished) => { - if (!node.isDestroyed) { - if (disallowEmpty && v.length === 0) { - node[prop] = state.initialValue; - } else { - node[prop] = v; - } - } - if (finished) { - workbench.context.node = null; - } - } - - if (node.raw.Rel === "Fields") { - placeholder = (editValue) ? "Value" : "Field"; - } - - let id = `input-${path.id}-${node.id}`; - if (prop === "value") { - id = id+"-value"; - } - let editor = TextAreaEditor; - if (node.parent && node.parent.hasComponent(Document) && window.Editor) { - editor = CodeMirrorEditor; - } - let desc = undefined; - if (node.hasComponent(Description)) { - desc = node.getComponent(Description); - } - return ( -
    - {m(editor, {id, getter, setter, display, onkeydown, onfocus, oninput, placeholder, workbench, path})} - {(desc) ? m(desc.editor(), {node}) :null} -
    - ) - } -} - - -interface Attrs { - id?: string; - onkeydown?: Function; - onfocus?: Function; - onblur?: Function; - onmount?: Function; - getter: Function; - setter: Function; - display?: Function; -} - -interface State { - editing: boolean; - buffer: string; -} - -export const CodeMirrorEditor: m.Component = { - oncreate({dom,state,attrs: {id, onkeydown, onfocus, onblur, oninput, getter, setter, display, placeholder}}) { - const value = (state.editing) - ? state.buffer - : (display) ? display() : getter(); - - const defaultKeydown = (e) => { - if (e.key === "Enter") { - e.preventDefault(); - e.stopPropagation(); - } - } - const startEdit = (e) => { - if (onfocus) onfocus(e); - state.editing = true; - state.buffer = getter(); - } - const finishEdit = (e) => { - // safari can trigger blur more than once - // for a given element, namely when clicking - // into devtools. this prevents the second - // blur setting node name to undefined/empty. - if (state.editing) { - state.editing = false; - setter(state.buffer, true); - state.buffer = undefined; - } - if (onblur) onblur(e); - } - const edit = (e) => { - state.buffer = e.target.value; - setter(state.buffer, false); - if (oninput) { - oninput(e); - } - } - - state.editor = new window.Editor(dom, value, placeholder); - state.editor.onblur = finishEdit; - state.editor.onfocus = startEdit; - state.editor.oninput = edit; - state.editor.onkeydown = onkeydown||defaultKeydown; - dom.editor = state.editor; - dom.id = id; - }, - onupdate({dom,state,attrs: {getter, display}}) { - state.editor.value = (state.editing) - ? state.buffer - : (display) ? display() : getter(); - }, - view () { - return ( -
    - ) - } -} - -export const TextAreaEditor: m.Component = { - oncreate({dom,attrs}) { - const textarea = dom.querySelector("textarea"); - const initialHeight = textarea.offsetHeight; - const span = dom.querySelector("span"); - this.updateHeight = () => { - span.style.width = `${Math.max(textarea.offsetWidth, 100)}px`; - span.innerHTML = textarea.value.replace("\n", "
    "); - let height = span.offsetHeight; - if (height === 0 && initialHeight > 0) { - height = initialHeight; - } - textarea.style.height = (height > 0) ? `${height}px` : `var(--body-line-height)`; - } - textarea.addEventListener("input", () => this.updateHeight()); - textarea.addEventListener("blur", () => span.innerHTML = ""); - setTimeout(() => this.updateHeight(), 50); - if (attrs.onmount) attrs.onmount(textarea); - }, - onupdate() { - this.updateHeight(); - }, - view ({attrs: {id, onkeydown, onfocus, onblur, oninput, getter, setter, display, placeholder, path, workbench}, state}) { - const value = (state.editing) - ? state.buffer - : (display) ? display() : getter(); - - const defaultKeydown = (e) => { - if (e.key === "Enter") { - e.preventDefault(); - e.stopPropagation(); - } - } - const startEdit = (e) => { - if (onfocus) onfocus(e); - state.editing = true; - state.buffer = getter(); - } - const finishEdit = (e) => { - // safari can trigger blur more than once - // for a given element, namely when clicking - // into devtools. this prevents the second - // blur setting node name to undefined/empty. - if (state.editing) { - state.editing = false; - setter(state.buffer, true); - state.buffer = undefined; - } - if (onblur) onblur(e); - } - const edit = (e) => { - state.buffer = e.target.value; - setter(state.buffer, false); - if (oninput) { - oninput(e); - } - } - const handlePaste = (e) => { - const textData = e.clipboardData.getData('Text'); - if (textData.length > 0) { - e.preventDefault(); - e.stopPropagation(); - - const lines = textData.split('\n').map(line => line.trim()).filter(line => line.length > 0); - state.buffer = lines.shift(); - setter(state.buffer, true); - - let node = path.node; - for (const line of lines) { - const newNode = workbench.workspace.new(line); - newNode.parent = node.parent; - newNode.siblingIndex = node.siblingIndex + 1; - m.redraw.sync(); - const p = path.clone(); - p.pop(); - workbench.focus(p.append(newNode)); - node = newNode; - } - } - } - - return ( -
    - - -
    - ) - } -} \ No newline at end of file diff --git a/lib/ui/node/new.tsx b/lib/ui/node/new.tsx deleted file mode 100644 index 0a9c099..0000000 --- a/lib/ui/node/new.tsx +++ /dev/null @@ -1,32 +0,0 @@ - -export const NewNode = { - view({attrs: {workbench, path}}) { - const keydown = (e) => { - if (e.key === "Tab") { - e.stopPropagation(); - e.preventDefault(); - if (node.childCount > 0) { - const lastchild = path.node.children[path.node.childCount-1]; - workbench.executeCommand("insert-child", {node: lastchild, path}); - } - } else { - workbench.executeCommand("insert-child", {node: path.node, path}, e.target.value); - } - } - return ( -
    - - - - -
    - -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/ui/notices.tsx b/lib/ui/notices.tsx deleted file mode 100644 index f14bfe7..0000000 --- a/lib/ui/notices.tsx +++ /dev/null @@ -1,73 +0,0 @@ - -export const LockStolenMessage = { - view() { - return ( -
    -

    Refresh to view latest updates

    -

    - Your notes were updated in another browser session. Refresh the page to view the latest version. -

    -
    - - -
    -
    - ) - } -} - -export const FirstTimeMessage = { - view({attrs: {workbench}}) { - return ( -
    -

    Treehouse is under active development

    -

    This is a preview based on our main branch, which is actively being developed.

    -

    If you find a bug, please report it via -   -  > Submit Issue. -

    -

    - Data is stored using localstorage, which you can reset via -   -  > Reset Demo. -

    -
    - - -
    -
    - ) - } -} - -export const GitHubMessage = { - view({attrs: {workbench, finished}}) { - return ( -
    -

    Login with GitHub

    -

    The GitHub backend is experimental so use at your own risk!

    -

    To store your workbench we will create a public repository called

    <username>.treehouse.sh
    if it doesn't already exist. You can manually make this repository private via GitHub if you want.

    -

    You can Logout via the -   -   - menu in the top right to return to the localstorage backend. -

    -
    - - -
    -
    - ) - } -} diff --git a/lib/ui/outline.tsx b/lib/ui/outline.tsx deleted file mode 100644 index a5ed016..0000000 --- a/lib/ui/outline.tsx +++ /dev/null @@ -1,289 +0,0 @@ - -import { Workbench, Path } from "../workbench/mod.ts"; -import { objectCall, componentsWith, objectHas } from "../model/hooks.ts"; -import { NodeEditor } from "./node/editor.tsx"; - -// bunch of components I wish weren't direct dependencies -import { Checkbox } from "../com/checkbox.tsx"; -import { Document } from "../com/document.tsx"; -import { Tag } from "../com/tag.tsx"; -import { CodeBlock } from "../com/codeblock.tsx"; - -import { getNodeView } from "../view/views.ts"; - -export interface Attrs { - path: Path; - workbench: Workbench; -} - -export interface State { - hover: boolean; - tagPopover?: Popover; -} - -interface Popover { - onkeydown: Function; - oninput: Function; -} - -export const OutlineEditor: m.Component = { - view ({attrs: {workbench, path, alwaysShowNew}}) { - return objectHas(path.node, "childrenView") - ? m(componentsWith(path.node, "childrenView")[0].childrenView(), {workbench, path}) - : m(getNodeView(path.node), {workbench, path, alwaysShowNew}); - } -} - -// handles: expanded state, node menu+handle, children -export const OutlineNode: m.Component = { - view ({attrs, state, children}) { - let {path, workbench} = attrs; - let node = path.node; - - let isRef = false; - let handleNode = node; - if (node.refTo) { - isRef = true; - node = handleNode.refTo; - } - - let isCut = false; - if (workbench.clipboard && workbench.clipboard.op === "cut") { - if (workbench.clipboard.node.id === node.id) { - isCut = true; - } - } - - const expanded = workbench.workspace.getExpanded(path.head, handleNode); - const placeholder = objectHas(node, "handlePlaceholder") ? objectCall(node, "handlePlaceholder") : ''; - - const hover = (e) => { - state.hover = true; - e.stopPropagation(); - } - - const unhover = (e) => { - state.hover = false; - e.stopPropagation(); - } - - - const cancelTagPopover = () => { - if (state.tagPopover) { - workbench.closePopover(); - state.tagPopover = undefined; - } - } - - const oninput = (e) => { - if (state.tagPopover) { - state.tagPopover.oninput(e); - if (!e.target.value.includes("#")) { - cancelTagPopover(); - } - } else { - if (e.target.value.includes("#")) { - state.tagPopover = {}; - // Don't love that we're hard depending on Tag - Tag.showPopover(workbench, path, node, (onkeydown, oninput) => { - state.tagPopover = {onkeydown, oninput}; - }, cancelTagPopover); - } - } - } - - const onkeydown = (e) => { - if (state.tagPopover) { - if (e.key === "Escape") { - cancelTagPopover(); - return; - } - if (state.tagPopover.onkeydown(e) === false) { - e.stopPropagation(); - return false; - } - } - const anyModifiers = e.shiftKey || e.metaKey || e.altKey || e.ctrlKey; - switch (e.key) { - case "ArrowUp": - if (e.target.selectionStart !== 0 && !anyModifiers) { - e.stopPropagation() - } - break; - case "ArrowDown": - if (e.target.selectionStart !== e.target.value.length && e.target.selectionStart !== 0 && !anyModifiers) { - e.stopPropagation() - } - break; - case "Backspace": - // cursor at beginning of empty text - if (e.target.value === "") { - e.preventDefault(); - e.stopPropagation(); - if (node.childCount > 0) { - return; - } - workbench.executeCommand("delete", {node, path, event: e}); - return; - } - // cursor at beginning of non-empty text - if (e.target.value !== "" && e.target.selectionStart === 0 && e.target.selectionEnd === 0) { - e.preventDefault(); - e.stopPropagation(); - if (node.childCount > 0) { - return; - } - - // TODO: make this work as a command? - const above = workbench.workspace.findAbove(path); - if (!above) { - return; - } - const oldName = above.node.name; - above.node.name = oldName+e.target.value; - node.destroy(); - m.redraw.sync(); - workbench.focus(above, oldName.length); - - return; - } - break; - case "Enter": - e.preventDefault(); - if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return; - - // first check if node should become a code block - // todo: this should be a hook or some loose coupled system - if (e.target.value.startsWith("```") && !node.hasComponent(CodeBlock)) { - const lang = e.target.value.slice(3); - if (lang) { - workbench.executeCommand("make-code-block", {node, path}, lang); - e.stopPropagation(); - return; - } - } - - // cursor at end of text - if (e.target.selectionStart === e.target.value.length) { - if (node.childCount > 0 && workbench.workspace.getExpanded(path.head, node)) { - workbench.executeCommand("insert-child", {node, path}, "", 0); - } else { - workbench.executeCommand("insert", {node, path}); - } - e.stopPropagation(); - return; - } - // cursor at beginning of text - if (e.target.selectionStart === 0) { - workbench.executeCommand("insert-before", {node, path}); - e.stopPropagation(); - return; - } - // cursor in middle of text - if (e.target.selectionStart > 0 && e.target.selectionStart < e.target.value.length) { - workbench.executeCommand("insert", {node, path}, e.target.value.slice(e.target.selectionStart)).then(() => { - node.name = e.target.value.slice(0, e.target.selectionStart); - }); - e.stopPropagation(); - return; - } - break; - } - } - - const open = (e) => { - e.preventDefault(); - e.stopPropagation(); - - workbench.executeCommand("zoom", {node, path}); - - // clear text selection that happens after from double click - if (document.selection && document.selection.empty) { - document.selection.empty(); - } else if (window.getSelection) { - window.getSelection().removeAllRanges(); - } - } - - const toggle = (e) => { - // TODO: hook or something so to not hardcode - if (node.hasComponent(Document)) { - open(e); - return; - } - if (expanded) { - workbench.executeCommand("collapse", {node: handleNode, path}); - } else { - workbench.executeCommand("expand", {node: handleNode, path}); - } - e.stopPropagation(); - } - - const subCount = (n) => { - return n.childCount + n.getLinked("Fields").length; - } - - const showHandle = () => { - if (node.id === workbench.context?.node?.id || state.hover) { - return true; - } - if (node.name.length > 0) return true; - if (placeholder.length > 0) return true; - } - - return ( -
    -
    - workbench.showMenu(e, {node: handleNode, path})} - oncontextmenu={(e) => workbench.showMenu(e, {node: handleNode, path})} - data-menu="node" - viewBox="0 0 16 16"> - {state.hover && } - -
    workbench.showMenu(e, {node: handleNode, path})} data-menu="node" style={{ display: showHandle() ? 'block' : 'none' }}> - {(objectHas(node, "handleIcon")) - ? objectCall(node, "handleIcon", subCount(node) > 0 && !expanded) - : - {(subCount(node) > 0 && !expanded)?:null} - , - {(isRef)?:null} - - } -
    - {(node.raw.Rel === "Fields") - ?
    -
    - -
    - -
    - :
    - {objectHas(node, "beforeEditor") && componentsWith(node, "beforeEditor").map(component => m(component.beforeEditor(), {node, component}))} - - {objectHas(node, "afterEditor") && componentsWith(node, "afterEditor").map(component => m(component.afterEditor(), {node, component}))} -
    - } -
    - {objectHas(node, "belowEditor") && componentsWith(node, "belowEditor").map(component => m(component.belowEditor(), {node, component, expanded}))} - {(expanded === true) && -
    -
    -
    - {objectHas(node, "childrenView") - ? m(componentsWith(node, "childrenView")[0].childrenView(), {workbench, path}) - : m(getNodeView(node), {workbench, path})} -
    -
    - } -
    - ) - } -}; - - - - - - - diff --git a/lib/ui/palette.tsx b/lib/ui/palette.tsx deleted file mode 100644 index 70f01a1..0000000 --- a/lib/ui/palette.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { bindingSymbols } from "../action/keybinds.ts"; -import { Picker } from "./picker.tsx"; - -export const CommandPalette: m.Component = { - - view({ attrs: { workbench, ctx } }) { - const getTitle = (cmd) => { - const title = cmd.title || cmd.id; - return title.replace('-', ' ').replace(/(^|\s)\S/g, t => t.toUpperCase()); - } - const sort = (a, b) => { - return getTitle(a).localeCompare(getTitle(b)); - } - const onpick = (cmd) => { - workbench.closeDialog(); - workbench.commands.executeCommand(cmd.id, ctx); - } - const onchange = (state) => { - state.items = cmds.filter(cmd => { - const value = cmd.title || cmd.id; - return value.toLowerCase().includes(state.input.toLowerCase()); - }) - } - const getBindingSymbols = (cmd) => { - const binding = workbench.keybindings.getBinding(cmd.id); - return binding ? bindingSymbols(binding.key).join(" ").toUpperCase() : ""; - } - - const cmds = Object.values(workbench.commands.commands) - .filter(cmd => !cmd.hidden) - .filter(cmd => workbench.canExecuteCommand(cmd.id, ctx)) - .sort(sort); - - return ( -
    - -
    - -
    - } - itemview={(cmd) => -
    -
    {getTitle(cmd)}
    -
    {getBindingSymbols(cmd)}
    -
    - } /> -
    - ) - } -} - diff --git a/lib/ui/panel.tsx b/lib/ui/panel.tsx deleted file mode 100644 index ac7e9f3..0000000 --- a/lib/ui/panel.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { OutlineEditor } from "./outline.tsx"; -import { NodeEditor } from "./node/editor.tsx"; - -export const Panel = { - view({ attrs }) { - const path = attrs.path; - const workbench = attrs.workbench; - const node = path.node; - - const close = (e) => { - workbench.executeCommand("close-panel", {}, path); - } - const goBack = (e) => { - let node = path.pop(); - // if there was a duplicate id in the path, - // pop again - if (node === path.node) { - path.pop(); - } - } - const maximize = (e) => { - // todo: should be a command - workbench.panels = [path]; - workbench.context.path = path; - } - function calcHeight(value = "") { - let numberOfLineBreaks = (value.match(/\n/g) || []).length; - // min-height + lines x line-height + padding + border - let newHeight = 20 + numberOfLineBreaks * 20; - return newHeight; - } - let viewClass = ""; - if (node.getAttr("view")) { - viewClass = `${node.getAttr("view")}-panel` - } - return
    -
    - {(path.length > 1) ? -
    - - - -
    - : null} - -
    - {(node.parent && node.parent.id !== "@root") ? workbench.open(node.parent)}>{node.parent.name} :  } -
    - - {(workbench.panels.length > 1) ? -
    - - -
    - : null} -
    - -
    -
    workbench.showMenu(e, { node, path })} data-menu="node"> - -
    - -
    -
    - } -}; diff --git a/lib/ui/picker.tsx b/lib/ui/picker.tsx deleted file mode 100644 index 432a592..0000000 --- a/lib/ui/picker.tsx +++ /dev/null @@ -1,86 +0,0 @@ - -export interface Attrs { - input: string; - inputview: (onkeydown: Function, oninput: Function) => any; - itemview: (item: any, idx: number) => any; - onpick: (item: any) => void; - onchange: (State) => void; -} - -export interface State { - selected: number; - input: string; - items: any[]; -} - -export const Picker: m.Component = { - onupdate({ state, dom }) { - const items = dom.querySelector(".items").children; - if (state.selected !== undefined && items.length > 0) { - items[state.selected].scrollIntoView({ block: "nearest" }); - } - }, - - oncreate({ attrs, state, dom }) { - if (attrs.inputview) { - dom.querySelector("input")?.focus(); - } - if (state.selected === undefined) { - state.selected = 0; - } - }, - - view({ attrs, state }) { - - state.selected = (state.selected === undefined) ? 0 : state.selected; - state.input = (state.input === undefined) ? (attrs.input || "") : state.input; - if (state.items === undefined) { - state.items = []; - attrs.onchange(state); - } - - const onkeydown = (e) => { - const mod = (a, b) => ((a % b) + b) % b; - if (e.key === "ArrowDown") { - if (state.selected === undefined) { - state.selected = 0; - return false; - } - state.selected = mod(state.selected + 1, state.items.length); - return false; - } - if (e.key === "ArrowUp") { - if (state.selected === undefined) { - state.selected = 0; - } - state.selected = mod(state.selected - 1, state.items.length); - return false; - } - if (e.key === "Enter") { - if (state.selected !== undefined) { - attrs.onpick(state.items[state.selected]); - } - return false; - } - } - const oninput = (e) => { - state.input = e.target.value; - state.selected = 0; - attrs.onchange(state); - } - return ( -
    - {attrs.inputview(onkeydown, oninput, state.input)} -
    - {state.items.map((item, idx) => ( -
    attrs.onpick(item)} - onmouseover={() => state.selected = idx}> - {attrs.itemview(item, idx)} -
    - ))} -
    -
    - ) - } -} diff --git a/lib/ui/quickadd.tsx b/lib/ui/quickadd.tsx deleted file mode 100644 index dd87a8f..0000000 --- a/lib/ui/quickadd.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { OutlineEditor } from "./outline.tsx"; -import { Path } from "../workbench/mod.ts"; - -export const QuickAdd = { - view({attrs: {workbench, node}}) { - const path = new Path(node, "quickadd"); - return ( -
    -

    Quick Add

    - -
    - - -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/ui/reference.tsx b/lib/ui/reference.tsx deleted file mode 100644 index eee8e6f..0000000 --- a/lib/ui/reference.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { bindingSymbols } from "../action/keybinds.ts"; - -export const KeyboardReference = { - view({ attrs }) { - const workbench = attrs.workbench; - const shortcuts = { - "": [ - "pick-command", - ], - "Edit": [ - "cut", - "copy", - "copy-reference", - "paste", - "mark-done", - "insert", - "delete", - ], - "Navigate": [ - "expand", - "collapse", - "indent", - "outdent", - "move-up", - "move-down", - "prev", - "next", - ], - }; - - const getBindingSymbols = (cmd) => { - const binding = workbench.keybindings.getBinding(cmd.id); - return binding ? bindingSymbols(binding.key).join(" ").toUpperCase() : ""; - }; - - return ( -
    -

    Keyboard Shortcuts

    - - {Object.entries(shortcuts).map(([header, ids]) => { - return ( -
    - {(header.length !== 0) &&

    {header}

    } -
    - {ids.map(id => workbench.commands.commands[id]).map(cmd => ( -
    -
    {getBindingSymbols(cmd)}
    -
    {cmd.title}
    -
    - ))} -
    -
    - ); - })} -
    - ) - } -} \ No newline at end of file diff --git a/lib/ui/search.tsx b/lib/ui/search.tsx deleted file mode 100644 index d08932f..0000000 --- a/lib/ui/search.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Picker } from "./picker.tsx"; - -export const Search: m.Component = { - - view({ attrs: { input, workbench } }) { - - const onpick = (node) => { - workbench.closeDialog(); - workbench.open(node); - } - const onchange = (state) => { - if (state.input) { - state.items = workbench.search(state.input); - } else { - state.items = []; - } - } - - return ( - - ) - } -} diff --git a/lib/ui/settings.tsx b/lib/ui/settings.tsx deleted file mode 100644 index fa38f09..0000000 --- a/lib/ui/settings.tsx +++ /dev/null @@ -1,40 +0,0 @@ - -export const Settings = { - view({attrs: {workbench}, state}) { - const currentTheme = workbench.workspace.settings.theme; - state.selectedTheme = (state.selectedTheme === undefined) ? currentTheme : state.selectedTheme; - const oninput = (e) => { - state.selectedTheme = e.target.value; - } - return ( -
    -

    Settings

    -
    -
    Theme
    -
    - -
    -
    -
    - - -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/view/cards.tsx b/lib/view/cards.tsx deleted file mode 100644 index de669bb..0000000 --- a/lib/view/cards.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Node } from "../model/mod.ts"; -import { InlineFrame } from "../com/iframe.tsx"; - -function tryFields(n: Node, fields: string[]): any|null { - for (const field of fields) { - const value = n.componentField(field); - if (value) { - return value; - } - } - return null; -} - -export default { - view({attrs: {workbench, path}}) { - const node = path.node; - return ( -
    - {node.children.map(n => { - const linkURL = tryFields(n, ["linkURL"]); - const dateTime = tryFields(n, ["updatedAt", "createdAt"]); - const userName = tryFields(n, ["updatedBy", "createdBy", "username"]); - const thumbnailURL = tryFields(n, ["thumbnailURL", "coverURL"]); - const frame = n.getComponent(InlineFrame); - - let thumbnail = ; - if (frame) { - thumbnail = ( -
    - -
    - ) - } - return ( -
    -
    - {(linkURL) - ? {thumbnail} - : thumbnail - } -
    -
    - {(linkURL) - ? {n.name} - : n.name} -
    - {userName &&
    - {userName} -
    } - {dateTime &&
    - {timeAgo(dateTime)} -
    } -
    - )})} -
    - ) - } -} - -function timeAgo(date) { - if (!(date instanceof Date)) { - throw new Error("Input must be a valid Date object."); - } - - const now = new Date(); - const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); - - const intervals = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1 - }; - - for (const [unit, secondsInUnit] of Object.entries(intervals)) { - const count = Math.floor(seconds / secondsInUnit); - if (count > 0) { - return `${count} ${unit}${count > 1 ? 's' : ''} ago`; - } - } - - return 'just now'; -} \ No newline at end of file diff --git a/lib/view/document.tsx b/lib/view/document.tsx deleted file mode 100644 index 1f13de8..0000000 --- a/lib/view/document.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { NewNode } from "../ui/node/new.tsx"; -import { OutlineNode } from "../ui/outline.tsx"; - -export default { - view({attrs: {workbench, path, alwaysShowNew}}) { - let node = path.node; - if (path.node.refTo) { - node = path.node.refTo; - } - let showNew = false; - if ((node.childCount === 0 && node.getLinked("Fields").length === 0) || alwaysShowNew) { - showNew = true; - } - return ( -
    -
    - {(node.getLinked("Fields").length > 0) && - node.getLinked("Fields").map(n => ) - } -
    -
    - {(node.childCount > 0) && node.children.map(n => )} - {showNew && } -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/view/empty.tsx b/lib/view/empty.tsx deleted file mode 100644 index 4007c3a..0000000 --- a/lib/view/empty.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { NewNode } from "../ui/node/new.tsx"; -import { OutlineNode } from "../ui/outline.tsx"; - -export default { - view({attrs: {workbench, path}}) { - return ( -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/view/list.tsx b/lib/view/list.tsx deleted file mode 100644 index e927704..0000000 --- a/lib/view/list.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { NewNode } from "../ui/node/new.tsx"; -import { OutlineNode } from "../ui/outline.tsx"; -import { SmartNode } from "../com/smartnode.tsx"; - -export default { - view({attrs: {workbench, path, alwaysShowNew}}) { - let node = path.node; - if (path.node.refTo) { - node = path.node.refTo; - } - let showNew = false; - if ((node.childCount === 0 && node.getLinked("Fields").length === 0) || alwaysShowNew) { - showNew = true; - } - // TODO: find some way to not hardcode this rule - if (node.hasComponent(SmartNode)) { - showNew = false; - } - return ( -
    -
    - {(node.getLinked("Fields").length > 0) && - node.getLinked("Fields").map(n => ) - } -
    -
    - {(node.childCount > 0) && node.children.map(n => )} - {showNew && } -
    -
    - ) - } -} \ No newline at end of file diff --git a/lib/view/table.tsx b/lib/view/table.tsx deleted file mode 100644 index d7a466a..0000000 --- a/lib/view/table.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { NodeEditor, TextAreaEditor } from "../ui/node/editor.tsx"; -import { OutlineNode } from "../ui/outline.tsx"; - -export default { - view({attrs: {workbench, path}, state}) { - const node = path.node; - state.fields = (state.fields === undefined) ? new Set() : state.fields; - node.children.forEach(n => { - n.getLinked("Fields").forEach(f => state.fields.add(f.name)); - }); - const getFieldEditor = (node, field) => { - const fields = node.getLinked("Fields").filter(f => f.name === field); - if (fields.length === 0) return ""; - return - } - return ( - - - - - {[...state.fields].map(f => )} - - - - {node.children.map(n => ( - - - {[...state.fields].map(f => )} - - ))} - -
    Title{f}
    {getFieldEditor(n, f)}
    - ) - } -} \ No newline at end of file diff --git a/lib/view/tabs.tsx b/lib/view/tabs.tsx deleted file mode 100644 index 3deba19..0000000 --- a/lib/view/tabs.tsx +++ /dev/null @@ -1,29 +0,0 @@ - -import { getNodeView } from "../view/views.ts"; - -export default { - view({ attrs: { workbench, path }, state }) { - const node = path.node; - state.tabs = (state.tabs === undefined) ? new Set() : state.tabs; - state.selectedTab = (state.selectedTab === undefined) ? "" : state.selectedTab; - node.children.forEach(n => { - state.tabs.add(n.raw); - if (state.selectedTab === "") state.selectedTab = n.raw.ID; - }); - const handleTabClick = (id) => { - state.selectedTab = id; - }; - const selectedNode = node.children.find(n => n.id === state.selectedTab); - return ( -
    -
    - {[...state.tabs].map(n =>
    handleTabClick(n.ID)}>{n.Name}
    )} -
    -
    -
    - {m(getNodeView(selectedNode), {workbench, path: path.append(selectedNode)})} -
    -
    - ) - } -} diff --git a/lib/view/views.ts b/lib/view/views.ts deleted file mode 100644 index ceaaea9..0000000 --- a/lib/view/views.ts +++ /dev/null @@ -1,44 +0,0 @@ -import empty from "./empty.tsx"; -import list from "./list.tsx"; -import table from "./table.tsx"; -import tabs from "./tabs.tsx"; -import document from "./document.tsx"; -import cards from "./cards.tsx"; - -export const views = { - list, - table, - tabs, - document, - cards -} - -// deprecated. use getNodeView -export function getView(name) { - return views[name] || empty; -} - -export function getNodeView(node) { - return views[node.getAttr("view") || "list"] || empty; -} - -window.registerView = (name, view) => { - views[name] = view; - workbench.commands.registerCommand({ - id: `view-${name}`, - title: `View as ${toTitleCase(name)}`, - action: (ctx: Context) => { - if (!ctx.node) return; - ctx.node.setAttr("view", name); - } - }); -} - -function toTitleCase(str) { - return str.replace( - /\w\S*/g, - function(txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - } - ); -} \ No newline at end of file diff --git a/lib/workbench/mod.ts b/lib/workbench/mod.ts deleted file mode 100644 index 359d2e7..0000000 --- a/lib/workbench/mod.ts +++ /dev/null @@ -1,18 +0,0 @@ -export { Workbench } from "./workbench.ts"; -export { Path } from "./path.ts"; -export { Workspace } from "./workspace.ts"; - -import { Node } from "../model/mod.ts"; - -/** - * Context is a user context object interface. This is used to - * track a global context for the user, mainly what node(s) are selected, - * but is also used for local context in commands. - */ -export interface Context { - path: Path; - node: Node|null; - nodes?: Node[]; - event?: Event; - -} \ No newline at end of file diff --git a/lib/workbench/path.ts b/lib/workbench/path.ts deleted file mode 100644 index dd1fc90..0000000 --- a/lib/workbench/path.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Node } from "../model/mod.ts"; -import { SHA1 } from "./util.js"; - -/** - * Path is a stack of nodes. It can be used as a history stack - * so you can "zoom" into subnodes and return back to previous nodes. - * It is also used to identify nodes in the UI more specifically than - * the node ID since a node can be shown more than once (references, panels, etc). - */ -export class Path { - name: string; - nodes: Node[]; - - constructor(head?: Node, name?: string) { - if (name) { - this.name = name; - } else { - this.name = Math.random().toString(36).substring(2); - } - if (head) { - this.nodes = [head]; - } else { - this.nodes = []; - } - } - - push(node: Node) { - this.nodes.push(node); - } - - pop(): Node|null { - return this.nodes.pop() || null; - } - - sub(): Path { - return new Path(this.node, this.name); - } - - clone(): Path { - const p = new Path(); - p.name = this.name; - p.nodes = [...this.nodes]; - return p; - } - - append(node: Node): Path { - const p = this.clone(); - p.push(node); - return p; - } - - get length(): number { - return this.nodes.length; - } - - get id(): string { - return SHA1([this.name, ...this.nodes.map(n => n.id)].join(":")); - } - - get node(): Node { - return this.nodes[this.nodes.length-1]; - } - - get previous(): Node|null { - if (this.nodes.length < 2) return null; - return this.nodes[this.nodes.length-2]; - } - - get head(): Node { - return this.nodes[0]; - } -} - diff --git a/lib/workbench/path_test.ts b/lib/workbench/path_test.ts deleted file mode 100644 index 1f08068..0000000 --- a/lib/workbench/path_test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { assertEquals, assertNotEquals } from "https://deno.land/std@0.173.0/testing/asserts.ts"; -import { Path } from "./path.ts"; -import { Bus } from "../model/module/mod.ts"; - -Deno.test("path ids", async () => { - const b = new Bus(); - const n1 = b.make("Node1"); - const n2 = b.make("Node2"); - const n3 = b.make("Node3"); - - const p = new Path(n1); - const d1 = p.id; - p.push(n2); - assertNotEquals(d1, p.id); - - const pA = p.append(n3); - const pB = p.append(n3); - assertEquals(pA.id, pB.id); - - pA.pop(); - assertNotEquals(pA.id, pB.id); - pB.pop(); - assertEquals(pA.id, pB.id); - - assertEquals(p.head.name, "Node1"); - assertEquals(p.node.name, "Node2"); -}); \ No newline at end of file diff --git a/lib/workbench/util.js b/lib/workbench/util.js deleted file mode 100644 index 90647bd..0000000 --- a/lib/workbench/util.js +++ /dev/null @@ -1,138 +0,0 @@ - -/** -* Secure Hash Algorithm (SHA1) -* http://www.webtoolkit.info/ -**/ -export function SHA1(msg) { - function rotate_left(n,s) { - var t4 = ( n<>>(32-s)); - return t4; - }; - function lsb_hex(val) { - var str=''; - var i; - var vh; - var vl; - for( i=0; i<=6; i+=2 ) { - vh = (val>>>(i*4+4))&0x0f; - vl = (val>>>(i*4))&0x0f; - str += vh.toString(16) + vl.toString(16); - } - return str; - }; - function cvt_hex(val) { - var str=''; - var i; - var v; - for( i=7; i>=0; i-- ) { - v = (val>>>(i*4))&0x0f; - str += v.toString(16); - } - return str; - }; - function Utf8Encode(string) { - string = string.replace(/\r\n/g,'\n'); - var utftext = ''; - for (var n = 0; n < string.length; n++) { - var c = string.charCodeAt(n); - if (c < 128) { - utftext += String.fromCharCode(c); - } - else if((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } - else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } - return utftext; - }; - var blockstart; - var i, j; - var W = new Array(80); - var H0 = 0x67452301; - var H1 = 0xEFCDAB89; - var H2 = 0x98BADCFE; - var H3 = 0x10325476; - var H4 = 0xC3D2E1F0; - var A, B, C, D, E; - var temp; - msg = Utf8Encode(msg); - var msg_len = msg.length; - var word_array = new Array(); - for( i=0; i>>29 ); - word_array.push( (msg_len<<3)&0x0ffffffff ); - for ( blockstart=0; blockstart null}; - this.menu = {body: () => null}; - - } - - get mainPanel(): Path { - return this.panels[0]; - } - - async initialize() { - await this.workspace.load(); - - this.workspace.rawNodes.forEach(n => this.backend.index.index(n)); - this.workspace.observe((n => { - this.workspace.save(); - if (n.isDestroyed) { - this.backend.index.remove(n.id); - } else { - this.backend.index.index(n.raw); - n.components.forEach(com => this.backend.index.index(com.raw)); - } - })); - - - if (this.workspace.lastOpenedID) { - this.openNewPanel(this.workspace.find(this.workspace.lastOpenedID) || this.workspace.mainNode()); - } else { - this.openNewPanel(this.workspace.mainNode()); - } - - if (this.backend.loadExtensions) { - await this.backend.loadExtensions(); - } - - if (this.workspace.settings.theme) { - const css = document.createElement("link"); - // TODO: figure out better way to couple themes than hardcoded hotlinked URL - css.setAttribute("href", `https://treehouse.sh/style/themes/${this.workspace.settings.theme}.css`); - css.setAttribute("rel", "stylesheet"); - css.setAttribute("type", "text/css"); - document.head.appendChild(css); - } - - m.redraw(); - - } - - authenticated(): boolean { - return this.backend.auth && this.backend.auth.currentUser(); - } - - openQuickAdd() { - let node = this.workspace.find("@quickadd"); - if (!node) { - node = this.workspace.new("@quickadd"); - } - this.showDialog(() => m(QuickAdd, {workbench: this, node}), true); - setTimeout(() => { - document.querySelector("main > dialog .new-node input").focus(); - }, 1); - } - - commitQuickAdd() { - const node = this.workspace.find("@quickadd"); - if (!node) return; - const today = this.todayNode(); - node.children.forEach(n => n.parent = today); - } - - clearQuickAdd() { - const node = this.workspace.find("@quickadd"); - if (!node) return; - node.children.forEach(n => n.destroy()); - } - - // TODO: goto workspace - todayNode(): Node { - const today = new Date(); - const dayNode = today.toUTCString().split(today.getFullYear())[0]; - const weekNode = `Week ${String(getWeekOfYear(today)).padStart(2, "0")}`; - const yearNode = `${today.getFullYear()}`; - const todayPath = ["@calendar", yearNode, weekNode, dayNode].join("/"); - let todayNode = this.workspace.find(todayPath); - if (!todayNode) { - todayNode = this.workspace.new(todayPath); - } - return todayNode; - } - - openToday() { - this.open(this.todayNode()); - } - - open(n: Node) { - // TODO: not sure this is still necessary - if (!this.workspace.expanded[n.id]) { - this.workspace.expanded[n.id] = {}; - } - - this.workspace.lastOpenedID = n.id; - this.workspace.save(); - const p = new Path(n); - this.panels[0] = p - this.context.path = p; - } - - openNewPanel(n: Node) { - // TODO: not sure this is still necessary - if (!this.workspace.expanded[n.id]) { - this.workspace.expanded[n.id] = {}; - } - - this.workspace.lastOpenedID = n.id; - this.workspace.save(); - const p = new Path(n); - this.panels.push(p); - this.context.path = p; - } - - closePanel(panel: Path) { - this.panels = this.panels.filter(p => p.name !== panel.name); - } - - defocus() { - const input = this.getInput(this.context.path); - if (input) { - input.blur(); - } - this.context.node = null; - this.context.path = null; - } - - focus(path: Path, pos?: number = 0) { - const input = this.getInput(path); - if (input) { - this.context.path = path; - input.focus(); - if (pos !== undefined) { - input.setSelectionRange(pos,pos); - } - } else { - console.warn("unable to find input for", path); - } - } - - getInput(path: Path): any { - let id = `input-${path.id}-${path.node.id}`; - // kludge: - if (path.node.raw.Rel === "Fields") { - if (path.node.name !== "") { - id = id+"-value"; - } - } - const el = document.getElementById(id); - if (el.editor) { - return el.editor; - } - return el; - } - - canExecuteCommand(id: string, ctx: any, ...rest: any): boolean { - ctx = this.newContext(ctx); - return this.commands.canExecuteCommand(id, ctx, ...rest); - } - - executeCommand(id: string, ctx: any, ...rest: any): Promise { - ctx = this.newContext(ctx); - console.log(id, ctx, ...rest); - return this.commands.executeCommand(id, ctx, ...rest); - } - - newContext(ctx: any): Context { - return Object.assign({}, this.context, ctx); - } - - showMenu(event: Event, ctx: any, style?: {}) { - event.stopPropagation(); - event.preventDefault(); - const trigger = event.target.closest("*[data-menu]"); - const rect = trigger.getBoundingClientRect(); - if (!style) { - const align = trigger.dataset["align"] || "left"; - style = { - top: `${document.body.scrollTop+rect.y+rect.height}px` - } - if (align === "right") { - style.marginLeft = "auto"; - style.marginRight = `${document.body.offsetWidth - rect.right}px`; - } else { - style.marginLeft = `${document.body.scrollLeft+rect.x}px`; - style.marginRight = "auto"; - } - } - const items = this.menus.menus[trigger.dataset["menu"]]; - const cmds = items.filter(i => i.command).map(i => this.commands.commands[i.command]); - if (!items) return; - this.menu = {body: () => m(Menu, { - workbench: this, - ctx: this.newContext(ctx), - items: items, - commands: cmds, - }), style}; - m.redraw(); - setTimeout(() => { - // this next frame timeout is so any current dialog can close before attempting - // to showModal on already open dialog, which causes exception. - document.querySelector("main > dialog.menu").showModal(); - }, 0); - } - - closeMenu() { - document.querySelector("main > dialog.menu").close(); - workbench.menu.body = () => null; - } - - showPalette(x: number, y: number, ctx: Context) { - this.showDialog(() => m(CommandPalette, {workbench: this, ctx}), false, {left: `${x}px`, top: `${y}px`}); - } - - showNotice(notice, finished) { - this.showDialog(() => m({ - "firsttime": FirstTimeMessage, - "github": GitHubMessage, - "lockstolen": LockStolenMessage, - }[notice], {workbench: this, finished}), true, undefined, (notice==="lockstolen")?true:false); - } - - toggleDrawer() { - this.drawer.open = !this.drawer.open; - m.redraw(); - } - - showSettings() { - this.showDialog(() => m(Settings, {workbench: this}), true); - } - - showPopover(body: any, style?: {}) { - this.popover = {body, style}; - m.redraw(); - } - - closePopover() { - this.popover = null; - m.redraw(); - } - - showDialog(body: any, backdrop?: boolean, style?: {}, explicitClose?: boolean) { - this.dialog = {body, backdrop, style, explicitClose}; - m.redraw(); - setTimeout(() => { - // this next frame timeout is so any current dialog can close before attempting - // to showModal on already open dialog, which causes exception. - document.querySelector("main > dialog.modal").showModal(); - }, 0); - } - - isDialogOpen(): boolean { - return document.querySelector("main > dialog.modal").hasAttribute("open"); - } - - closeDialog() { - document.querySelector("main > dialog.modal").close(); - this.dialog.body = () => null; - } - - search(query: string): Node[] { - if (!query) return []; - // this regex selects spaces NOT inside quotes - let splitQuery = query.split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/); - let textQuery = splitQuery.filter(term => !term.includes(":")).join(" "); - let fieldQuery = Object.fromEntries(splitQuery.filter(term => term.includes(":")).map(term => term.toLowerCase().split(":"))); - if (!textQuery && Object.keys(fieldQuery).length > 0) { - // when text query is empty, no results will show up, - // but we index field names, so this works for now. - textQuery = Object.keys(fieldQuery)[0]; - } - const passFieldQuery = (node: Node): boolean => { - // kludgy filter on fields - if (Object.keys(fieldQuery).length > 0) { - const fields = {}; - for (const f of node.getLinked("Fields")) { - fields[f.name.toLowerCase()] = f.value.toLowerCase(); - } - for (const f in fieldQuery) { - const field = fields[f.replace(/['"]/g, "")]; - if (!field || field !== fieldQuery[f].replace(/['"]/g, "")) { - return false; - } - } - } - return true; - } - // simple, limited search for tag implementation - if (textQuery.startsWith("#")) { - return Tag.findTagged(this.workspace, textQuery.replace("#", "")).filter(passFieldQuery); - } - let resultCache = {}; - this.backend.index.search(textQuery).forEach(id => { - let node = window.workbench.workspace.find(id); - if (!node) { - return; - } - // if component/field value, get the parent - if (node.value) { - node = node.parent; - // parent might not actually exist - if (!node.raw) return; - } - if (!passFieldQuery(node)) { - return; - } - resultCache[node.id] = node; - }); - return Object.values(resultCache); - } - - -} - - -function getWeekOfYear(date) { - var d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); - var dayNum = d.getUTCDay() || 7; - d.setUTCDate(d.getUTCDate() + 4 - dayNum); - var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1)); - return Math.ceil((((d - yearStart) / 86400000) + 1)/7); -} \ No newline at end of file diff --git a/lib/workbench/workspace.ts b/lib/workbench/workspace.ts deleted file mode 100644 index 3039a59..0000000 --- a/lib/workbench/workspace.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { FileStore, ChangeNotifier } from "../backend/mod.ts"; -import { Bus, Node, RawNode } from "../model/mod.ts"; -import { Path } from "./mod.ts"; -import * as module from "../model/module/mod.ts"; - - -/** - * Workspace is a container for nodes and manages marshaling them using - * the FileStore backend API. It also keeps track of what nodes have been - * expanded and what node was last opened. It serializes as JSON with a - * version indicator and will handle migrations of old versions to the - * latest when loading. Saving is currently debounced here so this applies - * to all backends. - */ -export class Workspace { - fs: FileStore; - bus: Bus; - - lastOpenedID: string; - expanded: { [key: string]: { [key: string]: boolean } }; // [rootid][id] - settings: {}; - - constructor(fs: FileStore, changes?: ChangeNotifier) { - this.fs = fs; - this.bus = new module.Bus(); - this.expanded = {}; - this.settings = {}; - - if (changes) { - changes.registerNotifier(this.reload.bind(this)); - } - - this.writeDebounce = debounce(async (path, contents) => { - try { - await this.fs.writeFile(path, contents); - console.log("Saved workspace."); - } catch (e: Error) { - console.error(e); - document.dispatchEvent(new CustomEvent("BackendError")); - } - }); - } - - get rawNodes(): RawNode[] { - return this.bus.export(); - } - - observe(fn: (n: Node) => void) { - this.bus.observe(fn); - } - - async save(immediate?: boolean) { - const contents = JSON.stringify({ - version: 1, - lastopen: this.lastOpenedID, - expanded: this.expanded, - nodes: this.rawNodes, - settings: this.settings - }, null, 2); - if (immediate) { - await this.fs.writeFile("workspace.json", contents); - } else { - this.writeDebounce("workspace.json", contents); - } - } - - migrateRawNode(n: RawNode): RawNode { - if (n.Name === "treehouse.SearchNode") { - n.Name = "treehouse.SmartNode"; - } - return n; - } - - async reload(nodeIDs: string[]) { - let doc = JSON.parse(await this.fs.readFile("workspace.json") || "{}"); - if (doc.nodes) { - doc.nodes = doc.nodes.filter(n => nodeIDs.includes(n.ID)); - doc.nodes = doc.nodes.map(this.migrateRawNode); - this.bus.import(doc.nodes); - m.redraw(); - console.log(`Reloaded ${doc.nodes.length} nodes.`); - } - } - - async load() { - let doc = JSON.parse(await this.fs.readFile("workspace.json") || "{}"); - if (doc.nodes) { - doc.nodes = doc.nodes.map(this.migrateRawNode); - this.bus.import(doc.nodes); - console.log(`Loaded ${doc.nodes.length} nodes.`); - } - if (doc.expanded) { - // Only import the node keys that still exist - // in the workspace. - for (const n in doc.expanded) { - for (const i in doc.expanded[n]) { - if (this.bus.find(i)) { - if (!this.expanded[n]) this.expanded[n] = {}; - this.expanded[n][i] = doc.expanded[n][i]; - } - } - } - } - if (doc.lastopen) { - this.lastOpenedID = doc.lastopen; - } - if (doc.settings) { - this.settings = Object.assign(this.settings, doc.settings); - } - } - - mainNode(): Node { - let main = this.bus.find("@workspace"); - if (!main) { - console.info("Building missing workspace node.") - const root = this.bus.find("@root"); - const ws = this.bus.make("@workspace"); - ws.name = "Workspace"; - ws.parent = root; - const cal = this.bus.make("@calendar"); - cal.name = "Calendar"; - cal.parent = ws; - const home = this.bus.make("Home"); - home.parent = ws; - main = ws; - } - return main; - } - - find(path: string): Node | null { - return this.bus.find(path) - } - - new(name: string, value?: any): Node { - return this.bus.make(name, value); - } - - // TODO: take single Path - getExpanded(head: Node, n: Node): boolean { - if (!this.expanded[head.id]) { - this.expanded[head.id] = {}; - } - let expanded = this.expanded[head.id][n.id]; - if (expanded === undefined) { - expanded = false; - } - return expanded; - } - - // TODO: take single Path - setExpanded(head: Node, n: Node, b: boolean) { - if (!this.expanded[head.id]) { - this.expanded[head.id] = {}; - } - this.expanded[head.id][n.id] = b; - this.save(); - } - - findAbove(path: Path): Path | null { - if (path.node.id === path.head.id) { - return null; - } - const p = path.clone(); - p.pop(); // pop to parent - let prev = path.node.prevSibling; - if (!prev) { - // if not a field and parent has fields, return last field - const fieldCount = path.previous.getLinked("Fields").length; - if (path.node.raw.Rel !== "Fields" && fieldCount > 0) { - return p.append(path.previous.getLinked("Fields")[fieldCount - 1]); - } - // if no prev sibling, and no fields, return parent - return p; - } - const lastSubIfExpanded = (p: Path): Path => { - const expanded = this.getExpanded(path.head, p.node); - if (!expanded) { - // if not expanded, return input path - return p; - } - const fieldCount = p.node.getLinked("Fields").length; - if (p.node.childCount === 0 && fieldCount > 0) { - const lastField = p.node.getLinked("Fields")[fieldCount - 1]; - // if expanded, no children, has fields, return last field or its last sub if expanded - return lastSubIfExpanded(p.append(lastField)); - } - if (p.node.childCount === 0) { - // expanded, no fields, no children - return p; - } - const lastChild = p.node.children[p.node.childCount - 1]; - // return last child or its last sub if expanded - return lastSubIfExpanded(p.append(lastChild)); - } - // return prev sibling or its last child if expanded - return lastSubIfExpanded(p.append(prev)); - } - - findBelow(path: Path): Path | null { - // TODO: find a way to indicate pseudo "new" node for expanded leaf nodes - const p = path.clone(); - if (this.getExpanded(path.head, path.node) && path.node.getLinked("Fields").length > 0) { - // if expanded and fields, return first field - return p.append(path.node.getLinked("Fields")[0]); - } - if (this.getExpanded(path.head, path.node) && path.node.childCount > 0) { - // if expanded and children, return first child - return p.append(path.node.children[0]); - } - const nextSiblingOrParentNextSibling = (p: Path): Path | null => { - const next = p.node.nextSibling; - if (next) { - p.pop(); // pop to parent - // if next sibling, return that - return p.append(next); - } - const parent = p.previous; - if (!parent) { - // if no parent, return null - return null; - } - if (p.node.raw.Rel === "Fields" && parent.childCount > 0) { - p.pop(); // pop to parent - // if field and parent has children, return first child - return p.append(parent.children[0]); - } - p.pop(); // pop to parent - // return parents next sibling or parents parents next sibling - return nextSiblingOrParentNextSibling(p); - } - // return next sibling or parents next sibling - return nextSiblingOrParentNextSibling(p); - } - -} - - -function debounce(func, timeout = 3000) { - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => { func.apply(this, args); }, timeout); - }; -} diff --git a/web/static/logo.svg b/logo.svg similarity index 100% rename from web/static/logo.svg rename to logo.svg diff --git a/web/static/photos/blog/nls.jpeg b/photos/blog/nls.jpeg similarity index 100% rename from web/static/photos/blog/nls.jpeg rename to photos/blog/nls.jpeg diff --git a/web/static/photos/blog/notion.jpeg b/photos/blog/notion.jpeg similarity index 100% rename from web/static/photos/blog/notion.jpeg rename to photos/blog/notion.jpeg diff --git a/web/static/photos/blog/obsidian.png b/photos/blog/obsidian.png similarity index 100% rename from web/static/photos/blog/obsidian.png rename to photos/blog/obsidian.png diff --git a/web/static/photos/blog/tana.webp b/photos/blog/tana.webp similarity index 100% rename from web/static/photos/blog/tana.webp rename to photos/blog/tana.webp diff --git a/web/static/photos/blog/tiddlywiki.png b/photos/blog/tiddlywiki.png similarity index 100% rename from web/static/photos/blog/tiddlywiki.png rename to photos/blog/tiddlywiki.png diff --git a/web/static/photos/blog/workflowy.png b/photos/blog/workflowy.png similarity index 100% rename from web/static/photos/blog/workflowy.png rename to photos/blog/workflowy.png diff --git a/web/static/photos/hero-image.png b/photos/hero-image.png similarity index 100% rename from web/static/photos/hero-image.png rename to photos/hero-image.png diff --git a/web/static/photos/live-search.png b/photos/live-search.png similarity index 100% rename from web/static/photos/live-search.png rename to photos/live-search.png diff --git a/web/static/photos/outline-image.png b/photos/outline-image.png similarity index 100% rename from web/static/photos/outline-image.png rename to photos/outline-image.png diff --git a/web/static/photos/quickadd-image.png b/photos/quickadd-image.png similarity index 100% rename from web/static/photos/quickadd-image.png rename to photos/quickadd-image.png diff --git a/web/static/photos/screenshot-small.png b/photos/screenshot-small.png similarity index 100% rename from web/static/photos/screenshot-small.png rename to photos/screenshot-small.png diff --git a/web/static/photos/search-image.png b/photos/search-image.png similarity index 100% rename from web/static/photos/search-image.png rename to photos/search-image.png diff --git a/web/static/style.css b/style.css similarity index 100% rename from web/static/style.css rename to style.css diff --git a/web/static/style/design.css b/style/design.css similarity index 100% rename from web/static/style/design.css rename to style/design.css diff --git a/web/static/style/normalize.css b/style/normalize.css similarity index 100% rename from web/static/style/normalize.css rename to style/normalize.css diff --git a/web/static/style/site.css b/style/site.css similarity index 100% rename from web/static/style/site.css rename to style/site.css diff --git a/web/static/style/themes/darkmode.css b/style/themes/darkmode.css similarity index 100% rename from web/static/style/themes/darkmode.css rename to style/themes/darkmode.css diff --git a/web/static/style/themes/sepia.css b/style/themes/sepia.css similarity index 100% rename from web/static/style/themes/sepia.css rename to style/themes/sepia.css diff --git a/web/static/style/themes/sublime.css b/style/themes/sublime.css similarity index 100% rename from web/static/style/themes/sublime.css rename to style/themes/sublime.css diff --git a/web/static/vnd/codemirror-6.0.1.min.js b/vnd/codemirror-6.0.1.min.js similarity index 100% rename from web/static/vnd/codemirror-6.0.1.min.js rename to vnd/codemirror-6.0.1.min.js diff --git a/web/static/vnd/minisearch-6.0.1.min.js b/vnd/minisearch-6.0.1.min.js similarity index 100% rename from web/static/vnd/minisearch-6.0.1.min.js rename to vnd/minisearch-6.0.1.min.js diff --git a/web/static/vnd/mithril-2.0.3.min.js b/vnd/mithril-2.0.3.min.js similarity index 100% rename from web/static/vnd/mithril-2.0.3.min.js rename to vnd/mithril-2.0.3.min.js diff --git a/web/static/vnd/octokit-18.12.0.min.js b/vnd/octokit-18.12.0.min.js similarity index 100% rename from web/static/vnd/octokit-18.12.0.min.js rename to vnd/octokit-18.12.0.min.js diff --git a/web/_components/latest.njk b/web/_components/latest.njk deleted file mode 100644 index e2f87b0..0000000 --- a/web/_components/latest.njk +++ /dev/null @@ -1,8 +0,0 @@ -
    -{% asyncEach item in nav.menu("/blog").children | sort(true, true, "data.date") %} -
    - {{item.data.title}} -
    {{item.data.date | formatDate }}
    -
    -{% endeach %} -
    \ No newline at end of file diff --git a/web/_components/nav.tsx b/web/_components/nav.tsx deleted file mode 100644 index 558998e..0000000 --- a/web/_components/nav.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export default ({ title, href, currentUrl, nav }) => { - const menu = nav.menu(href); - const showChildren = currentUrl.includes(href) && menu.children.length > 0; - return ( - <> -
    {title}
    - {showChildren &&
    } - - ); -} \ No newline at end of file diff --git a/web/_components/toc.njk b/web/_components/toc.njk deleted file mode 100644 index dd37596..0000000 --- a/web/_components/toc.njk +++ /dev/null @@ -1,7 +0,0 @@ -{% if toc.length %} - -{% endif %} \ No newline at end of file diff --git a/web/_includes/layouts/blog.tsx b/web/_includes/layouts/blog.tsx deleted file mode 100644 index 430a490..0000000 --- a/web/_includes/layouts/blog.tsx +++ /dev/null @@ -1,28 +0,0 @@ -export const title = "Blog"; -export const active = "blog"; -export const heading = "Branching Out"; -export const subheading = "Notes from the Treehouse"; -export const layout = "layouts/default.tsx"; - -export default ({title, author, date, children, comp}, filters) => ( -
    -
    -
    -

    {title}

    -
    {author}
    -
    {new Date(date).toLocaleDateString('en-us', { year:"numeric", month:"long", day:"numeric"})}
    - {children} -
    - -
    -
    -); \ No newline at end of file diff --git a/web/_includes/layouts/default.tsx b/web/_includes/layouts/default.tsx deleted file mode 100644 index 3ff9f5e..0000000 --- a/web/_includes/layouts/default.tsx +++ /dev/null @@ -1,92 +0,0 @@ -export default ({title, site, active, heading, subheading, children}) => ( - - - - - - - - - - - - - - {title?`${title} - ${site.title}`:site.title} - - -
    - -
    -
    -
    -

    {heading}

    - {subheading &&
    {subheading}
    } -
    -
    - - {children} - -
    -
    -
    -

    Stay in touch

    -

    Sign up for our mailing list to receive updates on the project.

    -
    -
    -
    - - - -
    -
    -

    We don't share your email.

    -
    -
    - - - - - -); diff --git a/web/_includes/layouts/docs.tsx b/web/_includes/layouts/docs.tsx deleted file mode 100644 index 06af654..0000000 --- a/web/_includes/layouts/docs.tsx +++ /dev/null @@ -1,29 +0,0 @@ -export const title = "Docs"; -export const active = "docs"; -export const heading = "Documentation"; -export const layout = "layouts/default.tsx"; -export default ({url, nav, comp, children}) => { - const header = () => { - if (url.includes('/docs/quickstart')) return 'Quickstart'; - if (url.includes('/docs/user')) return 'User Guide'; - if (url.includes('/docs/dev')) return 'Developer Guide'; - if (url.includes('/docs/project')) return 'Project Guide'; - return 'Documentation'; - }; - return ( -
    -
    - -
    -

    {header()}

    - {children} -
    -
    -
    - ) -} \ No newline at end of file diff --git a/web/_vendor/codemirror/Makefile b/web/_vendor/codemirror/Makefile deleted file mode 100644 index 3292cc2..0000000 --- a/web/_vendor/codemirror/Makefile +++ /dev/null @@ -1,3 +0,0 @@ - -bundle: - npm install && esbuild codemirror.ts --bundle --outfile=../../static/vnd/codemirror-6.0.1.min.js --format=esm --minify \ No newline at end of file diff --git a/web/_vendor/codemirror/codemirror.ts b/web/_vendor/codemirror/codemirror.ts deleted file mode 100644 index c7b25c2..0000000 --- a/web/_vendor/codemirror/codemirror.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { EditorState } from "@codemirror/state" -import { placeholder as placeholderPlugin, EditorView, ViewUpdate } from "@codemirror/view" -import { markdown, markdownLanguage } from "@codemirror/lang-markdown" -import { javascript } from "@codemirror/lang-javascript" -import { defaultHighlightStyle, syntaxHighlighting} from "@codemirror/language" -import { classHighlighter } from "@lezer/highlight" - -const proxyMerge = (...objects: any[]) => { - const extra = objects.slice(1); - - return new Proxy(objects[0], { - get: (target, name) => { - const obj = extra.find(o => o[name]); - return obj - ? obj[name] - : target[name]; - }, - }); -}; - -export class Editor { - view: EditorView; - parent: HTMLElement; - oninput?: Function; - onblur?: Function; - onfocus?: Function; - onkeydown?: Function; - - constructor(parent: HTMLElement, value: string, placeholder?: string) { - this.parent = parent; - this.view = new EditorView({ - parent: parent, - state: EditorState.create({ - doc: value, - extensions: [ - EditorView.lineWrapping, - placeholderPlugin(placeholder), - syntaxHighlighting(defaultHighlightStyle), - syntaxHighlighting(classHighlighter), - markdown({ - defaultCodeLanguage: javascript(), - base: markdownLanguage - }), - EditorView.updateListener.of((v: ViewUpdate) => { - if (v.docChanged && this.oninput) { - this.oninput(proxyMerge(new CustomEvent("UpdateEvent"), {target: this})); - if (m) m.redraw(); - } - }), - EditorView.domEventHandlers({ - // input: (event, view) => { - // if (this.oninput) { - // this.oninput(proxyMerge(event, {target: this})); - // if (m) m.redraw(); - // } - // }, - blur: (event, view) => { - if (this.onblur) { - this.onblur(proxyMerge(event, {target: this})); - if (m) m.redraw(); - } - }, - focus: (event, view) => { - if (this.onfocus) { - this.onfocus(proxyMerge(event, {target: this})); - if (m) m.redraw(); - } - }, - keydown: (event, view) => { - if (this.onkeydown) { - let defaultPrevented = false; - this.onkeydown(proxyMerge(event, { - target: this, - preventDefault: () => defaultPrevented = true, - stopPropagation: () => null, - })); - if (m) m.redraw(); - return defaultPrevented; - } - }, - }), - ] - }) - }); - } - - get value(): string { - return this.view.state.doc.toString(); - } - - set value(v: string) { - if (v !== this.value) { - const update = this.view.state.update({changes: {from: 0, to: this.view.state.doc.length, insert: v}}); - this.view.update([update]); - } - } - - get selectionStart(): number { - return this.view.state.selection.main.anchor; - } - - get selectionEnd(): number { - return this.view.state.selection.main.head; - } - - get coordsAtCursor(): any { - return this.view.coordsAtPos(this.view.state.selection.main.head); - } - - focus() { - this.view.focus(); - } - - blur() { - this.view.contentDOM.blur(); - } - - setSelectionRange(start: number, end: number) { - this.view.dispatch({ - selection: { - anchor: start, - head: end, - }, - }); - } - - getBoundingClientRect(): any { - return this.parent.getBoundingClientRect() - } -} diff --git a/web/_vendor/codemirror/package.json b/web/_vendor/codemirror/package.json deleted file mode 100644 index 603b0b1..0000000 --- a/web/_vendor/codemirror/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dependencies": { - "@codemirror/lang-javascript": "^6.1.4", - "@codemirror/lang-markdown": "^6.1.0", - "@codemirror/language": "^6.6.0", - "@codemirror/state": "^6.2.0", - "@codemirror/view": "^6.9.2", - "codemirror": "^6.0.1" - } -} \ No newline at end of file diff --git a/web/_vendor/minisearch/Makefile b/web/_vendor/minisearch/Makefile deleted file mode 100644 index 94a96f1..0000000 --- a/web/_vendor/minisearch/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -VERSION := 6.0.1 - -download: - curl -s https://cdn.jsdelivr.net/npm/minisearch@$(VERSION)/dist/umd/index.min.js | grep -v "//# sourceMappingURL=" > ../../static/vnd/minisearch-$(VERSION).min.js \ No newline at end of file diff --git a/web/_vendor/mithril/Makefile b/web/_vendor/mithril/Makefile deleted file mode 100644 index 980b337..0000000 --- a/web/_vendor/mithril/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -VERSION := 2.0.3 - -download: - curl -s https://cdnjs.cloudflare.com/ajax/libs/mithril/$(VERSION)/mithril.min.js > ../../static/vnd/mithril-$(VERSION).min.js \ No newline at end of file diff --git a/web/_vendor/octokit/Makefile b/web/_vendor/octokit/Makefile deleted file mode 100644 index 8637a46..0000000 --- a/web/_vendor/octokit/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -VERSION := 18.12.0 - -bundle: - npm install @octokit/rest@$(VERSION) && esbuild octokit.js --bundle --outfile=../../static/vnd/octokit-$(VERSION).min.js --format=esm --minify \ No newline at end of file diff --git a/web/_vendor/octokit/octokit.js b/web/_vendor/octokit/octokit.js deleted file mode 100644 index d3c0e81..0000000 --- a/web/_vendor/octokit/octokit.js +++ /dev/null @@ -1 +0,0 @@ -export { Octokit } from "@octokit/rest"; \ No newline at end of file diff --git a/web/blog/index.tsx b/web/blog/index.tsx deleted file mode 100644 index 7465d0b..0000000 --- a/web/blog/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const title = "Treehouse"; -export default (data) => ( - - - - - - - -) diff --git a/web/blog/influences.md b/web/blog/influences.md deleted file mode 100644 index d8ff711..0000000 --- a/web/blog/influences.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Treehouse Influences" -author: "Jeff Lindsay" -date: "2023-04-20" ---- - -Over the past few months, we've built a frontend framework for an elegant, quality outliner that's open source, extensible, and gives you control of your data. I’d like to share some of the design influences for the Treehouse frontend, which should give a sense of the unique direction Treehouse is going from here. - -The biggest influences for Treehouse are Tana, Notion, and Obsidian. These three represent the state of the art of personal and collaborative information management, sometimes simplified as note-taking tools. However, "note-taking tools" sells them short as they go beyond note-taking and information management. For lack of a better descriptor, many consider them [tools for thought](https://www.forthought.tools/). - -## From Notes to Tools for Thought - -For most, note-taking brings to mind simple apps like Apple Notes and Google Keep, or even just a text file editor. These work well for people because they're already there and focus on quick and easy plain text capture. We could call this casual note-taking. - -Back in the 2000s, hosted and self-hosted wikis became popular for easy, collaborative web publishing and knowledge management. Like Wikipedia, they could be used to build out hyperlinked knowledge repositories. Many wiki-based tools focused on their use as personal notebooks, one of the most influential examples being [TiddlyWiki](https://tiddlywiki.com/). The simple versatility of the wiki laid the groundwork for what we call "tools for thought" today. - -![TiddlyWiki screenshot](https://treehouse.sh/photos/blog/tiddlywiki.png) - -When [Notion](https://www.notion.so/) appeared in the mid-2010s, it built on the idea of the wiki and introduced structured data management with flexible views that *effectively gave you integrated, customizable versions of other productivity tools*. Notion, Airtable, and others helped bring in the age of no-code and low-code tools, allowing knowledge workers and entrepreneurs to build their own "apps" or solutions to problems without traditionally building software. Notion brought it all together in a simple, user-friendly experience based around the core idea of wiki-like information management. - -![Notion screenshot](https://treehouse.sh/photos/blog/notion.jpeg) - -Meanwhile, a separate paradigm of note-taking tools emerged, focusing on the nested, tree-like structure of the outline. Perhaps inspired by tools like [OmniOutliner](https://www.omnigroup.com/omnioutliner/) and [Org Mode](https://orgmode.org/) for Emacs of the 2000s, [Workflowy](https://workflowy.com/) appeared in 2010 as a no-frills web-based outliner. - -![Workflowy screenshot](https://treehouse.sh/photos/blog/workflowy.png) - -[Obsidian](https://obsidian.md/) arrived in 2020 and is a local app focusing on Markdown files stored on your filesystem. Obsidian has a large plugin ecosystem giving it a wide breadth of features, but it’s especially appealing to those that want to own their data. If you strip away the plugins, Obsidian is a pretty simple hyperlinked Markdown editor. - -![Obsidian screenshot](https://treehouse.sh/photos/blog/obsidian.png) - -Most recently, a tool in early access called [Tana](https://tana.inc/) caught my attention. Their key innovation is taking the linked outline model of Workflowy and introducing schemas for nodes, making them into structured data. This gives Tana the embedded database functionality of Notion and Airtable, a step towards bringing the two paradigms of note-taking software together towards powerful, malleable tools for thought. - -![Tana screenshot](https://treehouse.sh/photos/blog/tana.webp) - -## How Treehouse Fits In - -By now there's no shortage of options in this space, both as SaaS and open source. Take a look at this growing [encyclopedia of note-taking tools](https://noteapps.info/). Like Notion and Tana, many of the apps listed are much more than note-taking tools. Some lean into the framing of "collaborative documents", and some are just categorized more generally as "productivity tools". Tana goes so far as to say "the everything OS". - -Note-taking is just the beginning. It's a tangible gateway for something more powerful inherent to computing. Ever since Engelbart's [mother of all demos](https://en.wikipedia.org/wiki/The_Mother_of_All_Demos), the computing revolution seems to start with powerful tools for thought, which are, at minimum, good note-taking tools. - -![Engelbart using NLS](https://treehouse.sh/photos/blog/nls.jpeg) - -Treehouse is a frontend and starter kit for anybody else that wants to explore this space with us. We will release a standalone product based on it soon, but most of the user-facing development will be done in the open source Treehouse project. - -![Treehouse screenshot](https://treehouse.sh/photos/hero-image.png) - -Today with Treehouse you can build your own Workflowy equivalent, but soon it will become more comparable to Tana and Notion with the open extensibility of Obsidian. That alone is pretty exciting to have in a minimal open source project, but I can't wait to show you what will come next. - -## Coming Soon - -In the next post, I'll start getting technical and share details on the Treehouse project stack and architecture. If you can't wait, we do have [documentation](https://treehouse.sh/docs/dev/) for you to check out. - -Thanks for reading, and a big thanks to my [sponsors](https://github.com/sponsors/progrium) for supporting this kind of open source work. Share your thoughts and favorite note-taking tools in the [discussion thread](https://github.com/treehousedev/treehouse/discussions/95) for this post. \ No newline at end of file diff --git a/web/blog/v0-1-0.md b/web/blog/v0-1-0.md deleted file mode 100644 index dd433b3..0000000 --- a/web/blog/v0-1-0.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.1.0" -author: "Jeff Lindsay" -date: "2023-03-03" ---- -## It begins... - -This first [development release](https://github.com/treehousedev/treehouse/releases/tag/0.1.0) captures core functionality of the frontend to the point of being internally usable in the demo deployment. For developers, the project source layout, architecture, and backend API is defined in broad strokes in the right direction but is by no means stable. - -[![Watch Demo](http://i3.ytimg.com/vi/wtJCYlR2_ys/hqdefault.jpg)](https://www.youtube.com/watch?v=wtJCYlR2_ys) -[Demo video](https://www.youtube.com/watch?v=wtJCYlR2_ys) - -## Initial Functionality -* Basic outliner -* Commands, menus, keybindings -* Workspace model -* Navigation tree -* Command palette -* Multi-view panels -* Calendar/today notes -* Quick add notes -* Full-text search using [Minisearch](https://github.com/lucaong/minisearch) -* Localstorage backend -* GitHub backend - -## Enhancements -* Implemented OS detection for registering different keybindings [#1](https://github.com/treehousedev/treehouse/issues/1) -* Don't collapse new nested node [#3](https://github.com/treehousedev/treehouse/issues/3) -* Hitting tab on plus sign should create an indented node [#4](https://github.com/treehousedev/treehouse/issues/4) -* Improve cursor location when manually deleting a node [#5](https://github.com/treehousedev/treehouse/issues/5) -* Improve backspace behavior when cursor is at the beginning of a node [#6](https://github.com/treehousedev/treehouse/issues/6) -* Add keyboard shortcut to zoom [#8](https://github.com/treehousedev/treehouse/issues/8) -* Allow editing title node [#9](https://github.com/treehousedev/treehouse/issues/9) -* Hide root node [#10](https://github.com/treehousedev/treehouse/issues/10) -* Clean up sidebar text [#11](https://github.com/treehousedev/treehouse/issues/11) -* Remember node expansion state [#19](https://github.com/treehousedev/treehouse/issues/19) -* Typography updates [#21](https://github.com/treehousedev/treehouse/issues/21) - -## Bugfixes -* Enable text wrapping for nodes [#2](https://github.com/treehousedev/treehouse/issues/2) -* Highlight to delete for nested node gives a TypeError [#12](https://github.com/treehousedev/treehouse/issues/12) -* Hitting return mid-node copies contents instead of moving [#17](https://github.com/treehousedev/treehouse/issues/17) -* Quick Add formatting issue [#18](https://github.com/treehousedev/treehouse/issues/18) -* Error logging in with GitHub [#22](https://github.com/treehousedev/treehouse/issues/22) -* Node content disappears when it loses focus [#24](https://github.com/treehousedev/treehouse/issues/24) -* Display issue w/ icons overlapping search bar [#26](https://github.com/treehousedev/treehouse/issues/26) \ No newline at end of file diff --git a/web/blog/v0-2-0.md b/web/blog/v0-2-0.md deleted file mode 100644 index df4ea91..0000000 --- a/web/blog/v0-2-0.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.2.0" -author: "Jeff Lindsay" -date: "2023-03-27" ---- -## New design system, GitHub session locking, and documentation - -[This release](https://github.com/treehousedev/treehouse/releases/tag/0.2.0) is a refinement of our initial release, fixing a number of bugs and adding interaction improvements. The look and feel of the UI was also updated with the start of a new CSS design system based on custom properties. Session locking was added for the live demo and GitHub backend so multiple devices/browsers/tabs don't clobber changes of each other. Documentation also got an upgrade with the start of a [full guide](https://treehouse.sh/docs/quickstart/) on the website. - -[![Watch Demo](http://i3.ytimg.com/vi/wj4uai9yUJ0/hqdefault.jpg)](https://www.youtube.com/watch?v=wj4uai9yUJ0) -[Demo video](https://www.youtube.com/watch?v=wj4uai9yUJ0) - -## Bugfixes -* Autosave error when switching between devices [#32](https://github.com/treehousedev/treehouse/issues/32) -* Deleting a node doesn't delete child nodes [#25](https://github.com/treehousedev/treehouse/issues/25) -* Hitting return should produce a new node directly below the above node [#29](https://github.com/treehousedev/treehouse/issues/29) -* TypeError exception when switching back from new panel [#65](https://github.com/treehousedev/treehouse/issues/65) -* Support emojis [#52](https://github.com/treehousedev/treehouse/issues/52) - -## Enhancements and Chores -* Typography and layout improvements to application [#37](https://github.com/treehousedev/treehouse/issues/37) -* Add keyboard shortcut to move nodes up or down [#28](https://github.com/treehousedev/treehouse/issues/28) -* Prevent backspace to delete if there are child nodes [#15](https://github.com/treehousedev/treehouse/issues/15) -* Allow renaming the workspace [#23](https://github.com/treehousedev/treehouse/issues/23) -* Clicking outside of the search bar/command palette should close it [#48](https://github.com/treehousedev/treehouse/issues/48) -* Workspace/workbench separation [#39](https://github.com/treehousedev/treehouse/issues/39) -* Add API docs [#34](https://github.com/treehousedev/treehouse/issues/34) -* Set up versioned library bundle [#41](https://github.com/treehousedev/treehouse/issues/41) -* Allow backspace to delete an empty child node [#53](https://github.com/treehousedev/treehouse/issues/53) -* Save last location on reloads [#54](https://github.com/treehousedev/treehouse/issues/54) -* Hide buttons to move a panel up/down [#49](https://github.com/treehousedev/treehouse/issues/49) - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/82) \ No newline at end of file diff --git a/web/blog/v0-3-0.md b/web/blog/v0-3-0.md deleted file mode 100644 index d169a58..0000000 --- a/web/blog/v0-3-0.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.3.0" -author: "Jeff Lindsay" -date: "2023-05-17" ---- -## New Node Types: Fields, Live Search, and References - -This release we added some exciting new features: Fields, Live Search, and References. These new node types represent our first step into becoming much more than a simple outliner. Quality of live improvements include making the node menu easier to click, initial work towards a mobile view, and improving interactivity of the command palette. We also cleaned up all our UI elements to match our design system, allowing for better custom CSS support. - -*Update 7/10/2023: Live Search is now called Smart Nodes* - -[![Watch Demo](http://i3.ytimg.com/vi/PjWibMkKBOE/hqdefault.jpg)](https://www.youtube.com/watch?v=PjWibMkKBOE) -[Demo video](https://www.youtube.com/watch?v=PjWibMkKBOE) - -### New Feature: Fields - -A field is a node that can store a key-value pair. This introduces structured data to your nodes, letting you create nodes as data records. You can also search for nodes by field. This initial pass supports text values, but we'll soon provide more value types. - -To turn a node into a field: -1. Indent underneath the node you want to contain the field, and type the field name -2. Command/Control + K to open the command palette, and choose "Create Field" -3. Add your field value in the value section - -### New Feature: Live Search - -Live Search allows you to create search nodes where the children are auto-updating search results. Simply type a keyword, or use the format "fieldname:value" to filter by fields. The Live Search nodes will update automatically as your workspace content changes. This is a powerful way to view your data in new configurations. - -To create a Live Search: -1. Create a new node where you want your search node, and type your search value -2. Command/Control + K to open the command palette, and choose "Create Search Node" - -Tips: Search terms are case-insensitive, and you can filter on multiple fields (uses AND, not OR) like so: "fieldname:value fieldname:value". - -### New Feature: References - -References are nodes that refer to another node and its children inline. They're sort of like symlinks on the filesystem. This lets you have a node exist in multiple places at once. References are differentiated from normal nodes with a dashed outline around their outline bullet. Deleting a reference node does not delete the node it points to. You may notice that Live Search results are reference nodes! - -To create a reference node: -1. Select the node you want to make a reference to -2. Command/Control + K to open the command palette, and choose "Create Reference" - -## Bugfixes -* Command palette now uses normalized title [#117](https://github.com/treehousedev/treehouse/issues/117) -* Command palette supports mouse interactivity [#102](https://github.com/treehousedev/treehouse/issues/102) -* Components no longer fail with bundled library [#119](https://github.com/treehousedev/treehouse/issues/119) -* Ensure Node IDs are unique [#104](https://github.com/treehousedev/treehouse/issues/104) -* Reference sub-nodes now include fields [#121](https://github.com/treehousedev/treehouse/issues/121) -* Search nodes no longer production workspace [#118](https://github.com/treehousedev/treehouse/issues/118) -* Prevent indenting/outdenting a field node [#116](https://github.com/treehousedev/treehouse/issues/116) - -## Enhancements and Chores -* New feature: Field nodes -* New feature: Live Search nodes -* New feature: Reference nodes -* Clear out non-existent node keys in workspace document under expanded [#120](https://github.com/treehousedev/treehouse/issues/120) -* Don't show bullet for empty nodes [#31](https://github.com/treehousedev/treehouse/issues/31) -* Allow hovering over menu area to show menu [#76](https://github.com/treehousedev/treehouse/issues/76) -* Clean up styles on search bar, palette, quick add, menu, buttons [#81](https://github.com/treehousedev/treehouse/issues/81), [#85](https://github.com/treehousedev/treehouse/issues/85), [#96](https://github.com/treehousedev/treehouse/issues/96), [#108](https://github.com/treehousedev/treehouse/issues/108), [#110](https://github.com/treehousedev/treehouse/issues/110) -* Show placeholder text for empty field key/value inputs [#135](https://github.com/treehousedev/treehouse/issues/135) -* Implement initial version of mobile web app [#103](https://github.com/treehousedev/treehouse/issues/103) - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/156) \ No newline at end of file diff --git a/web/blog/v0-4-0.md b/web/blog/v0-4-0.md deleted file mode 100644 index 543c058..0000000 --- a/web/blog/v0-4-0.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.4.0" -author: "Jeff Lindsay" -date: "2023-06-19" ---- -## Mobile support and CSS themes - -In this release we focused on making Treehouse more powerful and enjoyable to use from more places. Editing text on mobile is always a tricky prospect, but we've taken our first step to being mobile-friendly with customized navigation and easier touch targets. - -White background a bit too bright? We've added several built-in themes (including sepia and dark mode) *and* we've made it super easy to [create your own CSS themes](https://treehouse.sh/docs/user/#css-theming) using our component variables. - -Otherwise, lots of quality of life improvements as usual, especially for fields and Smart Nodes. - -[![Watch Demo](http://i3.ytimg.com/vi/byZnYzzrP7E/hqdefault.jpg)](https://www.youtube.com/watch?v=byZnYzzrP7E) -[Demo video](https://www.youtube.com/watch?v=byZnYzzrP7E) - -## Enhancements and Chores -* Support setting a theme in the UI, and custom CSS themes [#168](https://github.com/treehousedev/treehouse/issues/168), [#122](https://github.com/treehousedev/treehouse/issues/122) -* Mobile improvements: new navigation and improved interactivity [#114](https://github.com/treehousedev/treehouse/issues/114), [#115](https://github.com/treehousedev/treehouse/issues/115) -* Allow partial phrase match for command palette search [#148](https://github.com/treehousedev/treehouse/issues/148) -* UX improvements for search nodes [#134](https://github.com/treehousedev/treehouse/issues/134) -* Only show new-node plus sign if a node is expanded and has no children [#149](https://github.com/treehousedev/treehouse/issues/149) -* Add keyboard shortcuts to command palette [#129](https://github.com/treehousedev/treehouse/issues/129) -* Prevent turning nodes with children into search nodes [#161](https://github.com/treehousedev/treehouse/issues/161) -* Support multiple workflows for creating fields [#152](https://github.com/treehousedev/treehouse/issues/152) -* Show visual indicator when search node is collapsed [#150](https://github.com/treehousedev/treehouse/issues/150) -* Consolidate search logic [#159](https://github.com/treehousedev/treehouse/issues/159) -* Consolidate overlays with dialog element [#158](https://github.com/treehousedev/treehouse/issues/158) -* Support multiple workflows for creating fields[#152](https://github.com/treehousedev/treehouse/issues/152) - -## Bugfixes -* Backspace behavior on fields should be predictable [#130](https://github.com/treehousedev/treehouse/issues/130) -* Don't collapse above node when outdenting [#131](https://github.com/treehousedev/treehouse/issues/131) -* Backspacing from beginning of node deletes children [#151](https://github.com/treehousedev/treehouse/issues/151) -* Prevent edit interactions on search node [#105](https://github.com/treehousedev/treehouse/issues/105) - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/215) \ No newline at end of file diff --git a/web/blog/v0-5-0.md b/web/blog/v0-5-0.md deleted file mode 100644 index 109b0ad..0000000 --- a/web/blog/v0-5-0.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.5.0" -author: "Jeff Lindsay" -date: "2023-07-06" ---- -## Tags, templates, and table view - -Treehouse now supports tags and templates! For example, you can now create a template node with children and fields, name it "book", then any node tagged with #book will automatically get those fields and children. To turn a node into a template, select "Make Template" from the Command Palette. You can also tag nodes without an equivalent template. Tagging is done by simply adding hashtags to a node's text. - -You can search for tagged nodes in the search bar using the hashtag notation (#book), however there is a known issue preventing you from searching for nodes by tag in Smart Nodes. This will be resolved in the next release, and in `main` much sooner. - -You can now take a list of nodes with fields and turn them into an easy-to-scan table. Simply go to the parent node of the rows, open the Command Palette with Command+K, and choose "View as Table". We'll be adding more features to the table view in coming releases. - -Double-quoted search terms now allow spaces and search results are a little tighter now. Last but not least we've added cut, copy, and paste commands for nodes, making it easier to duplicate and move nodes around. - -[![Watch Demo](http://i3.ytimg.com/vi/qzsGuO6sfC0/hqdefault.jpg)](https://www.youtube.com/watch?v=qzsGuO6sfC0) -[Demo video](https://www.youtube.com/watch?v=qzsGuO6sfC0) - -## Enhancements and Chores -* Tags and templates [#206](https://github.com/treehousedev/treehouse/issues/206) -* Cut/copy/paste nodes [#194](https://github.com/treehousedev/treehouse/issues/194) -* Table view for nodes [#106](https://github.com/treehousedev/treehouse/issues/106), [#120](https://github.com/treehousedev/treehouse/issues/210) -* Support field search terms containing spaces [#153](https://github.com/treehousedev/treehouse/issues/153) -* Link to documentation from within Treehouse [#175](https://github.com/treehousedev/treehouse/issues/175) - -## Bugfixes -* Search bug when capitalizing search term [#217](https://github.com/treehousedev/treehouse/issues/217) -* Search is behaving too broadly when matching [#197](https://github.com/treehousedev/treehouse/issues/197) -* Support multiple concurrent dialogs [#203](https://github.com/treehousedev/treehouse/issues/203) -* Support editing "Calendar" node without Today making a new one [#232](https://github.com/treehousedev/treehouse/issues/232) - - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/244) \ No newline at end of file diff --git a/web/blog/v0-6-0.md b/web/blog/v0-6-0.md deleted file mode 100644 index f60fec3..0000000 --- a/web/blog/v0-6-0.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.6.0" -author: "Jeff Lindsay" -date: "2023-07-20" ---- -## New Document and Tab Views -In this release we're introducing two new views: Document view, and Tab view. Views are a powerful idea in Treehouse that can either be used to create embedded mini-apps or act as building blocks for you to make your own. These new views are an example of each. - -Document view gives you a more conventional note-taking experience. In Document view each child node is rendered as a paragraph in the center of the panel. Documents support simple Markdown formatting like bold, italic, strikethrough, and ordered and unordered lists. Turn a node into a document by choosing "Make Document" from the Command Palette. Unlike other views, changing to Document view is a one-way action and nodes can't be converted back to other views. - -Tab view renders each child node as a tab. Their children (grandchild nodes) are shown beneath when the tab is selected. These nodes will retain their view, so you can combine Tab view with others to create a more complex larger view of your data. In general, Tab view is useful for saving vertical space across several categories. Turn nodes into tabs by selecting the parent node and choose "View as Tabs" from the Command Palette. Since Tab view is read-only, you can modify tabs by switching back to List view. - -We've also renamed Live Search Nodes to Smart Nodes, which can now be named separate from their query. This also resolves an issue where previously you couldn't use them for tag searches. - -[![Watch Demo](http://i3.ytimg.com/vi/-CsRlyJx2cU/hqdefault.jpg)](https://www.youtube.com/watch?v=-CsRlyJx2cU) -[Demo video](https://www.youtube.com/watch?v=-CsRlyJx2cU) - -## Enhancements and Chores -* Tabs view [#126](https://github.com/treehousedev/treehouse/issues/126) -* Document view [#246](https://github.com/treehousedev/treehouse/issues/246) -* Support setting a name for a Smart Node [#235](https://github.com/treehousedev/treehouse/issues/235) -* Clean up command palette commands [#242](https://github.com/treehousedev/treehouse/issues/242) -* Rename live search/search node to Smart Node [#248](https://github.com/treehousedev/treehouse/issues/248) - -## Bugfixes -* Range error when turning a tag into a search node [#236](https://github.com/treehousedev/treehouse/issues/236) -* Don't allow outdenting a top-level node [#234](https://github.com/treehousedev/treehouse/issues/234) -* Can't interact with an empty node in Quick Add (mobile) [#204](https://github.com/treehousedev/treehouse/issues/204) -* Capital letters in GitHub username prevent login [#245](https://github.com/treehousedev/treehouse/issues/245) -* Can't select a theme in Chrome [#266](https://github.com/treehousedev/treehouse/issues/266) - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/273) \ No newline at end of file diff --git a/web/blog/v0-7-0.md b/web/blog/v0-7-0.md deleted file mode 100644 index 82e81e4..0000000 --- a/web/blog/v0-7-0.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Release 0.7.0" -author: "Jeff Lindsay" -date: "2023-08-18" ---- -## Descriptions, better paste, shortcut drawer - -We're excited to be adding a handful of minor enhancements in this release. The first is node descriptions, which allow you to add small text below your node content, as part of the same node. Use it to add context or any kind of secondary information. To use, select your node, open the Command Palette with Command/Control + K, and select "Add Description". - -We've made several improvements related to cut/copy/pasting text. Now if you paste text containing new lines, we'll convert each newline to a separate node. We've also fixed some bugs and UX flows. - -Lastly, check out the keyboard shortcut drawer! We want it to be super easy to learn and use keyboard shortcuts and hope this helps. - - -## Enhancements and Chores -* Node descriptions [#295](https://github.com/treehousedev/treehouse/issues/295) -* Support searching for field keys containing spaces [#277](https://github.com/treehousedev/treehouse/issues/277) -* Newlines should be translated as new nodes when pasting from outside sources [#250](https://github.com/treehousedev/treehouse/issues/250) -* When multiple panels, all panels should be equal width [#259](https://github.com/treehousedev/treehouse/issues/259) -* Keyboard shortcut reference drawer [#264](https://github.com/treehousedev/treehouse/issues/264) -* Sublime theme - adjust pink to improve contrast [#284](https://github.com/treehousedev/treehouse/issues/284) -* Use real italics/bold font variant for Codemirror (treehouse) [#272](https://github.com/treehousedev/treehouse/issues/272) - -## Bugs -* Can't paste into (+) node [#275](https://github.com/treehousedev/treehouse/issues/275) -* Prevent multiple checkboxes [#299](https://github.com/treehousedev/treehouse/issues/299) -* Cut node pastes when you select cut, not paste [#293](https://github.com/treehousedev/treehouse/issues/293) -* "Back" link should not require two clicks [#257](https://github.com/treehousedev/treehouse/issues/257) -* Cutting the node should remove it from view [#274](https://github.com/treehousedev/treehouse/issues/274) -* TypeError when smart node returns results [#269](https://github.com/treehousedev/treehouse/issues/269) -* TypeError related to command palette + tags [#258](https://github.com/treehousedev/treehouse/issues/258) - ---- -[*Discuss on GitHub*](https://github.com/treehousedev/treehouse/discussions/302) \ No newline at end of file diff --git a/web/blog/welcome.md b/web/blog/welcome.md deleted file mode 100644 index 8dce442..0000000 --- a/web/blog/welcome.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: layouts/blog.tsx -title: "Welcome to Treehouse" -author: "Jeff Lindsay" -date: "2023-02-23" ---- -I'm excited to announce the start of a new major project that I'll be sharing the journey of on this fancy new blog. The project is called [Treehouse](https://treehouse.sh/), which is starting as an open source note-taking frontend and will evolve into something much more. - -![Treehouse screenshot](https://treehouse.sh/photos/hero-image.png) - -We're creating a simple, hackable kernel of a note-taking tool as a web frontend that can be extended and customized by developers. Then we'll use that frontend to build a new kind of note-taking product. Today we have an early preview of our first minimum viable prototype of the frontend, and we'll be finishing up this release in the open on GitHub. - -The [open source project](https://github.com/treehousedev/treehouse) is a functional app frontend that you can deploy and back in various ways. For developers, this "bring your own backend" approach makes Treehouse a great starting point to build your own note-taking tool, just as we intend to do. We also have pre-made backends for those who want to use it with little-to-no programming. Either way, you'll always own your data, and now the system presenting it to you as well. - -In this post, I'm going to talk about our minimum viable prototype and the approach we're taking with Treehouse as an open source project. - -## Out of the Box - -While inspired by powerful tools like [Notion](https://www.notion.so/), [Tana](https://tana.inc/), and [Obsidian](https://obsidian.md/), we wanted to boil our initial goal down to the essentials while still being usable enough to use ourselves. As we continue to plan our prototype release milestone, we'd love to hear what you think is essential for you to have in a tool and platform like this, but here is what to expect as a baseline. - -### Outliner with Markdown Pages - -At the heart of Treehouse is a graph-like system I've been developing called Manifold. This will play a big part in the long-term extensibility of Treehouse, which I'll talk more about in the future. For now, this maps very cleanly to the outliner model popularized by Workflowy, Tana, and others. - -However, I'm also a fan of Notion-style pages and Obsidian's commitment to working with plain Markdown files. The Manifold system allows us to make certain nodes into Markdown pages. This hybrid model gets us the best of both worlds with room for even more possibilities later on. - -### Quick Add and Daily Notes - -Being able to quickly get thoughts and information into the system has been a key aspect of every good note-taking system. This can be exposed a number of ways from browser extensions to desktop shortcuts, but what makes any "quick add" functionality quick is not having to think about where it will be organized. - -Luckily, "daily notes" has become such a common pattern that many recent tools have managed daily notes built-in. This typical gives you a "today" note that is automatically organized into a calendar-like structure. This conveniently becomes the perfect destination for "quick add" notes. - -### Full-text Search - -Whether you are an obsessive organizer or are too busy to take the time, solid full-text search is basically a necessity. It's how we quickly get to our data. - -Out of the box we have a full-text search that doesn't require a backend. However, our pluggable backend will allow you to power search in a number of ways, and also opens up the ability to search into external systems that are important to you. - -### Built-in Backends - -"Bring your own backend" is a critical part of the design of this project, but Treehouse also needs to be usable by people who prefer not to *build* their own backend. Our prototype release is planned to include a handful of built-in backends to get started with. - -Our preview demo is using a localStorage backend, which works for development and special use cases. Of course, we will have a simple local filesystem backend for desktop scenarios, similar to Obsidian. - -However, we're most excited for the GitHub backend. This functions similarly to the local filesystem backend, but it would be versioned in a central repository on your GitHub account and be accessible on any online platform. - -Backends will not only let you extend storage, search, and authentication, but other aspects in the future. - -## Project Overview - -The Treehouse codebase and open source project is as much the product as its features. The entire user and developer experience is being designed around simplicity and human ergonomics. - -The Treehouse frontend is built with web technologies intended for use across web, mobile, and desktop platforms. The project is written in TypeScript using [Deno](https://deno.land/) as its JavaScript toolchain. - -The JavaScript ecosystem is a mess, but Deno provides a forward-looking, self-contained toolchain that's easy to love. We actually have a zero Node.js policy and avoid NPM modules as much as possible. - -With minimal dependencies and an ongoing effort to keep the codebase small, it will always be easy to understand the entire Treehouse system. This will be further supported with a focus on documentation, both API docs and guides. - -We're using a permissive open source license, allowing you to use or change our code as you see fit. Contribution and participation is welcome, but so is simply consuming downstream. - -The project is actively being developed but we keep a [preview demo](https://treehouse.sh/demo/) running off the main branch that should always be in working order. - -## Coming Soon - -I'm excited to give you a deeper look at the project stack in a future post. For now, keep an eye out for the next post digging into the influences for Treehouse. We'll also try to answer "why another note-taking tool", and tease some long-term ideas for the future. - -Lastly, I want to mention the project is being developed in the open, not just with the code on GitHub, but most of my work is streamed and archived on [Twitch](https://www.twitch.tv/progrium). Feel free to come and co-work with me. - -Thanks for reading, and a big thanks to my [sponsors](https://github.com/sponsors/progrium/) for supporting this kind of open source work. \ No newline at end of file diff --git a/web/docs/dev/1-overview.md b/web/docs/dev/1-overview.md deleted file mode 100644 index 044c642..0000000 --- a/web/docs/dev/1-overview.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Overview ---- -## Overview - -Treehouse is a frontend written in TypeScript made to be rendered in a browser or webview for building note-taking and information management tools. It is a "thick" frontend in that it holds most application state in-memory and executes user triggered commands to mutate that state. Persistence of that state, and a few other features, are expected to be provided by a "backend", which the frontend code interacts with via a backend adapter. -Even without a backend adapter, the Treehouse frontend is still a fully functional application packaged as a JavaScript library that can be loaded onto any HTML page. - -### Architecture - -The library exposes a `setup()` function that: - -1. Takes a DOM document and backend adapter object -1. Sets up a central controller for the UI called Workbench -1. Loads a Workspace using the backend adapter -1. Registers built-in commands and keybindings -1. Uses [Mithril.js](https://mithril.js.org/) to mount a top level Mithril component to the document - -The UI is represented by a class called Workbench. This class orchestrates and provides most of the API for the rest of the system. The UI is broken down into Mithril components that implement the views for each part of the UI, pulling state from Workbench and connecting interactions to registered commands that represent all user actions. The Workbench and commands manipulate a Workspace, which represents the main data model for the system. - -### Stack - -To avoid the complexity and dependency hell, Treehouse avoids most common JavaScript tooling such as Node.js and NPM. Instead, Treehouse uses [Deno](https://deno.land/) as a toolchain and otherwise avoids dependencies as much as possible. The other main dependency we have is [Mithril.js](https://mithril.js.org/), which was chosen for its simplicity and lack of further dependencies. - -That said, there are other dependencies beyond this core stack which are chosen very deliberately. Out of the box search indexing depends on [MiniSearch](https://lucaong.github.io/minisearch/), and our most complex (but unavoidable) dependency is [CodeMirror](https://codemirror.net/). We're very conscious of project dependencies, including development and toolchain dependencies. \ No newline at end of file diff --git a/web/docs/dev/2-data-model.md b/web/docs/dev/2-data-model.md deleted file mode 100644 index 200d715..0000000 --- a/web/docs/dev/2-data-model.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Data Model ---- -## Data Model - -The "document" for Treehouse is the Workspace, which is mostly a container for nodes. These nodes are based on a versatile and extensible API and data model called Manifold. In short, nodes: - -* have a unique ID -* have a text name -* can be children to other nodes -* can have key-value attributes -* can have components - -Most importantly, nodes build a tree-like structure where each node can be extended with components. Components extend the state and functionality of a node. For example, a checkbox component can be added to a node. This gives it a state (checked or not) and allows the UI to render it differently (add a checkbox). \ No newline at end of file diff --git a/web/docs/dev/3-user-actions.md b/web/docs/dev/3-user-actions.md deleted file mode 100644 index fae3a59..0000000 --- a/web/docs/dev/3-user-actions.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: layouts/docs.tsx -title: User Actions ---- -## User Actions - -All user performable actions are modeled as commands and registered with a command system. Commands are functions with some extra metadata, like a user displayable name and system identifier. They can be called throughout the system by their system identifier, such as when a user clicks a something. - -Menus are often defined upfront, usually by commands. Commands can have keybindings registered for them for keyboard shortcuts. These systems work together. For example, a menu item for a command will show the keybinding for it. - -Commands can take arguments and usually take at least a single argument called a context. This is a way to represent state of the user’s current context, such as the currently selected node. - -In addition to menus, keyboard shortcuts, and UI event handlers, there is a command palette the user can trigger to show all commands that can be run in the current context. \ No newline at end of file diff --git a/web/docs/dev/4-workbench-ui.md b/web/docs/dev/4-workbench-ui.md deleted file mode 100644 index 90418b0..0000000 --- a/web/docs/dev/4-workbench-ui.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Workbench UI ---- -## Workbench UI - -### Components - -The workbench UI is made up of Mithril components, which are similar to React components. They take parameters, can have state, and specify a view using JSX. Instead of many atomic components like buttons and labels, Treehouse focuses on larger functional components that map to areas of the UI. Reusable visual elements are represented by CSS classes. Only if a reusable atomic visual element becomes so re-used and is too complex to re-implement is it made into a Mithril component. - -All the Mithril components can be found under `lib/ui`. We use Typescript to make sure their parameters are typed so they're picked up by API documentation; no need for custom documentation for view components. Mithril components are just plain old JavaScript objects with a `view` method. - -### User Context - -Most components are explicitly passed a reference to the Workbench, which they can use to execute commands or pull data in the current Workspace or Context. The Workbench provides a top level context that has the current selected node or nodes, the current panel, etc. However, when passing a Context around it can be given overrides. For example, you may be currently editing a particular node, but you use the mouse to perform a command on another node. The menu ensures the command will receive a Context with that node being acted on. - -### Design System - -Our design system is inspired by projects like [Pollen](https://www.pollen.style/), where instead of generating CSS classes from JavaScript like Tailwind (which requires a compile step and Node.js based tooling), we simply use and override CSS custom properties. This means they can be used in inline styles as well. We also have a subset of common Tailwind utility classes defined, though using the custom properties. \ No newline at end of file diff --git a/web/docs/dev/5-backend-adapters.md b/web/docs/dev/5-backend-adapters.md deleted file mode 100644 index 75b3edb..0000000 --- a/web/docs/dev/5-backend-adapters.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Backend Adapters ---- -## Backend Adapters - -Backend adapters are classes that implement the backend API for a given backend. If you wanted to make your own custom backend, you would implement your own backend adapter -implementing the APIs you wish to hook into and pass that into the `setup` function when initializing Treehouse. We also have a handful of built-in adapters for public or -well-known backend interfaces: - -### lib/backend/browser.ts - -This backend implements the FileStorage API using localStorage. This means data will be stored in the browser for a particular device. It also implements search indexing -using MiniSearch. It does not implement the Authenticator API. - -### lib/backend/github.ts - -This backend implements the FileStorage API using the GitHub API to store data in a GitHub hosted Git repository. It also implements the Authenticator API against a -script that can be hosted on CloudFlare Workers that implements an OAuth client for the GitHub API. This backend adapter does not implement a search index, it simply -uses the browser implementation (MiniSearch). - -### lib/backend/filesystem.ts (coming soon) - -This backend implements the FileStorage API using a local filesystem. Since there isn't a good standard filesystem API in browsers, this implementation operates against -a simple REST API that can be implemented by a backend host process, such as Electron, Apptron, or something custom. - -### Writing an Adapter - -An adapter is just an object that implements this API: - -```js -interface Backend { - auth: Authenticator|null; - index: SearchIndex; - files: FileStore; -} - -interface Authenticator { - login(); - logout(); - currentUser(): User|null; -} - -interface User { - userID(): string; - displayName(): string; - avatarURL(): string; -} - -interface SearchIndex { - index(node: RawNode); - remove(id: string); - search(query: string): string[]; -} - -interface FileStore { - async readFile(path: string): string|null; - async writeFile(path: string, contents: string); -} -``` - -The Authenticator API is optional (and soon so might the SearchIndex API, always defaulting to MiniSearch). Typically a backend adapter will -set `auth`, `index`, and `files` to `this` and implement each of those interfaces on that same object. \ No newline at end of file diff --git a/web/docs/dev/6-api-reference.md b/web/docs/dev/6-api-reference.md deleted file mode 100644 index 34989d7..0000000 --- a/web/docs/dev/6-api-reference.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: layouts/docs.tsx -title: API Reference ---- -## API Reference - -TypeScript documentation for Treehouse is available via [Deno Land](https://deno.land/x/treehouse@0.1.0). \ No newline at end of file diff --git a/web/docs/dev/index.tsx b/web/docs/dev/index.tsx deleted file mode 100644 index 0c9e63c..0000000 --- a/web/docs/dev/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const title = "Docs"; -export const active = "docs"; -export const heading = "Documentation"; -export const layout = "layouts/docs.tsx"; -export default (data, filters) => { - const url = data.url; - const nav = data.nav; - const menu = nav.menu(url); - const children = menu.children || []; - return children.map(child =>
    ); -} \ No newline at end of file diff --git a/web/docs/index.tsx b/web/docs/index.tsx deleted file mode 100644 index c14478d..0000000 --- a/web/docs/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const title = "Treehouse"; -export default (data) => ( - - - - - - - -) diff --git a/web/docs/project/1-contributing.md b/web/docs/project/1-contributing.md deleted file mode 100644 index bdaa6f0..0000000 --- a/web/docs/project/1-contributing.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Contributing ---- -## Contributing - -We'd love your contributions to the project, especially fixes to long-standing issues. If you'd like to work on something that necessitates discussion first, please create an issue so we can provide feedback and avoid stepping on each other's toes. - -You can also contribute by: -* submitting bugs as [issues](https://github.com/treehousedev/treehouse/issues) -* sharing feedback on our [discussion forum](https://discord.gg/6Ae3VNqJbr) - -We welcome any kind of feedback about features you'd like, and we'd especially love to learn about your use case. \ No newline at end of file diff --git a/web/docs/project/2-roadmap.md b/web/docs/project/2-roadmap.md deleted file mode 100644 index 1ba02f0..0000000 --- a/web/docs/project/2-roadmap.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Roadmap ---- -## Roadmap - -We're still formalizing our roadmap, how to organize it, share it, and collaborate with the community on it. For now, keep an eye on the [GitHub issues](https://github.com/treehousedev/treehouse/issues) and [discussions](https://discord.gg/6Ae3VNqJbr). \ No newline at end of file diff --git a/web/docs/project/index.tsx b/web/docs/project/index.tsx deleted file mode 100644 index 0c9e63c..0000000 --- a/web/docs/project/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const title = "Docs"; -export const active = "docs"; -export const heading = "Documentation"; -export const layout = "layouts/docs.tsx"; -export default (data, filters) => { - const url = data.url; - const nav = data.nav; - const menu = nav.menu(url); - const children = menu.children || []; - return children.map(child =>
    ); -} \ No newline at end of file diff --git a/web/docs/quickstart/1-using.md b/web/docs/quickstart/1-using.md deleted file mode 100644 index 97bbef5..0000000 --- a/web/docs/quickstart/1-using.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Using from CDN ---- -## Using from CDN - -First, you have to directly include Mithril and MiniSearch for now: - -```html - - -``` - -If you want to use our CSS, you'll also need to include the Google Font it uses: - -```html - - -``` - -Then you just need some JavaScript to import and call setup with a built-in or custom backend adapter: - -```html - -``` diff --git a/web/docs/quickstart/2-building.md b/web/docs/quickstart/2-building.md deleted file mode 100644 index 99d1c45..0000000 --- a/web/docs/quickstart/2-building.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Building from Source ---- -## Building from Source - -You can build from source by cloning or forking the project and installing [Deno](https://deno.land/). Then you can run: - -`deno task bundle` - -This will produce a JS file you can use at `web/static/lib/treehouse.min.js`. - -For development or debugging, you can run: - -`deno task serve` - -This will build and serve this website locally, including the live demo site at `localhost:9000/demo`, which will use -and watch for changes in the `lib` source. \ No newline at end of file diff --git a/web/docs/quickstart/3-customizing.md b/web/docs/quickstart/3-customizing.md deleted file mode 100644 index 07ef80c..0000000 --- a/web/docs/quickstart/3-customizing.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Customizing the Frontend ---- -## Customizing the Frontend - -The easiest aspect to customize is the look and feel, which you can do with custom CSS. Our CSS design system is built with -custom properties, so you can include your own CSS and just override them with basic CSS. - -Further customization might require you to fork and change the source. The source code is very straight forward, but learn -more in the Developer Guide. - -Lastly, of course, you can implement a custom backend adapter. Although there are just a few things to change this way, -more extension points will be exposed in the future so you won't have to change source. Let us know in the forum what kind -of extension points you'd like! \ No newline at end of file diff --git a/web/docs/quickstart/index.tsx b/web/docs/quickstart/index.tsx deleted file mode 100644 index 0c9e63c..0000000 --- a/web/docs/quickstart/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const title = "Docs"; -export const active = "docs"; -export const heading = "Documentation"; -export const layout = "layouts/docs.tsx"; -export default (data, filters) => { - const url = data.url; - const nav = data.nav; - const menu = nav.menu(url); - const children = menu.children || []; - return children.map(child =>
    ); -} \ No newline at end of file diff --git a/web/docs/user/01-what-is-treehouse.md b/web/docs/user/01-what-is-treehouse.md deleted file mode 100644 index 8694cfe..0000000 --- a/web/docs/user/01-what-is-treehouse.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: layouts/docs.tsx -title: What is Treehouse? ---- -## What is Treehouse? - -Treehouse is an outline editor, which you can think of as a nested bulleted list. Each bullet item is called a **node**. You can use the nodes to create nested layers of folders and notes. \ No newline at end of file diff --git a/web/docs/user/02-data-storage.md b/web/docs/user/02-data-storage.md deleted file mode 100644 index 05bd522..0000000 --- a/web/docs/user/02-data-storage.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Data Storage ---- -## Data Storage - -### Localstorage - -By default, data is stored in your browser’s local storage. That means that the data is linked to your specific browser and device. If you clear your browser cache, the data will be wiped. - -### GitHub - -If you choose to log in with GitHub, we’ll create a repository and store your data there. - -To store your workspace, we will create a public repository called .treehouse.sh if it doesn’t already exist. If you want to make the repository private, you can do so in GitHub. - -To switch back to the local storage backend, log out from the Options menu. - -#### Multiple sessions - -If you log in to Treehouse from multiple devices, the most recent device will have edit and save access. Other sessions will be prompted to refresh the page; if/when you do so, that session becomes the new active session. \ No newline at end of file diff --git a/web/docs/user/03-nodes.md b/web/docs/user/03-nodes.md deleted file mode 100644 index 25a364d..0000000 --- a/web/docs/user/03-nodes.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Nodes ---- -## Nodes - -Your **Workspace** is your top level node, which all other nodes are nested under. - -Learn how to manage and edit your nodes. - -### Add - -You can add a new node in a few ways. - -1. Hit ENTER on your keyboard and start typing. -2. Click into the blank area next to the plus symbol and start typing. - -### Indent - -* Indent a node using TAB ↹ -* Outdent a node using SHIFT + TAB ⇧ ↹ - -### Move - -* Move a node up with SHIFT + COMMAND + UP ARROW ⇧ **⌘ ↑** -* Move a node down with SHIFT + COMMAND + DOWN ARROW ⇧ **⌘** ↓ - -### Expand or collapse a node - -If a node bullet has an outline around it, that’s an indication that it has nested content that is currently hidden. Click the node to expand it. - -Click an expanded node once to collapse it. - -### Node menu - -When you hover over a node, you’ll see a menu to the left of the node bullet. Click it to access node options, such as indent/outdent, open in a panel, etc. - -### Node formatting - -Currently, all nodes are formatted as plain text. You can, however, add a checkbox to a node. With a node selected, open the command palette (⌘ K) and select "Add checkbox". - -### View - -Double click a node to zoom in. - -### Side-by-side (Panel) view - -To view two nodes side-by-side: - -Open the node you want to be in the righthand panel. From its menu, choose “Open in New Panel”. - -You can close or expand either panel to return to a single panel view. \ No newline at end of file diff --git a/web/docs/user/04-fields.md b/web/docs/user/04-fields.md deleted file mode 100644 index 833c0b2..0000000 --- a/web/docs/user/04-fields.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Fields ---- -## Fields - -A **field** is a node that can store structured information. Fields provide your data with structure, and allow for special search syntax (see [Smart Nodes](#smart-nodes)). - -### Create a field - -To add a field to a node: -1. Indent underneath the node you want to contain the field, and type the field name -2. Use Command/Control + K to open the command palette, and choose "Create field" -3. Add your field value in the value section \ No newline at end of file diff --git a/web/docs/user/05-smart-nodes.md b/web/docs/user/05-smart-nodes.md deleted file mode 100644 index fc228d8..0000000 --- a/web/docs/user/05-smart-nodes.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Smart Nodes ---- -## Smart Nodes - -Smart Nodes allow you to create an auto-updating search of all the nodes in your workspace. Simply type your search term, or use the format "fieldname:valuename" to filter specifically by field values. The Smart Node will update automatically as your node content changes. This is a simple but super powerful way to view your data in new configurations. - -To create a Smart Node: -1. Create a new node where you want your Smart Node, and type your search value -2. Use Command/Control + K to open the command palette, and choose "Create Smart Node" - -### Tips for using Smart Nodes -* You can filter on multiple fieldname values (using AND, not OR) like so: "fieldname:valuename fieldname:valuename" etc. -* If your fieldname has spaces, put quotes around it (fieldname:"Value name") -* Search terms are case-insensitive \ No newline at end of file diff --git a/web/docs/user/06-tags.md b/web/docs/user/06-tags.md deleted file mode 100644 index d75052a..0000000 --- a/web/docs/user/06-tags.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Tags ---- -## Tags - -Tags are versatile metadata. Here are some of the ways you can use them: - -* Conduct a keyword search in the searchbar -* Use in Smart Nodes to create custom views -* Use to add a template to a node - -### Create a tag -On any node, with your cursor at the end of the text, type "#" then the tag name to create a tag. Any tags you have already created will show up in an autocomplete list when you type "#". - -### Delete a tag -Click on the tag and press Backspace on your keyboard to delete it. \ No newline at end of file diff --git a/web/docs/user/07-templates.md b/web/docs/user/07-templates.md deleted file mode 100644 index 1b01046..0000000 --- a/web/docs/user/07-templates.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Templates ---- -## Templates - -Templates allow you to add a set of predefined fields and other child nodes to a node simply by adding a tag to that node. - -### Create and use a template - -In this example, we'll create a template called "book" with child fields Author and Release Date. - -1. Wherever you want to store your template, type "book" in the node. -2. Add child fields Author and Release Date, and leave the value fields empty. (To turn a regular node into a Field: Command Palette > Create field) -3. With your cursor in the parent "book" node, open the Command Palette (Command/Control+K) and choose Make Template. -4. Create a list of books wherever you'd like, if you haven't already. With your cursor on one if the individual books, type "#book" and hit ENTER to create the tag. -5. Check out one of your book nodes—it should have the child fields Author and Release Date that you set up in your template. \ No newline at end of file diff --git a/web/docs/user/07.1-views.md b/web/docs/user/07.1-views.md deleted file mode 100644 index 0de4b49..0000000 --- a/web/docs/user/07.1-views.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Views ---- -## Views - -You can view your data in many different ways using commands in the Command Palette. - -### Document -Converting your node(s) to document view centers them in the panel, styles the nodes like paragraphs, and supports basic Markdown formatting. - -*Note: document view is a one-way conversion. Document nodes can't be converted back to List (outline) view.* - -#### Create a Document -With your cursor on the parent node of the node(s) you want to convert to a document, open the Command Palette and select **Make Document**. - -#### Markdown formatting - -##### Text styles -```html -*italic* or _italic_ -**bold** -~~strikethrough~~ -``` -##### Lists -```html -- unordered lists -* with any of -+ these characters - -1. ordered -2. lists -``` - -### Table -Useful for a set of nodes with common fields. Node fields will become columns. - -#### Use Table View -With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select **View As Table**. - -To revert back to List (outline) view, select the parent node and choose "View as List" from the Command Palette. - -### Tabs -The tab view tidies up a common group of nodes, allowing you to view one tab at a time. - -#### Use Tabs View -With your cursor on the parent node of the nodes you want to convert to a document, open the Command Palette and select **View As Tabs**. \ No newline at end of file diff --git a/web/docs/user/08-calendar.md b/web/docs/user/08-calendar.md deleted file mode 100644 index 7a89cfa..0000000 --- a/web/docs/user/08-calendar.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Calendar ---- -## Calendar - -The Calendar is a default node that is automatically generated for every workspace. Nodes inside the calendar are grouped by date, week, then year. - -### Today - -The Today shortcut allows you to quickly view the node for Today in your Calendar. \ No newline at end of file diff --git a/web/docs/user/09-quick-add.md b/web/docs/user/09-quick-add.md deleted file mode 100644 index c620094..0000000 --- a/web/docs/user/09-quick-add.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Quick Add ---- -## Quick Add - -Quick Add in the top navigation is a shortcut that opens a modal in which you can jot a quick note. It will be added to today’s date in your Calendar. \ No newline at end of file diff --git a/web/docs/user/10-command-palette.md b/web/docs/user/10-command-palette.md deleted file mode 100644 index aef5b39..0000000 --- a/web/docs/user/10-command-palette.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Command Palette ---- -## Command Palette - -With a node selected, open the command palette (⌘ K) to view all the available actions for that node. \ No newline at end of file diff --git a/web/docs/user/11-keyboard-shortcuts.md b/web/docs/user/11-keyboard-shortcuts.md deleted file mode 100644 index b0ef751..0000000 --- a/web/docs/user/11-keyboard-shortcuts.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Keyboard Shortcuts ---- -## Keyboard Shortcuts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    indent
    outdent⇧ ↹
    move node up⇧ ⌘ ↑
    move node down⇧ ⌘ ↓
    delete node⇧ ⌘ ⌫
    add or remove checkbox⌘ ↵
    mark checkbox as done⌘ ↵
    open command palette⌘ K
    \ No newline at end of file diff --git a/web/docs/user/12-css-theming.md b/web/docs/user/12-css-theming.md deleted file mode 100644 index 495d9e2..0000000 --- a/web/docs/user/12-css-theming.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -layout: layouts/docs.tsx -title: CSS Theming ---- -## CSS Theming - -You can create your own custom theme for Treehouse using our built-in variables a.k.a. custom properties. - -### Create a theme -1. Add a top level folder called "ext" to your treehouse.sh repository -2. Create a CSS file inside the ext folder -3. Use the format below to populate the variables with hex code values. *Tip: Create color variables inside the root block to reuse color styles between custom properties.* - -```css -:root { - --font: 'Font name'; - - --color-primary: #hex; - - --color-background: #hex; - --color-background-sidebar: #hex; - - --color-icon: #hex; - --color-icon-secondary: #hex; - - --color-nav-label: #hex; - - --color-text: #hex; - --color-text-placeholder: #hex; - --color-text-secondary: #hex; - - --color-highlight: #hex; - - --color-node-handle: #hex; - --color-node-handle-secondary: #hex; - - --color-outline: #hex; - --color-outline-secondary: #hex; -} -``` - -### Managing multiple CSS files -If you have multiple CSS files you want to swap between, append ".disabled" to the end of the unused CSS filename(s). - -### Variable Reference - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    VariableDescription
    --fontGlobal font definition. Change the font itself but not sizes or styles with this.
    --color-primaryBackground color of primary button
    --color-backgroundBackground color of main panels, menus, and modals
    --color-background-sidebarBackground color of sidebar navigation
    --color-iconHigh contrast color used for primary icons. For example: icons in the top navigation
    --color-icon-secondaryLow-contrast color used for secondary icons
    --color-nav-labelUsed for top and sidebar navigation labels
    --color-textDefault text color used for body text, navigation, and primary icons
    --color-text-placeholderLower-contrast color used for placeholder text in inputs
    --color-text-secondaryLower-contrast color used for secondary text
    --color-highlightLowest-contrast color to subtly highlight selected item in the menu, search, and command palette
    --color-node-handleBullet color for nodes (a.k.a. the node handle)
    --color-node-handle-secondaryLower-contrast accent color on node handles. For instance, the outer filled circle on a node indicating collapsed children.
    --color-outlineHigh contrast border color on pop-over containers like modals and menus.
    --color-outline-secondaryLower contrast border color where less extreme contrast is needed, such as the divider between panels and the navigation.
    - -### Fonts - -To use a non-system font, you may import a font into your CSS file using either the @import method or the @font-face method. \ No newline at end of file diff --git a/web/docs/user/13-backend-extensions.md b/web/docs/user/13-backend-extensions.md deleted file mode 100644 index e1b8aa9..0000000 --- a/web/docs/user/13-backend-extensions.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: layouts/docs.tsx -title: Backend Extensions ---- -## Backend Extensions - -Backends can change or extend how a Treehouse application behaves and are exposed to the frontend via adapters. - -### Workspace Storage - -The Treehouse frontend uses its backend adapter to store the state of your workspace into a JSON document. The backend can decide where and how that JSON document is stored. For example, the Browser backend adapter will store the JSON into localStorage. - -### User Authentication - -An optional capability of the frontend is to know whether a particular user is authenticated. You typically want user authentication for cloud or web-based deployments, but not necessarily for a local desktop app. - -### Search Indexing - -Out of the box, Treehouse will index your workspace for full-text search using [Minisearch](https://lucaong.github.io/minisearch/), -which will be good enough for many cases. However, a backend adapter can choose to hook into the index and searching -so that you could have a more powerful search index such as ElasticSearch. \ No newline at end of file diff --git a/web/docs/user/index.tsx b/web/docs/user/index.tsx deleted file mode 100644 index 0c9e63c..0000000 --- a/web/docs/user/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const title = "Docs"; -export const active = "docs"; -export const heading = "Documentation"; -export const layout = "layouts/docs.tsx"; -export default (data, filters) => { - const url = data.url; - const nav = data.nav; - const menu = nav.menu(url); - const children = menu.children || []; - return children.map(child =>
    ); -} \ No newline at end of file diff --git a/web/index.tsx b/web/index.tsx deleted file mode 100644 index 704e1e8..0000000 --- a/web/index.tsx +++ /dev/null @@ -1,236 +0,0 @@ -export const title = "Treehouse"; -export default (data) => ( - - - - - - - - - - - - - Treehouse: Note-taking Frontend - - -
    - -
    - -
    -
    -
    -
    -

    - A lightweight note-taking tool to make your own -

    -

    - An open source note-taking frontend to extend and customize. Bring your own backend or configure a built-in backend to get started. -

    - - -
    -
    -
     
    - Treehouse UI preview -
    -
    - -
    -
    - -
    -
    -

    Treehouse is in early development. View release 0.7.0 →

    -
    -
    - -
    -
    -

    Simple note-taking out of the box

    -
    -
    - -

    Fields and Smart Nodes

    -

    Add metadata, then create a custom search view.

    -
    - -
    - -

    Quick Add and Daily Notes

    -

    Quickly add notes organized by date.

    -
    - -
    - -

    Full-Text Search

    -

    Quick, intuitive search that can be extended.

    -
    - - -
    -
    -
    - -
    -
    -
    -

    Quick development setup

    -

    The project is lightweight and uses Deno as the JavaScript toolchain. Get up and running in seconds.

    -
    -
    -
    -

    1. Clone the repository

    -

    Open in GitHub or use the command below to clone it with Git.

    -
    git clone https://github.com/treehousedev/treehouse.git
    -
    -
    -

    2. Start the dev server

    -

    Run locally with the built-in development server.

    -
    deno task serve
    -
    -
    -
    -
    - -
    -
    -
    -

    Bring your own backend

    -

    We include several backend options, but you can build your own for different scenarios and requirements.

    -
    -
    -
    -
    -
    - -
    -
    -
    -

    Storage

    -

    Keep your data where you want, how you want it. Use the filesystem or put it in the cloud.

    -
    -
    -
    -
    -
    - -
    -
    -
    -

    Custom search

    -

    If our full-text search isn't powerful enough, plug-in your own indexer. Pull in results from external tools if you want!

    -
    -
    -
    -
    -
    - -
    -
    -
    -

    Authentication

    -

    Optional pluggable authentication lets you use cloud platforms or integrate with your company authentication.

    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -

    Learn about the project

    -

    Read our blog and devlog series to follow along with the development process.

    -
    - -
    - -
    - -
    -
    -
    - - - -
    -
    -

    Interested in building a specialized note-taking tool? Work with us.

    -
    -
    - - - -);