diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 375587329..bf97b5a69 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Architecture -This document describes how Wordplay is built. It aspires to be a high level document, describing components, responsibilities, patterns, and dependencies. It should be a good first place for getting oriented with the overall implementation. Of course, reading the code will eventually be necessary; as you're reading this, it would be smart to read the code as you go, getting a sense of how the concepts here play out in the implemention. +This document describes how Wordplay is built. It aspires to be a high-level document describing components, responsibilities, patterns, and dependencies. It should be a good first place for getting oriented with the overall implementation. Of course, reading the code will eventually be necessary; as you're reading this, it would be smart to read the code as you go, getting a sense of how the concepts here play out in the implementation. ## Key Dependencies @@ -10,11 +10,11 @@ Wordplay has several major dependencies, each of which is crucial to understand - **[TypeScript](https://www.typescriptlang.org/)**. TypeScript is a _superset_ of JavaScript that adds type information -- that means that it's JavaScript, plus other goodies. Most defects in programs are type errors, and TypeScript catches most type errors, so we use it to catch most defects. Read the [tutorial](https://www.typescriptlang.org/docs/handbook/intro.html) if unfamiliar. As a practice, we do not use `any`, unless TypeScript _really_ can't express the type we're trying to express. -- **[Svelte](https://svelte.dev/)**. Svelte is a front-end framework for building web applications. At the highest level, a Svelte application is a collection of `.svelte` files, each corresponding to some component, and each Svelte file has a script, markup, and style section, using JavazScript, HTML, and CSS standards. It also adds several other simple language features, however, that make building interactive web applications easier. We use Svelte because it's the fastest font end framework and the easiest to learn (relative to React, Vue, Angular, and other frameworks). The [Svelte tutorial](https://learn.svelte.dev/) is a must read. +- **[Svelte](https://svelte.dev/)**. Svelte is a front-end framework for building web applications. At the highest level, a Svelte application is a collection of `.svelte` files, each corresponding to some component, and each Svelte file has a script, markup, and style section, using JavaScript, HTML, and CSS standards. It also adds several other simple language features, however, that make building interactive web applications easier. We use Svelte because it's the fastest front-end framework and the easiest to learn (relative to React, Vue, Angular, and other frameworks). The [Svelte tutorial](https://learn.svelte.dev/) is a must-read. - **[SvelteKit](https://kit.svelte.dev/)**. Builds upon Svelte, adding routing, server-side rendering, and other neat features for building web applications. We primarily use it to structure the Wordplay website, define consistent layout features, and interact with backend services, primarily Firebase. It's the obvious choice for a Svelte project. -- **[Firebase](https://firebase.google.com/)**. We use Firebase to persist creator's projects and configuration settings, as well as for enabling project sharing. It uses a non-relational database structure for high scalability, which has some unfortunate tradeoffs on software evolution. The worst is that any schema design decisions we make place hard constraints on the views of data we create, since the schema design determines what kinds of queries are feasible. So any time we're doing schema design, we must simultaneously do user interface design, and be highly confident we won't change our minds about interface design. +- **[Firebase](https://firebase.google.com/)**. We use Firebase to persist the creator's projects and configuration settings, as well as for enabling project sharing. It uses a non-relational database structure for high scalability, which has some unfortunate tradeoffs on software evolution. The worst is that any schema design decisions we make place hard constraints on the views of data we create, since the schema design determines what kinds of queries are feasible. So any time we're doing schema design, we must simultaneously do user interface design, and be highly confident we won't change our minds about interface design. There are several other more minor dependencies, especially in tooling ([Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), [Prettier](https://prettier.io/)). @@ -28,7 +28,7 @@ Here are the major components of Svelte, and how they interact with each other. [Database.ts](https://github.com/wordplaydev/wordplay/blob/main/src/db/Database.ts) is exactly what it's named: an interface to all data persistence. It keeps a snapshot of creator configuration settings, creator projects, and creator authentication information. It decides which state to persist in a browser's `localStorage` (because it's device specific) and which to keep in Firebase (because it's account specific). -The database also relies heavily on [Svelte stores](https://svelte.dev/docs/svelte-store), offering granular access to settings to the user interface. For example, it keeps a store for the current list of locales selected, and exposes it globally, so that user interfaces can access the current locale or locales, and change the interface based on them, automatically updating whenever the language is changed. +The database also relies heavily on [Svelte stores](https://svelte.dev/docs/svelte-store), offering granular access to settings on the user interface. For example, it keeps a store for the current list of locales selected, and exposes it globally, so that user interfaces can access the current locale or locales, and change the interface based on them, automatically updating whenever the language is changed. The database should generally be fairly opaque; it shouldn't matter to code using the Database's methods how or where data is stored. It's currently backed by Firebase, but that could change, and no other part of the application should have to care. @@ -42,8 +42,8 @@ Some nodes add additional interfaces, especially [Expression.ts](https://github. One important note about AST nodes: they are all **immutable**. This has a few implications: -- They should never have state can can be modified, so all of their fields are `readonly`, unless they are a temporary cache of some derived value (e.g., an expression's type). -- They do not know their parent. This is the parser builds the tree from the bottom up; nodes have to be created before they can become part of other nodes, and so each node's parent doesn't exist until after it's created. However, this is also because immutable nodes can be reused, since they cannot change. One node might appear in many trees. +- They should never have state that can be modified, so all of their fields are `readonly`, unless they are a temporary cache of some derived value (e.g., an expression's type). +- They do not know their parent. This is, the parser builds the tree from the bottom up; nodes have to be created before they can become part of other nodes, and so each node's parent doesn't exist until after it's created. However, this is also because immutable nodes can be reused, since they cannot change. One node might appear in many trees. To work around the lack of a parent, we have [Root.ts](https://github.com/wordplaydev/wordplay/tree/main/src/nodes), which represents the root of an AST, and manages all of the parent information, offering facilities for figuring out the structure of an AST. @@ -53,13 +53,13 @@ Overall, it's best to think of the nodes as the center of everything: they defin ### Type Checking -All nodes that are subclasses of [Expression.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Expression.ts) have a _type_. If you're not familiar with types in programming languags, they represent what kinds of values some symbol might store. They're a central idea in TypeScript and also a central idea in Wordplay. +All nodes that are subclasses of [Expression.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Expression.ts) have a _type_. If you're not familiar with types in programming languages, they represent what kinds of values some symbols might store. They're a central idea in TypeScript and also a central idea in Wordplay. -Wordplay allows for types to be declared explicitly, but also to be inferred from context. To enable this, each [Expression.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Expression.ts) node has a `computeType()` function that computes what type the expression has, either from its declared type, or its implicit semantics (e.g., a Boolean literal has a Boolean type, by definition), or inferred from context. To see what kinds of types an expression has and what kinds of type inference it does, check it's `computeType()`. +Wordplay allows for types to be declared explicitly, but also to be inferred from context. To enable this, each [Expression.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Expression.ts) node has a `computeType()` function that computes what type the expression has, either from its declared type, or its implicit semantics (e.g., a Boolean literal has a Boolean type, by definition), or inferred from context. To see what kinds of types an expression has and what kinds of type inference it does, check its `computeType()`. To enable type inference, and to prevent infinite cycles (e.g., a variable referencing itself), we have `Context.ts`, which is a place to cache information about an AST while it's being traversed and analyzed. This cache stores type information, remembers paths through trees during analysis to prevent cycles, gets roots of nodes, and remembers definitions in scope. It generally exists to make program analysis possible and efficient. -There are many types, each defined as a subclass of [Type.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Type.ts). Many of these represent values, some represent unknown values. Each defines a function `acceptsAll()`, which takes a set of types and verifies that all of the types in the set are okay to assign to the type in question. These various implementations of `acceptsAll()` defines the semantics of Wordplay's type system. +There are many types, each defined as a subclass of [Type.ts](https://github.com/wordplaydev/wordplay/blob/main/src/nodes/Type.ts). Many of these represent values, some represent unknown values. Each defines a function `acceptsAll()`, which takes a set of types and verifies that all of the types in the set are okay to assign to the type in question. These various implementations of `acceptsAll()` define the semantics of Wordplay's type system. Note that all types are subclasses of `Node` and are therefore immutable. This is because nodes can be explicitly stated in code, and are therefore must be AST nodes. But we use the very same nodes to represent types that are inferred; they just happen to not live in an AST. @@ -69,29 +69,29 @@ There are many ways that an AST might be invalid. They can have type errors, cau Not every node can have conflicts (e.g., `BooleanLiteral`). Some can have many. Overall, there are more than 50 types of errors that can occur, only some of which are type errors. -Each conflict gathers a bunch of contextual information about the nodes involved and then defines a _primary_ and _secondary_ node. The primary one is the node on which the error happened, and the secondary, which is optional, is the node for which there is disagreement. This follows a concept of errors as not a mistake that someone made, but a disagreement between two parts of a Wordplay program. +Each conflict gathers a bunch of contextual information about the nodes involved and then defines a _primary_ and _secondary_ node. The primary one is the node on which the error happened, and the secondary one, which is optional, is the node for which there is disagreement. This follows a concept of errors as not a mistake that someone made, but a disagreement between two parts of a Wordplay program. -Once an AST is built for a Wordplay program, it's not necessarily analyzed for conflicts. It's up to the front end when to call `Project.analyze()` to find defects. The analysis happens at the project level an many conflicts span multiple `Source` nodes in a project. +Once an AST is built for a Wordplay program, it's not necessarily analyzed for conflicts. It's up to the front end when to call `Project.analyze()` to find defects. The analysis happens at the project level and many conflicts span multiple `Source` nodes in a project. ### Evaluation Wordplay programs are _evaluated_, in that they are purely functional. A Wordplay program is one big function, composed of smaller functions, and every Wordpaly program evaluates to a single [Value](https://github.com/wordplaydev/wordplay/blob/main/src/values/Value.ts). Values can be as simple as a [BoolValue](https://github.com/wordplaydev/wordplay/blob/main/src/values/BoolValue.ts) or a [Text](https://github.com/wordplaydev/wordplay/blob/main/src/values/TextValue.ts), or as complex as a [Structure](https://github.com/wordplaydev/wordplay/blob/main/src/values/StructureValue.ts) with 17 properties, one of which is a `List` of other `Structure` values. The most interesting values that a Wordplay program evaluates to are [Stage](https://github.com/wordplaydev/wordplay/blob/main/src/output/Stage.ts) structures, which define the arrangement and appearance of [Phrase](https://github.com/wordplaydev/wordplay/blob/main/src/output/Phrase.ts)es. -Only `Expression` nodes are evaluable. Each one defines a `compile()` function that converts the node and its children into a series of [Step](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/Step.ts). There are fewer than a dozen types of steps; most do things like bind values to a name in scope, start a function evaluation, jump past some step based on some condition, or do some other low level operation. Every Wordplay `Source` therefore compiles down to a sequence of `Step`s that are evaluated one at a time. +Only `Expression` nodes are evaluable. Each one defines a `compile()` function that converts the node and its children into a series of [Step](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/Step.ts). There are fewer than a dozen types of steps; most do things like bind values to a name in scope, start a function evaluation, jump past some step based on some condition, or do some other low-level operation. Every Wordplay `Source` therefore compiles down to a sequence of `Step`s that are evaluated one at a time. -The component that evaluates steps is [Evaluator.ts](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/Evaluator.ts). It takes a [Project](https://github.com/wordplaydev/wordplay/blob/main/src/models/Project.ts), and compiles its `Source`, and evaluates each sequence of steps according to the rules of each step. As it does this, it maintains a stack of function evaluations, and for each evaluation, a stack of values, and named scope of key/`Value` bindings. As each step evaluates, values are pushed and popped onto the value stack, bound to names in memory, and passed as inputs to function evaluations. If any expression every evaluates to an [ExceptionValue](https://github.com/wordplaydev/wordplay/blob/main/src/values/ExceptionValue.ts) value, the `Evaluator` halts and evaluates to the exception. +The component that evaluates steps is [Evaluator.ts](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/Evaluator.ts). It takes a [Project](https://github.com/wordplaydev/wordplay/blob/main/src/models/Project.ts), compiles its `Source`, and evaluates each sequence of steps according to the rules of each step. As it does this, it maintains a stack of function evaluations, and for each evaluation, a stack of values, and named scope of key/`Value` bindings. As each step evaluates, values are pushed and popped onto the value stack, bound to names in memory, and passed as inputs to function evaluations. If any expression ever evaluates to an [ExceptionValue](https://github.com/wordplaydev/wordplay/blob/main/src/values/ExceptionValue.ts) value, the `Evaluator` halts and evaluates to the exception. -A key aspect of Wordplay is that some of it's values are [StreamValues](https://github.com/wordplaydev/wordplay/tree/main/src/values), which change over time. Streams are sequences of values that are input by the external world, including things like time, mouse buttons, keyboard presses, and other events. Every time a stream has a new value, `Evaluator` reevaluates the `Source` that references it. This is what creates interactivity; every time there is some input, the program gets a chance to respond to it be reevaluating. +A key aspect of Wordplay is that some of its values are [StreamValues](https://github.com/wordplaydev/wordplay/tree/main/src/values), which change over time. Streams are sequences of values that are input by the external world, including things like time, mouse buttons, keyboard presses, and other events. Every time a stream has a new value, `Evaluator` reevaluates the `Source` that references it. This is what creates interactivity; every time there is some input, the program gets a chance to respond to it by reevaluating. ### APIs -All APIs in Wordplay -- the input streams like `Key` and `Button`, output data structures like `Phrase` and `Stack` -- are defined as Wordplay type definitions. For example, consider [Grid](https://github.com/wordplaydev/wordplay/blob/main/src/output/Grid.ts), one of the `Layout` types. Inside that file, there's a function that takes a list of locales, and constructs a Wordplay structure definition using those locales, defining its inputs, their documentation and more. And then, there's a convience wrapper class defined to store the inputs in a type safe way for the rendering engine to use. There's also a function to convert the structure value generated by a program into an instance of that wrapper class. This basic pattern of 1) structure definition, 2) wrapper class, and 3) generator occurs for all built-in APIs in the implementation. +All APIs in Wordplay -- the input streams like `Key` and `Button` and output data structures like `Phrase` and `Stack` -- are defined as Wordplay type definitions. For example, consider [Grid](https://github.com/wordplaydev/wordplay/blob/main/src/output/Grid.ts), one of the `Layout` types. Inside that file, there's a function that takes a list of locales and constructs a Wordplay structure definition using those locales, defining its inputs, their documentation, and more. And then, there's a convenience wrapper class defined to store the inputs in a type-safe way for the rendering engine to use. There's also a function to convert the structure value generated by a program into an instance of that wrapper class. This basic pattern of 1) structure definition, 2) wrapper class, and 3) generator occurs for all built-in APIs in the implementation. Creating new output APIs in the language means following that pattern, and doing a few other key things: - Creating a similar file like [Grid](https://github.com/wordplaydev/wordplay/blob/main/src/output/Grid.ts)], defining its structure definition with locales, defining a wrapper class for use in the rendering, and writing a function that converts a `StructureValue` representing that type as an instance of that wrapper class. -- Updating [createDefaultShares](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/createDefaultShares.ts) to call the structure definition creator function, and including the definition in the appropriate set of types. -- Creating placeholders for localization strings for all of the strings defined for the type and it's documentation in [OutputTexts.ts](https://github.com/wordplaydev/wordplay/blob/main/src/locale/OutputTexts.ts), where the schema for the output API strings are defined. +- Updating [createDefaultShares](https://github.com/wordplaydev/wordplay/blob/main/src/runtime/createDefaultShares.ts) to call the structure definition creator function, and include the definition in the appropriate set of types. +- Creating placeholders for localization strings for all of the strings defined for the type and its documentation in [OutputTexts.ts](https://github.com/wordplaydev/wordplay/blob/main/src/locale/OutputTexts.ts), where the schema for the output API strings are defined. - Using the new wrapper class in the output engine in the appropriate place to change rendering. The output engine is generally comprised of the Svelte components `PhraseView`, `GroupView`, `StageView`, `Scene`, `OutputAnimation`, `Physics`, and other helper classes. Once these are done, the new API structure should appear in documentation and work in programs. @@ -100,7 +100,7 @@ Other APIs, like streams, and value APIs on things like numbers and lists, are d ### Localization -Wordplay defines a locale schema in [Locale](https://github.com/wordplaydev/wordplay/blob/main/src/locale/Locale.ts), which is basically one big JSON data structure of named string values. Some of these strings are constant, others are templates which can be given inputs and rendered with concrete values. Wordplays many nodes and user interfaces generally make deep links into this data structure to get a string or tempate, and render appropriate text. +Wordplay defines a locale schema in [Locale](https://github.com/wordplaydev/wordplay/blob/main/src/locale/Locale.ts), which is basically one big JSON data structure of named string values. Some of these strings are constant, others are templates that can be given inputs and rendered with concrete values. Wordplay's many nodes and user interfaces generally make deep links into this data structure to get a string or template and render appropriate text. Localization is intimately connected to accessibility, as many of the localization strings are templated descriptions of nodes, values, and other content. @@ -110,17 +110,17 @@ Localization is intimately connected to accessibility, as many of the localizati All output is rendered in the [OutputView](https://github.com/wordplaydev/wordplay/blob/main/src/components/output/OutputView.svelte) component. It renders `Exception`s, and other arbitrary values using all of the Svelte views defined in `valueToView.ts`, which maps `Value` instances onto views. If a Value corresponds to a `Phrase`, `Group`, or `Stage`, then it is rendered as typographic output. These typographic values are generally converted into classes that provide convenience functions for reasoning about the output without having to use the low level interface of `Structure` values. -The typographic output is defined by `Stage`. It's responsible for managing any typographic outputs that are animating, for tracking outputs that have entered stage, or existed, or moved, and for rendering the output in a way that respects various settings, such as the Stage's place (i.e., it's zoom level, rotation, etc.). It's also responsible for monitoring for inputs from input devices and passing them to the `Evaluator`'s streams, causing reevaluation. +The typographic output is defined by `Stage`. It's responsible for managing any typographic outputs that are animating, for tracking outputs that have entered the stage, existed, or moved, and for rendering the output in a way that respects various settings, such as the Stage's place (i.e., its zoom level, rotation, etc.). It's also responsible in monitoring for inputs from input devices and passing them to the `Evaluator`'s streams, causing reevaluation. ### Editor The `Editor.svelte` is responsible for both rendering an AST and for modifying an AST. -Rendering is managed by all of the mappings defined in `nodeToView.ts`; every type of node has a corresponding view. Most views are just very straightforward mappings from it's list of children to views of each child. But some have special behaviors. `RootView` is also very important for doing AST-wide hiding of nodes (e.g., nodes in non-selected locales). +Rendering is managed by all of the mappings defined in `nodeToView.ts`; every type of node has a corresponding view. Most views are just very straightforward mappings from its list of children to views of each child. But some have special behaviors. `RootView` is also very important for doing AST-wide hiding of nodes (e.g., nodes in non-selected locales). Editing comes in three forms: -- Typing, which is primarily managed by `Caret.ts`, and involves inserting and removing symbols, moving a caret to select a certain position or node. When edited as text, `Source` does its best to avoid reparsing the entire tree, reusing any nodes and tokens that it can. +- Typing, which is primarily managed by `Caret.ts`, and involves inserting and removing symbols, and moving a caret to select a certain position or node. When edited as text, `Source` does its best to avoid reparsing the entire tree, reusing any nodes and tokens that it can. - Drag and drop involves a global `ProjectView ` state that manages a selected node from an `Editor`, or `Documentation.svelte`. The editor uses Node facilities such as their grammar and types to decide what can be dropped where. @@ -129,7 +129,7 @@ Editing comes in three forms: The editor does many other things, including: - Rendering conflicts based on the current caret position -- Highlighting based on mouse, touch screen, and drag interactions, defined by `Highlights.ts` +- Highlighting based on the mouse, touch screen, and drag interactions, defined by `Highlights.ts` - Providing descriptions for screen readers ### Conflicts and resolutions @@ -137,7 +137,7 @@ The editor does many other things, including: After every edit, a project is analyzed for _conflicts_ (in other languages, you might call these errors). Subclasses of `Node` can compute conflicts based on their context. For example, `Evaluate` can generate many types of conflicts, such as `IncompatibleInput`, which happens when an input being provided doesn't match the function being evaluated. -We call them conflicts partly because they are inconsistencies in a program, and not necessarily a mistake someone made, but also because we anthropomorphize language constracts, and so "conflict" is a pun: its a disagreement between different characters in the Wordplay universe. +We call them conflicts partly because they are inconsistencies in a program, and not necessarily a mistake someone made, but also because we anthropomorphize language constructs, and so "conflict" is a pun: it is a disagreement between different characters in the Wordplay universe. `Conflict.ts` is the base class of all conflicts, and all conflicts are required to define methods that describe conflicts, provide references to the nodes in a program that are involved in the conflict, and optionally offer a way to resolve a conflict. Conflict resolutions are defined by the `Resolution` type, and generally need a way to describe the resolution, and a method that produces a revised project that resolves the conflict. @@ -151,7 +151,7 @@ Clicking that button calls `PossiblePII`'s `getResolution` method to generate a The palette is a special kind of editor that offers user interfaces for transforming the `Evaluate` nodes that represent Phrases, Groups, and Stages. It constructs detailed models of the `Evaluate`, and defines many controls for modifying inputs to the `Evaluate`. -On each edit, the project is revised, reevaluated, and re-rendered. Making this interactive possible requires reevluating the revised project on the previously provided inputs, to try to get the Evaluator back to the same state it was in previously. This is done by `Evaluator.mirror()`, which replays the same inputs the Evaluator received on the revised project. +On each edit, the project is revised, reevaluated, and re-rendered. Making this interactive possible requires reevaluating the revised project on the previously provided inputs, to try to get the Evaluator back to the same state it was in previously. This is done by `Evaluator.mirror()`, which replays the same inputs the Evaluator received on the revised project. ### Documentation @@ -161,7 +161,7 @@ Most documentation is written in `Locale`, as all of it needs to be localized. B ### Project View -[ProjectView.svelte](https://github.com/wordplaydev/wordplay/blob/main/src/components/project/ProjectView.svelte) defines a set of [Tile](https://github.com/wordplaydev/wordplay/blob/main/src/components/project/Tile.ts) that represent source files, documentation windows, palettes, output, and other project level settings. It's basically a window manager and global context store. It also reacts to project revisions, pushing the revised project down to its views to update its appearance. It relies heavily on Svelte to make these updates minimal and fast. +[ProjectView.svelte](https://github.com/wordplaydev/wordplay/blob/main/src/components/project/ProjectView.svelte) defines a set of [Tile](https://github.com/wordplaydev/wordplay/blob/main/src/components/project/Tile.ts) that represent source files, documentation windows, palettes, output, and other project-level settings. It's basically a window manager and global context store. It also reacts to project revisions, pushing the revised project down to its views to update its appearance. It relies heavily on Svelte to make these updates minimal and fast. ## Immutability diff --git a/CHANGELOG.md b/CHANGELOG.md index f51001d98..65ac4f2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,72 @@ We'll note all notable changes in this file, including bug fixes, enhancements, and all closed issues. Dates are in `YYYY-MM-DD` format and versions are in [semantic versioning](http://semver.org/) format. +## 0.12.3 2024-10-12 + +### Fixed + +- Fixed rendering of recent values in debugger. +- Fixed debugger highlights on literal values. +- Fixed UI ID for stage to correct highlight. +- Don't wrap node sequences in blocks mode. +- Describe added nodes during editing. +- Fixed ARIA bugs on autocomplete menu. + +## 0.12.2 2024-10-5 + +### Fixed + +- Corrected debugger behavior in the presence of reused values. + +## 0.12.1 2024-09-28 + +### Fixed + +- Corrected spacing on home page of header. +- Properly render placeholders in blocks mode. +- Consistent rendering of inferred and explicit placeholder types. +- Better type checking on operator wrapping +- Don't show full names of operators. +- Fixed evaluate autocomplete. +- Removed column layout of documented expressions. +- Changed explanation delimiter to ¶ for clarity, disambiguation in parsing. +- Allow entry into emtpy field lists with no tokens. +- Better handling of empty words tokens in blocks mode. +- Fixed caret position for newlines in interior blocks. +- Removed invalid symbol type from placeholder token. + +## 0.12.0 2024-09-22 + +### Add + +- [#529](https://github.com/wordplaydev/wordplay/issues/529) Redesign of blocks mode for accessibility and error-prevention. Much to do to make it super polished, but it's better than the previous version. + +## 0.11.2 2024-09-17 + +- Show conflicts even when paused. + +### Maintenance + +- Several dependendabot pull request updates. + +## 0.11.1 2024-08-25 + +### Added + +- Custom descriptions of Stage, Group, Phrase, and Shape output, overriding default descriptions. + +### Fixed + +- Included past tense verbs in higher order functions. +- Fixed aria-label updates. +- Drop old announcements if the queue exceeds three, preferering most recent. + +## 0.10.10 2024-08-12 + +### Fixed + +- [#550](https://github.com/wordplaydev/wordplay/issues/550) Ensure owned projects are marked as owned when loaded directly from Firestore. + ## 0.10.9 2024-08-10 ### Added diff --git a/LANGUAGE.md b/LANGUAGE.md index 1ccee8d25..507f7c750 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -79,7 +79,7 @@ Text literals can be opened and closed with numerous delimiters: > markup → `\` > text → _any sequence of characters between open/close delimiters_ -Wordplay has a secondary notation for markup, delimited by backticks, as in `` `I am \*bold\*\` ``. Between backticks, these tokens are valid: +Wordplay has a secondary notation for markup, delimited by backticks, as in ¶ `I am \*bold\*\` ¶. Between backticks, these tokens are valid: > linkopen → `<` > linkclose → `>` @@ -747,8 +747,8 @@ sum/en sum/en-US sum/en-US,suma/es sum/en-US,suma/es-MX -``Sum of values``/en sum/en-US,suma/es-MX -``Sum of values``/en sum/en-US,suma/es-MX: 1 +¶Sum of values¶/en sum/en-US,suma/es-MX +¶Sum of values¶/en sum/en-US,suma/es-MX: 1 ``` All of those examples define a name `sum`; some of them specify its type, some provide a value, some provide documentation, some have multiple language tagged aliases to enable localization of the program. Context determines whether these are semantically valid; for example, table columns require binds to specify a type; bindings in blocks (described below) have to have values. @@ -816,7 +816,7 @@ Here are some example function definitions: ƒ sum(a•# b•#) a + b ƒ sum(a•#:0 b•#:0) a + b ƒ sum(a•#:0 b•#:0)•# a + b -``Add some numbers`` ƒ sum(a•# b•#) a + b +¶Add some numbers¶ ƒ sum(a•# b•#) a + b ƒ kind(num•#) ( odd: (num % 2) = 1 odd ? 'odd' 'even' @@ -1040,11 +1040,11 @@ Programs create an evaluation scope, evaluate their binds and expressions in rea There are three places that comments can appear in code: just before programs, just before definitions of functions, structures, and conversions, and before expressions: ``` -``hi bind``a: 1 -``hi function`` ƒ hello() 'hi' -``hi structure`` •food(calories•#cal) -``hi conversion`` → #cal #kcal . · 0.001kcal/cal -``hi expression``1 + 1 +¶hi bind¶a: 1 +¶hi function¶ ƒ hello() 'hi' +¶hi structure¶ •food(calories•#cal) +¶hi conversion¶ → #cal #kcal . · 0.001kcal/cal +¶hi expression¶1 + 1 ``` Documentation is part of the grammar, not just discarded text in parsing. This allows for unambiguous association between text and documentation. diff --git a/functions/package-lock.json b/functions/package-lock.json index 6e9075cec..6dc16c944 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -2019,9 +2019,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -2031,7 +2031,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -2041,6 +2041,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2729,36 +2743,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -2769,6 +2783,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4593,9 +4615,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -4613,13 +4638,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "peer": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4837,9 +4862,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4998,9 +5026,9 @@ "peer": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/picocolors": { "version": "1.0.0", @@ -5361,9 +5389,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -5400,9 +5428,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -5413,6 +5441,45 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", diff --git a/package-lock.json b/package-lock.json index 6b12b7b6b..08855b38a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wordplay", - "version": "0.10.6", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wordplay", - "version": "0.10.6", + "version": "0.12.0", "hasInstallScript": true, "dependencies": { "@axe-core/playwright": "^4.8.5", @@ -45,7 +45,7 @@ "prettier": "3", "prettier-plugin-svelte": "^3", "run-script-os": "^1.1.6", - "svelte": "^4.2.12", + "svelte": "^4.2.19", "svelte-check": "^3.6.9", "svelte-jester": "^3.0.0", "svelte-preprocess": "^5.1.3", @@ -54,7 +54,7 @@ "tslib": "^2.6", "tsx": "^4.7.0", "typescript": "^5.5.2", - "vite": "^5.2.7", + "vite": "^5.4.7", "vite-plugin-eslint": "^1.8.1", "vitest": "^1.4.0" } @@ -2668,9 +2668,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", - "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], @@ -2681,9 +2681,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", - "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], @@ -2694,9 +2694,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", - "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], @@ -2707,9 +2707,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", - "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], @@ -2720,9 +2720,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", - "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], @@ -2733,9 +2746,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", - "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], @@ -2746,9 +2759,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", - "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], @@ -2759,11 +2772,11 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", - "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", "cpu": [ - "ppc64le" + "ppc64" ], "dev": true, "optional": true, @@ -2772,9 +2785,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", - "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", "cpu": [ "riscv64" ], @@ -2785,9 +2798,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", - "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "cpu": [ "s390x" ], @@ -2798,9 +2811,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", - "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], @@ -2811,9 +2824,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", - "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], @@ -2824,9 +2837,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", - "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], @@ -2837,9 +2850,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", - "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], @@ -2850,9 +2863,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", - "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], @@ -3067,9 +3080,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -4176,9 +4189,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4188,7 +4201,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5099,9 +5112,9 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -5838,36 +5851,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6035,12 +6048,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -9029,9 +9042,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -9056,11 +9072,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -9668,9 +9684,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -9709,10 +9725,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "license": "ISC" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9871,9 +9886,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -9889,11 +9904,10 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -10172,11 +10186,11 @@ "peer": true }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -10434,12 +10448,12 @@ } }, "node_modules/rollup": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", - "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -10449,21 +10463,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.2", - "@rollup/rollup-android-arm64": "4.13.2", - "@rollup/rollup-darwin-arm64": "4.13.2", - "@rollup/rollup-darwin-x64": "4.13.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", - "@rollup/rollup-linux-arm64-gnu": "4.13.2", - "@rollup/rollup-linux-arm64-musl": "4.13.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", - "@rollup/rollup-linux-riscv64-gnu": "4.13.2", - "@rollup/rollup-linux-s390x-gnu": "4.13.2", - "@rollup/rollup-linux-x64-gnu": "4.13.2", - "@rollup/rollup-linux-x64-musl": "4.13.2", - "@rollup/rollup-win32-arm64-msvc": "4.13.2", - "@rollup/rollup-win32-ia32-msvc": "4.13.2", - "@rollup/rollup-win32-x64-msvc": "4.13.2", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -10619,9 +10634,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -10654,6 +10669,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -10671,14 +10694,14 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -10892,9 +10915,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11163,11 +11186,10 @@ } }, "node_modules/svelte": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", - "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", + "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", @@ -11997,15 +12019,14 @@ } }, "node_modules/vite": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", - "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", + "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -12024,6 +12045,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -12041,6 +12063,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -12091,9 +12116,9 @@ } }, "node_modules/vite-plugin-eslint/node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "bin": { "rollup": "dist/bin/rollup" diff --git a/package.json b/package.json index 30f9d34cc..39b417a33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wordplay", - "version": "0.10.8", + "version": "0.12.3", "scripts": { "postinstall": "run-script-os", "postinstall:default": "svelte-kit sync && cp .env.template .env", @@ -67,7 +67,7 @@ "prettier": "3", "prettier-plugin-svelte": "^3", "run-script-os": "^1.1.6", - "svelte": "^4.2.12", + "svelte": "^4.2.19", "svelte-check": "^3.6.9", "svelte-jester": "^3.0.0", "svelte-preprocess": "^5.1.3", @@ -76,7 +76,7 @@ "tslib": "^2.6", "tsx": "^4.7.0", "typescript": "^5.5.2", - "vite": "^5.2.7", + "vite": "^5.4.7", "vite-plugin-eslint": "^1.8.1", "vitest": "^1.4.0" }, diff --git a/src/app.html b/src/app.html index a5c31c39a..e105ebf1b 100644 --- a/src/app.html +++ b/src/app.html @@ -45,9 +45,7 @@ } :root { --color-blue: #648fff; - --color-blue-transparent: #648fff10; --color-purple: #785ef0; - --color-purple-transparent: #785ef010; --color-pink: #dc267f; --color-light-pink: #f7d3e4; --color-orange: #fe6100; @@ -67,7 +65,7 @@ --wordplay-border-color: var(--color-light-grey); --wordplay-evaluation-color: var(--color-pink); --wordplay-highlight-color: var(--color-yellow); - --wordplay-focus-color: var(--color-orange); + --wordplay-focus-color: var(--color-blue); --wordplay-hover: #feebc3; --wordplay-error: var(--color-orange); --wordplay-warning: var(--color-yellow); @@ -99,6 +97,7 @@ --wordplay-palette-min-width: 5em; --wordplay-palette-max-width: 15em; --wordplay-widget-height: 1.5em; + --wordplay-min-line-height: 3ex; --bounce-height: 0.5em; --wobble-rotation: 2deg; @@ -106,9 +105,9 @@ :root .dark { --color-blue: #3a72fe; - --color-blue-transparent: #0f1e4560; + --color-blue-transparent: #0f1e45; --color-purple: #5b3ce6; - --color-purple-transparent: #26186260; + --color-purple-transparent: #261862; --color-pink: #921a54; --color-light-pink: rgb(77, 15, 45); --color-orange: #722c00; @@ -297,9 +296,9 @@ margin-top: 0; } - h1:not(:first-child), - h2:not(:first-child), - h3:not(:first-child) { + h1:not(:first-of-type), + h2:not(:first-of-type), + h3:not(:first-of-type) { margin-top: calc(2 * var(--wordplay-spacing)); } @@ -358,17 +357,6 @@ background: var(--wordplay-alternating-color); } - .firebase-emulator-warning { - top: 0 !important; - right: 1% !important; - bottom: auto !important; - left: 99% !important; - font-family: var(--wordplay-app-font) !important; - font-size: 4pt; - color: var(--wordplay-error) !important; - background: var(--wordplay-error) !important; - } - noscript { display: flex; align-items: center; diff --git a/src/basis/InternalExpression.ts b/src/basis/InternalExpression.ts index 9e7be307a..edcbe8aed 100644 --- a/src/basis/InternalExpression.ts +++ b/src/basis/InternalExpression.ts @@ -42,7 +42,7 @@ export default class InternalExpression extends SimpleExpression { } computeConflicts() { - return; + return []; } getGrammar() { diff --git a/src/basis/Iteration.ts b/src/basis/Iteration.ts index 974c8a27d..3348f299b 100644 --- a/src/basis/Iteration.ts +++ b/src/basis/Iteration.ts @@ -186,7 +186,7 @@ export class Iteration extends Expression { } computeConflicts() { - return; + return []; } // We don't clone these, we just erase their parent, since there's only one of them. diff --git a/src/components/annotations/Annotation.svelte b/src/components/annotations/Annotation.svelte index b8eaec249..b2ed0914a 100644 --- a/src/components/annotations/Annotation.svelte +++ b/src/components/annotations/Annotation.svelte @@ -3,7 +3,6 @@ import type { AnnotationInfo } from './Annotations.svelte'; import MarkupHTMLView from '../concepts/MarkupHTMLView.svelte'; import Speech from '../lore/Speech.svelte'; - import { getConceptIndex } from '../project/Contexts'; import { Projects, animationDuration, locales } from '../../db/Database'; import Button from '@components/widgets/Button.svelte'; import MarkupHtmlView from '../concepts/MarkupHTMLView.svelte'; @@ -13,10 +12,9 @@ export let id: number; export let annotations: AnnotationInfo[]; - let index = getConceptIndex(); - function resolveAnnotation(resolution: Resolution, context: Context) { - Projects.reviseProject(resolution.mediator(context)); + const {newProject} = resolution.mediator(context, $locales); + Projects.reviseProject(newProject); } @@ -32,8 +30,7 @@ }} > diff --git a/src/components/annotations/Annotations.svelte b/src/components/annotations/Annotations.svelte index 1008adedc..89e843fb5 100644 --- a/src/components/annotations/Annotations.svelte +++ b/src/components/annotations/Annotations.svelte @@ -96,13 +96,65 @@ // Wait for DOM updates so that everything is in position before we layout annotations. await tick(); - annotations = []; + // Reset the annotation list to active annotations. + annotations = conflicts + .map((conflict: Conflict) => { + const nodes = conflict.getConflictingNodes(context, Templates); + const primary = nodes.primary; + const secondary = nodes.secondary; + // Based on the primary and secondary nodes given, decide what to show. + // We expect + // 1) a single primary node + // 2) zero or more secondary nodes + // From these, we generate one or two speech bubbles to illustrate the conflict. + return [ + { + node: primary.node, + element: getNodeView(primary.node), + messages: [ + primary.explanation( + $locales, + project.getNodeContext(primary.node) ?? + project.getContext(project.getMain()), + ), + ], + kind: conflict.isMinor() + ? ('minor' as const) + : ('primary' as const), + context, + // Place the resolutions in the primary node. + resolutions: nodes.resolutions, + }, + ...(secondary !== undefined + ? [ + { + node: secondary.node, + element: getNodeView(secondary.node), + messages: [ + secondary.explanation( + $locales, + project.getNodeContext( + secondary.node, + ), + ), + ], + context, + kind: 'secondary' as const, + }, + ] + : []), + ]; + }) + .flat(); + + // If stepping, add the current evaluation. if (stepping) { const [node, view] = getStepView(); // Return a single annotation for the step. if (node && source.contains(node)) annotations = [ + ...annotations, { node: node, element: view, @@ -125,60 +177,6 @@ context, }, ]; - } else { - // Conflict all of the active conflicts to a list of annotations. - annotations = conflicts - .map((conflict: Conflict) => { - const nodes = conflict.getConflictingNodes( - context, - Templates, - ); - const primary = nodes.primary; - const secondary = nodes.secondary; - // Based on the primary and secondary nodes given, decide what to show. - // We expect - // 1) a single primary node - // 2) zero or more secondary nodes - // From these, we generate one or two speech bubbles to illustrate the conflict. - return [ - { - node: primary.node, - element: getNodeView(primary.node), - messages: [ - primary.explanation( - $locales, - project.getNodeContext(primary.node) ?? - project.getContext(project.getMain()), - ), - ], - kind: conflict.isMinor() - ? ('minor' as const) - : ('primary' as const), - context, - // Place the resolutions in the primary node. - resolutions: nodes.resolutions, - }, - ...(secondary !== undefined - ? [ - { - node: secondary.node, - element: getNodeView(secondary.node), - messages: [ - secondary.explanation( - $locales, - project.getNodeContext( - secondary.node, - ), - ), - ], - context, - kind: 'secondary' as const, - }, - ] - : []), - ]; - }) - .flat(); } // Now organize by node. @@ -200,28 +198,11 @@ // If we couldn't find a view for the node, it's probably because it was replaced by a value view. // Find the value corresponding to the node that just evaluated. if (nodeView === null) { - const currentStep = evaluator.getCurrentStep(); - if (currentStep) { - const firstExpression = - currentStep.node instanceof Expression - ? currentStep.node - : evaluator.project - .getRoot(currentStep.node) - ?.getAncestors(currentStep.node) - .find( - (a): a is Expression => - a instanceof Expression, - ); - if (firstExpression) { - const value = - evaluator.getLatestExpressionValueInEvaluation( - firstExpression, - ); - if (value) - nodeView = document.querySelector( - `.value[data-id="${value.id}"]`, - ); - } + const value = evaluator.getCurrentValue(); + if (value) { + nodeView = document.querySelector( + `.value[data-id="${value.id}"]`, + ); } } diff --git a/src/components/app/Page.svelte b/src/components/app/Page.svelte index 4b62aa109..3d302b35d 100644 --- a/src/components/app/Page.svelte +++ b/src/components/app/Page.svelte @@ -79,12 +79,8 @@ diff --git a/src/components/editor/BindView.svelte b/src/components/editor/BindView.svelte index 402b70b87..2b2bfe32b 100644 --- a/src/components/editor/BindView.svelte +++ b/src/components/editor/BindView.svelte @@ -3,23 +3,49 @@ -{#if node.type}{/if}{#if node.value}{/if} +{#if $blocks} +
+ +
{#if node.value}{/if} +
+
+{:else} + {#if node.value}{/if} +{/if} diff --git a/src/components/editor/BlockView.svelte b/src/components/editor/BlockView.svelte index 85dee60a4..0df110ba5 100644 --- a/src/components/editor/BlockView.svelte +++ b/src/components/editor/BlockView.svelte @@ -4,10 +4,29 @@ import type Block from '@nodes/Block'; import NodeSequenceView from './NodeSequenceView.svelte'; import NodeView from './NodeView.svelte'; + import { isBlocks } from '@components/project/Contexts'; export let node: Block; + + const blocks = isBlocks(); - +{#if $blocks} +
1 ? 'column' : 'row'} + />
+{:else} + +{/if} + + diff --git a/src/components/editor/BooleanTokenEditor.svelte b/src/components/editor/BooleanTokenEditor.svelte new file mode 100644 index 000000000..62ec77b39 --- /dev/null +++ b/src/components/editor/BooleanTokenEditor.svelte @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/editor/CaretView.svelte b/src/components/editor/CaretView.svelte index a09876c85..b20296dcc 100644 --- a/src/components/editor/CaretView.svelte +++ b/src/components/editor/CaretView.svelte @@ -7,6 +7,84 @@ height: number; bottom: number; }; + + export function getVerticalCenterOfBounds(rect: DOMRect) { + return rect.top + rect.height / 2; + } + + export function getHorizontalCenterOfBounds(rect: DOMRect) { + return rect.left + rect.width / 2; + } + + /** Move the caret to the nearest vertical token in the given direction and editor. */ + export function moveVisualVertical( + direction: -1 | 1, + editor: HTMLElement, + caret: Caret, + ): Caret | undefined { + // Find the token view that the caret is in. + const currentToken = + caret.position instanceof Node ? caret.position : caret.getToken(); + if (currentToken === undefined) return undefined; + const currentTokenView = getNodeView(editor, currentToken); + if (currentTokenView === null) return undefined; + const bounds = currentTokenView.getBoundingClientRect(); + const vertical = getVerticalCenterOfBounds(bounds); + const horizontal = getHorizontalCenterOfBounds(bounds); + const verticalThreshold = bounds.height; + + // Find all the token views + const nearest = Array.from(editor.querySelectorAll('.token-view')) + .map((el) => { + const elBounds = el.getBoundingClientRect(); + return { + node: + el instanceof HTMLElement && el.dataset.id + ? caret.source.getNodeByID( + parseInt(el.dataset.id), + ) ?? null + : null, + horizontal: + getHorizontalCenterOfBounds(elBounds) - horizontal, + vertical: getVerticalCenterOfBounds(elBounds) - vertical, + }; + }) + .filter( + (node) => + node.node instanceof Token && + Caret.isBlockEditable(node.node) && + // Filter out nodes in the wrong direction + (direction < 0 ? node.vertical < 0 : node.vertical > 0) && + // Filter out nodes that are too close vertically + Math.abs(node.vertical) > verticalThreshold, + ) + // Sort by closest distance of remaining + .sort( + (a, b) => + Math.pow(a.horizontal, 2) + + Math.pow(a.vertical, 2) - + (Math.pow(b.horizontal, 2) + Math.pow(b.vertical, 2)), + ); + + const closest = nearest[0]; + + if (closest && closest.node) return caret.withPosition(closest.node); + else return undefined; + } + + export function getTokenView( + editor: HTMLElement, + token: Token, + ): HTMLElement | null { + return editor.querySelector(`.token-view[data-id="${token.id}"]`); + } + + export function getNodeView( + editor: HTMLElement, + token: Node, + ): HTMLElement | null { + return editor.querySelector(`.node-view[data-id="${token.id}"]`); + } + >{#if blocks}
{/if}
diff --git a/src/components/editor/ConditionalView.svelte b/src/components/editor/ConditionalView.svelte index b6999272b..6ce21db70 100644 --- a/src/components/editor/ConditionalView.svelte +++ b/src/components/editor/ConditionalView.svelte @@ -3,10 +3,20 @@ - +{#if $blocks} + + +{:else} + +{/if} diff --git a/src/components/editor/DocView.svelte b/src/components/editor/DocView.svelte index 33bac11f6..105cbc34c 100644 --- a/src/components/editor/DocView.svelte +++ b/src/components/editor/DocView.svelte @@ -7,6 +7,23 @@ export let node: Doc; - + + + diff --git a/src/components/editor/DocsView.svelte b/src/components/editor/DocsView.svelte index a2b837fc7..03e39cd4e 100644 --- a/src/components/editor/DocsView.svelte +++ b/src/components/editor/DocsView.svelte @@ -7,4 +7,4 @@ export let node: Docs; - + diff --git a/src/components/editor/DocumentedExpressionView.svelte b/src/components/editor/DocumentedExpressionView.svelte index d57cf51ec..a248ac65e 100644 --- a/src/components/editor/DocumentedExpressionView.svelte +++ b/src/components/editor/DocumentedExpressionView.svelte @@ -3,8 +3,24 @@ - +{#if $blocks} +
+ +
+{:else} + +{/if} + + diff --git a/src/components/editor/Editor.svelte b/src/components/editor/Editor.svelte index 9ad578c86..7bd179c09 100644 --- a/src/components/editor/Editor.svelte +++ b/src/components/editor/Editor.svelte @@ -3,12 +3,7 @@ -{#each node.inputs as input}{/each}{#if nextBind}
 {#if menuPosition}{/if}
{/if} +{#if $blocks} +
+ + {#each node.inputs as input}{/each}{#if nextBind}
 {#if menuPosition} +  {/if}
{/if} + +
+{:else} + {#each node.inputs as input}{/each}{#if nextBind}
 {#if menuPosition} +  {/if}
{/if} +{/if} diff --git a/src/components/editor/ExpressionPlaceholderView.svelte b/src/components/editor/ExpressionPlaceholderView.svelte index e9f5d7fd9..b0b0e4c03 100644 --- a/src/components/editor/ExpressionPlaceholderView.svelte +++ b/src/components/editor/ExpressionPlaceholderView.svelte @@ -3,17 +3,24 @@ - - - + diff --git a/src/components/editor/GlyphChooser.svelte b/src/components/editor/GlyphChooser.svelte index e47111614..0ce92577a 100644 --- a/src/components/editor/GlyphChooser.svelte +++ b/src/components/editor/GlyphChooser.svelte @@ -34,9 +34,17 @@ ); function insert(glyph: string) { - const editor = $editors?.get(sourceID); - if (editor) { - editor.edit(editor.caret.insert(glyph), IdleKind.Typed, true); + const editorState = $editors?.get(sourceID); + if (editorState) { + editorState.edit( + editorState.caret.insert( + glyph, + editorState.blocks, + editorState.project, + ), + IdleKind.Typed, + true, + ); } } diff --git a/src/components/editor/InsertionPointView.svelte b/src/components/editor/InsertionPointView.svelte index 9b8628136..20e3ec258 100644 --- a/src/components/editor/InsertionPointView.svelte +++ b/src/components/editor/InsertionPointView.svelte @@ -4,7 +4,7 @@ .insertion-point { display: inline-block; vertical-align: middle; - height: 3ex; + height: var(--wordplay-min-line-height); width: 0; outline: 3px solid var(--wordplay-highlight-color); } diff --git a/src/components/editor/LanguageView.svelte b/src/components/editor/LanguageView.svelte index 3de4f183d..cd898146a 100644 --- a/src/components/editor/LanguageView.svelte +++ b/src/components/editor/LanguageView.svelte @@ -3,18 +3,26 @@ - diff --git a/src/components/editor/MatchView.svelte b/src/components/editor/MatchView.svelte index c6abd6f79..c32e0ed9c 100644 --- a/src/components/editor/MatchView.svelte +++ b/src/components/editor/MatchView.svelte @@ -8,6 +8,6 @@ export let node: Match; - + diff --git a/src/components/editor/Menu.svelte b/src/components/editor/Menu.svelte index 8010800b3..e97af8071 100644 --- a/src/components/editor/Menu.svelte +++ b/src/components/editor/Menu.svelte @@ -1,18 +1,13 @@ + +
handleItemClick(entry)} + class={`revision ${menu.getSelection() === entry ? 'selected' : ''}`} + on:pointerdown|stopPropagation|preventDefault={() => handleItemClick(entry)} + on:focusin={() => { + const index = menu.getSelectionFor(entry); + if (index) menu = menu.withSelection(index); + }} +> + {#if newNode !== undefined} + {#if entry.isRemoval()} + + {:else} + + {/if} + {:else} + + {/if} + + +
+ + diff --git a/src/components/editor/PlaceholderView.svelte b/src/components/editor/MenuTrigger.svelte similarity index 72% rename from src/components/editor/PlaceholderView.svelte rename to src/components/editor/MenuTrigger.svelte index 768480399..9ddb2b0f5 100644 --- a/src/components/editor/PlaceholderView.svelte +++ b/src/components/editor/MenuTrigger.svelte @@ -1,14 +1,15 @@ @@ -18,13 +19,14 @@ tabindex="0" on:pointerdown|stopPropagation={show} on:keydown|stopPropagation={(event) => - event.key === 'Enter' || event.key === ' ' ? show() : undefined}>▼▾ diff --git a/src/components/editor/NodeView.svelte b/src/components/editor/NodeView.svelte index 01d7aa1c7..7afeea382 100644 --- a/src/components/editor/NodeView.svelte +++ b/src/components/editor/NodeView.svelte @@ -8,6 +8,7 @@ getInsertionPoint, getRoot, getSpace, + isBlocks, } from '../project/Contexts'; import getNodeView from './util/nodeToView'; import Expression, { ExpressionKind } from '@nodes/Expression'; @@ -15,10 +16,13 @@ import type Value from '@values/Value'; import Space from './Space.svelte'; import Token from '../../nodes/Token'; - import { blocks, locales } from '../../db/Database'; + import { locales } from '../../db/Database'; + import InsertionPointView from './InsertionPointView.svelte'; + import Block from '@nodes/Block'; export let node: Node | undefined; export let small = false; + export let direction: 'row' | 'column' = 'row'; const evaluation = getEvaluation(); const root = getRoot(); @@ -45,12 +49,11 @@ node instanceof Expression && !node.isEvaluationInvolved() ) - value = - $evaluation.evaluator.getLatestExpressionValueInEvaluation( - node, - ); + value = $evaluation.evaluator.getLatestExpressionValue(node); } + const blocks = isBlocks(); + // Get the root's computed spaces store let spaces = getSpace(); // See if this node has any space to render. @@ -67,28 +70,68 @@ $: kind = $blocks && node instanceof Expression ? node.getKind() : undefined; + + function symbolOccurs(text: string, symbol: string) { + for (let i = 0; i < text.length; i++) + if (text.charAt(i) === symbol) return true; + return false; + } + + function countSymbolOccurences(text: string, symbol: string) { + let count = 0; + for (let i = 0; i < text.length; i++) + if (text.charAt(i) === symbol) count++; + return count; + } {#if node !== undefined} - {#if !hide && firstToken && spaceRoot === node}{/if}
+ {#if hasSpace}
{#if firstToken && $insertion?.token === firstToken}{/if} 
{:else}{#each lines as line}
{#if $insertion && $insertion.list[$insertion.index] === node && $insertion.line === line}{/if}
{/each}{/if}{/key}{/if}{:else} + {/if}{/if}
diff --git a/src/components/editor/NumberTokenEditor.svelte b/src/components/editor/NumberTokenEditor.svelte new file mode 100644 index 000000000..51b71e85b --- /dev/null +++ b/src/components/editor/NumberTokenEditor.svelte @@ -0,0 +1,24 @@ + + + { + const tokens = toTokens(newNumber); + return tokens.remaining() === 2 && tokens.nextIs(Sym.Number); + }} + creator={(text) => new Token(text, Sym.Number)} +/> diff --git a/src/components/editor/OperatorEditor.svelte b/src/components/editor/OperatorEditor.svelte new file mode 100644 index 000000000..e7412d3a6 --- /dev/null +++ b/src/components/editor/OperatorEditor.svelte @@ -0,0 +1,9 @@ + + + diff --git a/src/components/editor/ReferenceTokenEditor.svelte b/src/components/editor/ReferenceTokenEditor.svelte new file mode 100644 index 000000000..31cd755a3 --- /dev/null +++ b/src/components/editor/ReferenceTokenEditor.svelte @@ -0,0 +1,8 @@ + + + diff --git a/src/components/editor/StructureDefinitionView.svelte b/src/components/editor/StructureDefinitionView.svelte index 4c750158f..7bbc23d70 100644 --- a/src/components/editor/StructureDefinitionView.svelte +++ b/src/components/editor/StructureDefinitionView.svelte @@ -4,14 +4,52 @@ import type StructureDefinition from '@nodes/StructureDefinition'; import NodeSequenceView from './NodeSequenceView.svelte'; import NodeView from './NodeView.svelte'; + import { isBlocks } from '@components/project/Contexts'; export let node: StructureDefinition; + + const blocks = isBlocks(); - +{#if $blocks} +
+ +
+
+{:else} + +{/if} + + diff --git a/src/components/editor/TextLiteralView.svelte b/src/components/editor/TextLiteralView.svelte index 079938dba..3d2e0ab21 100644 --- a/src/components/editor/TextLiteralView.svelte +++ b/src/components/editor/TextLiteralView.svelte @@ -7,4 +7,4 @@ export let node: TextLiteral; - + diff --git a/src/components/editor/TextOrPlaceholder.svelte b/src/components/editor/TextOrPlaceholder.svelte new file mode 100644 index 000000000..58ed5fbf0 --- /dev/null +++ b/src/components/editor/TextOrPlaceholder.svelte @@ -0,0 +1,17 @@ + + +{#if placeholder !== undefined}{placeholder}{:else if text.length === 0}​{:else}{rendered}{/if} + + diff --git a/src/components/editor/TokenEditor.svelte b/src/components/editor/TokenEditor.svelte new file mode 100644 index 000000000..c2b07f9af --- /dev/null +++ b/src/components/editor/TokenEditor.svelte @@ -0,0 +1,119 @@ + + + { + // Not valid but losing focus? Restore the old text. + if (validator !== undefined && validator(newText) === false) { + text = token.getText(); + } + }} + changed={handleChange} +> diff --git a/src/components/editor/TokenView.svelte b/src/components/editor/TokenView.svelte index 78a6b87bf..981224b11 100644 --- a/src/components/editor/TokenView.svelte +++ b/src/components/editor/TokenView.svelte @@ -1,18 +1,30 @@ -{#if placeholder !== undefined}{placeholder}{:else if text.length === 0}​{:else}{renderedText}{/if} +{#if $blocks && $root} +
+ {#if editable && $project && context && (node.isSymbol(Sym.Name) || node.isSymbol(Sym.Operator) || node.isSymbol(Sym.Words) || node.isSymbol(Sym.Number) || node.isSymbol(Sym.Boolean))} + {#if node.isSymbol(Sym.Words)} + {:else if node.isSymbol(Sym.Boolean)} + {:else if node.isSymbol(Sym.Number)}{:else} + {@const parent = $root.getParent(node)} + + {#if parent instanceof Name} + + {:else if parent instanceof Reference} + {@const grandparent = $root.getParent(parent)} + + {#if grandparent && (grandparent instanceof BinaryEvaluate || grandparent instanceof UnaryEvaluate) && grandparent.fun === parent} + + {:else} + + {/if} + {:else}{/if} + {/if} + {:else}{/if} +
+{:else} + + + +{/if} diff --git a/src/components/editor/TranslationView.svelte b/src/components/editor/TranslationView.svelte index 6e56bf10c..c463ec862 100644 --- a/src/components/editor/TranslationView.svelte +++ b/src/components/editor/TranslationView.svelte @@ -4,10 +4,23 @@ import NodeView from './NodeView.svelte'; import type Translation from '../../nodes/Translation'; import NodeSequenceView from './NodeSequenceView.svelte'; + import { isBlocks } from '@components/project/Contexts'; export let node: Translation; + + const blocks = isBlocks(); - +{#if $blocks} + +{:else} + +{/if} diff --git a/src/components/editor/UnparsableExpressionView.svelte b/src/components/editor/UnparsableExpressionView.svelte index 0ff53678e..675389eed 100644 --- a/src/components/editor/UnparsableExpressionView.svelte +++ b/src/components/editor/UnparsableExpressionView.svelte @@ -2,9 +2,9 @@ - + diff --git a/src/components/editor/UnparsableTypeView.svelte b/src/components/editor/UnparsableTypeView.svelte index 7c3939f7b..69c08ac61 100644 --- a/src/components/editor/UnparsableTypeView.svelte +++ b/src/components/editor/UnparsableTypeView.svelte @@ -2,9 +2,9 @@ - + diff --git a/src/components/editor/UnparsableView.svelte b/src/components/editor/UnparsableView.svelte new file mode 100644 index 000000000..8fa22ff31 --- /dev/null +++ b/src/components/editor/UnparsableView.svelte @@ -0,0 +1,23 @@ + + + + +{#if node.unparsables.length > 0} + +{:else} {/if} + + diff --git a/src/components/editor/WordsTokenEditor.svelte b/src/components/editor/WordsTokenEditor.svelte new file mode 100644 index 000000000..b69614a35 --- /dev/null +++ b/src/components/editor/WordsTokenEditor.svelte @@ -0,0 +1,21 @@ + + + newWords === '' || WordsRegEx.test(newWords)} + creator={(text) => (text === '' ? undefined : new Token(text, Sym.Words))} +/> diff --git a/src/components/editor/WordsView.svelte b/src/components/editor/WordsView.svelte index 74f743fd7..76f079931 100644 --- a/src/components/editor/WordsView.svelte +++ b/src/components/editor/WordsView.svelte @@ -4,17 +4,25 @@ import type Words from '@nodes/Words'; import NodeView from './NodeView.svelte'; import NodeSequenceView from './NodeSequenceView.svelte'; + import { isBlocks } from '@components/project/Contexts'; export let node: Words; + + const blocks = isBlocks(); - diff --git a/src/components/palette/BindText.svelte b/src/components/palette/BindText.svelte index a8df82b99..6ebb21354 100644 --- a/src/components/palette/BindText.svelte +++ b/src/components/palette/BindText.svelte @@ -15,6 +15,7 @@ getLanguageQuoteClose, getLanguageQuoteOpen, } from '@locale/LanguageCode'; + import setKeyboardFocus from '@components/util/setKeyboardFocus'; export let property: OutputProperty; export let values: OutputPropertyValues; @@ -48,7 +49,11 @@ ); await tick(); - view?.focus(); + if (view) + setKeyboardFocus( + view, + 'Restoring bind text editor focus after edit.', + ); } diff --git a/src/components/palette/ContentEditor.svelte b/src/components/palette/ContentEditor.svelte index 89314789f..a61606ff5 100644 --- a/src/components/palette/ContentEditor.svelte +++ b/src/components/palette/ContentEditor.svelte @@ -10,7 +10,7 @@ } from '../project/Contexts'; import { addContent, moveContent, removeContent } from './editOutput'; import type ListLiteral from '../../nodes/ListLiteral'; - import { DB, locales } from '@db/Database'; + import { blocks, DB, locales } from '@db/Database'; import { EDIT_SYMBOL } from '../../parser/Symbols'; export let project: Project; @@ -82,7 +82,11 @@ active={editable} action={() => editContent(index)}>{EDIT_SYMBOL} - +
{/each}
diff --git a/src/components/palette/PaletteProperty.svelte b/src/components/palette/PaletteProperty.svelte index e9d43d9e6..2e157badd 100644 --- a/src/components/palette/PaletteProperty.svelte +++ b/src/components/palette/PaletteProperty.svelte @@ -28,6 +28,7 @@ import PlacementEditor from './PlacementEditor.svelte'; import NamedControl from './NamedControl.svelte'; import AuraEditor from './AuraEditor.svelte'; + import setKeyboardFocus from '@components/util/setKeyboardFocus'; export let project: Project; export let property: OutputProperty; @@ -46,7 +47,8 @@ else values.unset(DB, project, $locales); // Preserve focus on toggle button after setting. await tick(); - toggleView?.focus(); + if (toggleView) + setKeyboardFocus(toggleView, 'Restoring focus after toggle'); } diff --git a/src/components/palette/PlaceEditor.svelte b/src/components/palette/PlaceEditor.svelte index 22ba37d00..d8b8167e6 100644 --- a/src/components/palette/PlaceEditor.svelte +++ b/src/components/palette/PlaceEditor.svelte @@ -13,6 +13,7 @@ import Button from '../widgets/Button.svelte'; import type Bind from '../../nodes/Bind'; import NumberType from '../../nodes/NumberType'; + import setKeyboardFocus from '@components/util/setKeyboardFocus'; export let project: Project; export let place: Evaluate; @@ -51,7 +52,11 @@ if (focusIndex >= 0) { await tick(); const view = views[focusIndex]; - view?.focus(); + if (view) + setKeyboardFocus( + view, + 'Restoring focus after place editor edit.', + ); } } @@ -105,7 +110,7 @@ →{project.shares.input.Motion.getNames()[0]} →{project.shares.input.Placement.getNames()[0]} {/if} diff --git a/src/components/palette/VelocityEditor.svelte b/src/components/palette/VelocityEditor.svelte index 52b3294e3..1761670a6 100644 --- a/src/components/palette/VelocityEditor.svelte +++ b/src/components/palette/VelocityEditor.svelte @@ -11,6 +11,7 @@ import { Projects, locales } from '../../db/Database'; import { tick } from 'svelte'; import type Bind from '../../nodes/Bind'; + import setKeyboardFocus from '@components/util/setKeyboardFocus'; export let project: Project; export let velocity: Evaluate; @@ -48,7 +49,11 @@ if (focusIndex >= 0) { await tick(); const view = views[focusIndex]; - view?.focus(); + if (view) + setKeyboardFocus( + view, + 'Restoring focus after velocity editor edit.', + ); } } diff --git a/src/components/project/Announcer.svelte b/src/components/project/Announcer.svelte index 4210267e6..68a286ca4 100644 --- a/src/components/project/Announcer.svelte +++ b/src/components/project/Announcer.svelte @@ -23,6 +23,12 @@ // Is there a timeout? Wait for it to dequue. if (timeout) return; + // Have we fallen behind? Trim everything by the most recent. + if (announcements.length > 3) { + const mostRecent = announcements.shift(); + if (mostRecent) announcements = [mostRecent]; + } + // Grab the message of a different kind from the current message, or the next one if there aren't any. let next = announcements.pop(); announcements = []; @@ -40,12 +46,15 @@ const wordsPerSecond = 3; const secondsToRead = wordCount * (1 / wordsPerSecond); - // Dequeue - timeout = setTimeout(() => { - // It's been a second. Clear the timeout (so the dequeue does something above), then dequeue to update the announncement. - timeout = undefined; - dequeue(); - }, secondsToRead * delay); + // Dequeue after the amount of reading time it takes, or 2 seconds, whatever is shorter. + timeout = setTimeout( + () => { + // It's been a second. Clear the timeout (so the dequeue does something above), then dequeue to update the announncement. + timeout = undefined; + dequeue(); + }, + Math.min(2000, secondsToRead * delay), + ); } } } @@ -60,7 +69,7 @@ diff --git a/src/components/project/RootView.svelte b/src/components/project/RootView.svelte index 48dcba9da..8fcd1b0e9 100644 --- a/src/components/project/RootView.svelte +++ b/src/components/project/RootView.svelte @@ -16,6 +16,7 @@ CaretSymbol, LocalizeSymbol, ShowLinesSymbol, + BlocksSymbol, } from './Contexts'; import Root from '@nodes/Root'; import Source from '@nodes/Source'; @@ -30,7 +31,11 @@ export let node: Node; /** Optional space. To enable preferred space, set flag below. */ export let spaces: Spaces | undefined = undefined; + /** Whether to render as blocks */ + export let blocks: boolean; + /** Whether to be read only */ export let inert = false; + /** Whether to render inline */ export let inline = false; /** If inline, and true, this will be a maximum width */ export let elide = false; @@ -69,6 +74,10 @@ setContext(ShowLinesSymbol, showLines); $: showLines.set(lines); + let isBlocks = writable(blocks); + setContext(BlocksSymbol, isBlocks); + $: isBlocks.set(blocks); + // Update what's hidden when locales or localized changes. $: { $locales; @@ -160,8 +169,11 @@ class:elide> {:else} - {/if} diff --git a/src/components/util/setKeyboardFocus.ts b/src/components/util/setKeyboardFocus.ts new file mode 100644 index 000000000..60ac10007 --- /dev/null +++ b/src/components/util/setKeyboardFocus.ts @@ -0,0 +1,21 @@ +const DEBUG_FOCUS = false; + +/** A helper function that wraps HTMLElement.focus() in order to help with focus debugging. All + * front end code in the project should use this function to focus elements. This function will + * log a message to the console if DEBUG_FOCUS is set to true. + */ +export default function setKeyboardFocus( + element: HTMLElement, + message: string, +) { + if (document.activeElement !== element) { + if (DEBUG_FOCUS) { + console.log(`New focus: ${message}`); + console.log('\tFrom', document.activeElement); + console.log('\tTo: ', element); + } + element.focus(); + } else if (DEBUG_FOCUS) { + console.log(`Already focused: ${message}`); + } +} diff --git a/src/components/values/ValueView.svelte b/src/components/values/ValueView.svelte index fcbd27bdf..65b458c8c 100644 --- a/src/components/values/ValueView.svelte +++ b/src/components/values/ValueView.svelte @@ -29,4 +29,8 @@ word-break: break-all; } + + :global(.value.evaluating) { + color: var(--wordplay-background); + } diff --git a/src/components/widgets/CommandButton.svelte b/src/components/widgets/CommandButton.svelte index b639a4a64..617fdf6fa 100644 --- a/src/components/widgets/CommandButton.svelte +++ b/src/components/widgets/CommandButton.svelte @@ -12,6 +12,7 @@ import { tick } from 'svelte'; import CommandHint from './CommandHint.svelte'; import Emoji from '@components/app/Emoji.svelte'; + import setKeyboardFocus from '@components/util/setKeyboardFocus'; /** If source ID isn't provided, then the one with focus is used. */ export let sourceID: string | undefined = undefined; @@ -71,7 +72,11 @@ // If we didn't ask the editor to focus, restore focus on button after update. if (!focusAfter && hadFocus) { await tick(); - view?.focus(); + if (view) + setKeyboardFocus( + view, + 'Focusing on button after command if it previously had focus.', + ); } }} >{#if token} view?.focus()); + tick().then(() => + view + ? setKeyboardFocus(view, 'Focusing dialog') + : undefined, + ); } else { view.close(); } diff --git a/src/components/widgets/Options.svelte b/src/components/widgets/Options.svelte index b203c53d9..8bdfc3ca4 100644 --- a/src/components/widgets/Options.svelte +++ b/src/components/widgets/Options.svelte @@ -10,22 +10,26 @@ @@ -38,6 +42,7 @@ bind:this={view} style:width disabled={!editable} + class:code > {#each options as option} {#if 'options' in option} @@ -68,4 +73,8 @@ border: var(--wordplay-border-color) solid var(--wordplay-border-width); border-radius: var(--wordplay-border-radius); } + + .code { + font-family: var; + } diff --git a/src/components/widgets/Slider.svelte b/src/components/widgets/Slider.svelte index 8553264dd..4fbb49f4b 100644 --- a/src/components/widgets/Slider.svelte +++ b/src/components/widgets/Slider.svelte @@ -1,4 +1,5 @@ diff --git a/src/components/widgets/TextField.svelte b/src/components/widgets/TextField.svelte index f511e3459..fb5f5292f 100644 --- a/src/components/widgets/TextField.svelte +++ b/src/components/widgets/TextField.svelte @@ -1,6 +1,7 @@