From 5fc6a2fcdf367110dcef74497af187af096494f9 Mon Sep 17 00:00:00 2001 From: gossi Date: Tue, 4 Jul 2023 15:05:40 +0200 Subject: [PATCH 1/5] Documentation for ember-link --- .gitignore | 5 + README.md | 10 +- docs/.vitepress/config.ts | 58 ++ docs/.vitepress/sidebar-api.ts | 89 +++ docs/api-examples.md | 49 ++ docs/behavior.md | 45 ++ docs/commands.md | 41 ++ docs/configuration.md | 38 ++ docs/customization.md | 83 +++ docs/getting-started.md | 84 +++ docs/helper.md | 99 +++ docs/index.md | 25 + docs/installation.md | 7 + docs/link-builder.md | 98 +++ docs/markdown-examples.md | 85 +++ docs/migration.md | 10 + docs/references.md | 15 + docs/service.md | 25 + docs/testing.md | 47 ++ docs/using-locales.md | 82 +++ docs/using-primitives.md | 72 +++ ember-link/README.md | 6 - ember-link/package.json | 2 + ember-link/src/helpers/link.ts | 4 +- ember-link/src/index.ts | 9 + ember-link/src/test-support/index.ts | 7 + ember-link/typedoc.json | 16 + package.json | 16 +- pnpm-lock.yaml | 878 ++++++++++++++++++++++++++- 29 files changed, 1979 insertions(+), 26 deletions(-) create mode 100644 docs/.vitepress/config.ts create mode 100644 docs/.vitepress/sidebar-api.ts create mode 100644 docs/api-examples.md create mode 100644 docs/behavior.md create mode 100644 docs/commands.md create mode 100644 docs/configuration.md create mode 100644 docs/customization.md create mode 100644 docs/getting-started.md create mode 100644 docs/helper.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/link-builder.md create mode 100644 docs/markdown-examples.md create mode 100644 docs/migration.md create mode 100644 docs/references.md create mode 100644 docs/service.md create mode 100644 docs/testing.md create mode 100644 docs/using-locales.md create mode 100644 docs/using-primitives.md create mode 100644 ember-link/typedoc.json diff --git a/.gitignore b/.gitignore index 2a953744..1e4179bf 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ node_modules/ /.node_modules.ember-try/ /package.json.ember-try /yarn.lock.ember-try + +#vitepress +/docs/.vitepress/dist/ +/docs/.vitepress/cache/ +/docs/api diff --git a/README.md b/README.md index 0397f06c..8886ae7b 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,10 @@ [![npm version](https://badge.fury.io/js/ember-link.svg)](http://badge.fury.io/js/ember-link) [![Download Total](https://img.shields.io/npm/dt/ember-link.svg)](http://badge.fury.io/js/ember-link) [![Ember Observer Score](https://emberobserver.com/badges/ember-link.svg)](https://emberobserver.com/addons/ember-link) -[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) -[![Dependabot enabled](https://img.shields.io/badge/dependabot-enabled-blue.svg?logo=dependabot)](https://dependabot.com/) -[![dependencies Status](https://david-dm.org/buschtoens/ember-link/status.svg)](https://david-dm.org/buschtoens/ember-link) -[![devDependencies Status](https://david-dm.org/buschtoens/ember-link/dev-status.svg)](https://david-dm.org/buschtoens/ember-link?type=dev) Introduces a new `Link` primitive to pass around self-contained references to -routes, like URLs, but with state (`isActive`, ...) and methods (`transitionTo`, -...). Also brings along an accompanying template helper and component for easy +routes, like URLs, but with state (`isActive`, ...) and methods (`open`, +...). Also brings along an accompanying template helper for easy usage in templates. > `ember-link` does to routing what `ember-concurrency` did to asynchrony! @@ -20,7 +16,7 @@ usage in templates. ## Installation -``` +```sh ember install ember-link ``` diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 00000000..17d31197 --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,58 @@ +import { defineConfig } from 'vitepress'; +import { getSidebar } from './sidebar-api'; + +const apiSidebar = getSidebar('api'); + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "ember-link", + description: "Links as Primitives", + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Guides', link: '/getting-started' }, + { text: 'API', link: '/api/modules'} + ], + + outline: [2, 3], + + sidebar: { + '/': [ + { + text: 'Guides', + items: [ + { text: 'Getting Started', link: '/getting-started' }, + { text: 'Installation', link: '/installation' }, + { text: 'Configuration', link: '/configuration' }, + { text: 'Migration', link: '/migration' } + ] + }, + { + text: 'Usage', + items: [ + { text: 'Using Primitives', link: '/using-primitives' }, + { text: 'Behavior', link: '/behavior' }, + { text: 'Link Helper', link: '/helper' }, + { text: 'LinkManager Service', link: '/service' }, + { text: 'Testing', link: '/testing' } + ] + }, + { + text: 'Advanced', + items: [ + { text: 'Customization', link: '/customization' }, + { text: 'Using Locales', link: '/using-locales' }, + { text: 'Link Builder', link: '/link-builder' }, + { text: 'Commands', link: '/commands' } + ] + } + ], + '/api/': apiSidebar + }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/buschtoens/ember-link' } + ] + } +}) diff --git a/docs/.vitepress/sidebar-api.ts b/docs/.vitepress/sidebar-api.ts new file mode 100644 index 00000000..2bdf0aa9 --- /dev/null +++ b/docs/.vitepress/sidebar-api.ts @@ -0,0 +1,89 @@ +import { DefaultTheme } from 'vitepress'; +import data from '../api/data.json'; + +enum Kind { + Project = 1, + Module = 2 << 0, + Function = 2 << 5, + Class = 2 << 6, + Interface = 2 << 7, + Type = 2 << 21 +} + +const KindToSlugMap = { + [Kind.Module]: 'modules', + [Kind.Function]: 'modules', + [Kind.Class]: 'classes', + [Kind.Interface]: 'interfaces', + [Kind.Type]: 'modules' +} + +interface Entry { + name: string, + kind: Kind, + module?: string; +} + +function parseSegment(name) { + return name.replaceAll('/', '_').replaceAll('-', '_'); +} + +function slugForKind(kind: Kind) { + return KindToSlugMap[kind]; +} + +function slug(path, entry: Entry) { + const file = (entry: Entry) => { + const parts = []; + + if (entry.module) { + parts.push(parseSegment(entry.module) as never); + } + + if (slugForKind(entry.kind) !== 'modules' || entry.kind === Kind.Module) { + parts.push(parseSegment(entry.name) as never); + } + + return parts.join('.'); + } + + const segments = [path, slugForKind(entry.kind), file(entry)]; + + let slug = `/${segments.join('/')}`; + + if (entry.kind === Kind.Function || entry.kind === Kind.Type) { + slug += `#${entry.name.toLowerCase()}`; + } + + return slug; +} + +function getModuleChildren(data, path, module): DefaultTheme.SidebarItem[] { + return data.map(entry => { + return { + text: entry.name, + link: slug(path, { ...entry, module }) + } + }); +} + +function getModules(data, path): DefaultTheme.SidebarItem[] { + const modules: DefaultTheme.SidebarItem[] = data.children.map(entry => { + const item: DefaultTheme.SidebarItem = { + text: entry.name, + link: slug(path, entry), + }; + + if (entry.children) { + item.items = getModuleChildren(entry.children, path, entry.name); + } + + return item; + }); + + return modules; +} + +export function getSidebar(path): DefaultTheme.SidebarItem[] { + return getModules(data, path); +} diff --git a/docs/api-examples.md b/docs/api-examples.md new file mode 100644 index 00000000..6bd8bb5c --- /dev/null +++ b/docs/api-examples.md @@ -0,0 +1,49 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/behavior.md b/docs/behavior.md new file mode 100644 index 00000000..ba06ba83 --- /dev/null +++ b/docs/behavior.md @@ -0,0 +1,45 @@ +# Behavior + +[`Behavior`](./api/interfaces/ember_link.Behavior.md) controls the way links are +opened. This can be [configured globally](./configuration.md) or +[locally with each link](./helper.md#parameters). + +## `open` + +When links are opened with [`link.open`](./api/classes/ember_link.Link.md#open) +it will use +[`Router.transitionTo()`](https://api.emberjs.com/ember/5.0/classes/RouterService/methods/transitionTo?anchor=transitionTo) +by default, but you can set it to `replace` to use +[`Router.replaceWith()`](https://api.emberjs.com/ember/5.0/classes/RouterService/methods/replaceWith?anchor=replaceWith). + +## `prevent` + +Preventing will stop the browser from natively opening the link. The default +behavior implemented here is to mimic browser behavior but move control to +ember's routing system, as you'd expect. However, you can customize rsp. extend +the default behavior: + +```ts +import { prevent as originalPrevent } from 'ember-link'; + +function applicationPrevent(event: Event | unknown, link: Link): boolean { + // something very specific to the entire application +} + +function preventForLink(link: Link): boolean { + // something custom for a given link +} + +function prevent(event: Event | unknown, link: Link) { + return ( + originalPrevent(event) + || applicationPrevent(event, link) + || preventForLink(link) + ); +} +``` + +provide global rules in `applicationPrevent()` that address your entire +application. As `Link` instance is passed as well, there can be a prevent for a +given link, that is [customized](./customization.md) with application specific +behavior. diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..2cb57b1e --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,41 @@ +# Commands + +Sometimes, links are not enough to encode the information you want to pass down +as primitive, this leads to unelegant situations, such as: + +```hbs + +``` + +Information is spread apart and _two_ primitives are passed down for _one_ +action. This is where [`ember-command`](https://github.com/gossi/ember-command) +helps out. It can run any amount of arbitrary actions and take a link for +backpack. It is designed to work together with `ember-link`. The example from +above rewritten with `ember-command`: + +```hbs + +``` + +And `ember-command` provides `` component to approprietly render +the command you pass in. + +```hbs +{{! checkout-reward-screen.hbs}} + +

Congratulations on your Purchase

+ + + Return to Home + +``` + + diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..6ee4dd2f --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,38 @@ +# Configuration + +The [behavior](./behavior.md) of opening links is very much under your control. +You can configure it globally or locally and there are reasons for both. + +## Global Behavior + +Global configuration is happening through the +[`LinkManagerService`](./api/classes/ember_link.LinkManagerService.md) to match +the needs for your application: + +```ts +import Route from '@ember/routing/route'; +import { service } from '@ember/service'; + +export default class ApplicationRoute extends Route { + @service declare linkManager: LinkManagerService; + + beforeModel() { + this.linkManager.configureBehavior({ + ... + }); + } +} +``` + +## Local Behavior + +Local behavior can be applied for each link instance and _may_ overwrite +globally configured behavior. Pass the `behavior` parameter to a +[`Link`](./api/classes/ember_link.Link.md) instance. + +## Global vs. Local Behavior + +The way it is designed, **application** specific behavior is part of **global** +configuration. + +For addon authors, it is best advised to use **local** behavior. diff --git a/docs/customization.md b/docs/customization.md new file mode 100644 index 00000000..432f685d --- /dev/null +++ b/docs/customization.md @@ -0,0 +1,83 @@ +# Customization + +While `ember-link` focus on its strength at its core, connecting link primitives +with embers routing system, it leaves customization to you. + +## Custom Attributes + +For example, you may want to carry around more attributes than provided out of +the box. This is a great idea when defining links and using links are two +separate parties and you want to attach the link to the DOM as the intended on +definition side. + +::: code-group + +```gts [post.gts] +import { link } from 'ember-link'; +import { attachLinkAttributes } from './attributes'; +import AuthorLink from './author-link'; + + +``` + +```gts [author-link.gts] +import { attributesForLink } from './attributes'; + + +``` + +```ts [attributes.ts] +import { helper } from '@ember/component/helper'; +import { modifier } from 'ember-modifier'; +import type { Link } from 'ember-link'; + +const Attributes = Symbol('attributes'); + +export interface AttachLinkAttributesSignature { + Args: { + Positional: [Link]; + Named: HTMLAnchorElement; // yeah, sorta + }; + Return: Link; +} + +const attachLinkAttributes = helper(([link], attributes) => { + link[Attributes] = attributes; +}); + + +export interface AttributesForLinkSignature { + Element: HTMLAnchorElement; + Args: { + Positional: [Link]; + }; +} + +const attributesForLink = modifier((element, [link]) => { + for (const [attr, val] of Object.entries(link[Attributes])) { + element[attr] = val; + } +}); + +export { attachLinkAttributes, attributesForLink }; +``` + +::: diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 00000000..54b72225 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,84 @@ +# Getting Started + +## Installation + +Install `ember-link` with: + +```sh +ember install ember-link +``` + +## Usage + +You can use `ember-link` in a declarative form with a [`(link)` +helper](helper.md) or imperatively with the [`LinkManager` +Service](./service.md). + +### `(link)` Helper Example + +Use the `(link)` helper to create a link primitive and attach it to an element. + +```hbs +{{#let (link "about") as |l|}} + + About us + +{{/let}} +``` + +### `LinkManager` Service Example + +Use the `LinkManager.createLink()` method to create a link programmatically. + +```ts +import Contoller from '@ember/controller'; +import { service } from '@ember/service'; +import type { LinkManagerService } from 'ember-link'; + +export default class PageHeader extends Controller { + @service declare linkManager: LinkManagerService; + + aboutUsLink = this.linkManager.createLink('about'); +} +``` + +### Working with Primitives + +The idea of `ember-link` is to be able to create link primitives, that you can +pass around. Create links at route level and then pass them into components. + +A more in-depth guide is available at [using primitives](./using-primitives.md). + +## Testing + +[ember-link has testing support](./testing.md) on board, preparing the environment with +`setupLink()` and `linkFor()` to create a link to a route on the fly: + +```ts +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +import { setupLink, linkFor, TestLink } from 'ember-link/test-support'; + +module('`setupLink` example', function (hooks) { + setupRenderingTest(hooks); + setupLink(hooks); + + test('`(link)` works in render tests', async function (assert) { + const link = linkFor('some.route'); + link.onTransitionTo = assert.step('link clicked'); + + await render(hbs` + {{#let (link @route="some.route") as |l|}} + Click me + {{/let}} + `); + + await click('a'); + + assert.verifySteps(['link clicked']); + }); +}); +``` diff --git a/docs/helper.md b/docs/helper.md new file mode 100644 index 00000000..77c79233 --- /dev/null +++ b/docs/helper.md @@ -0,0 +1,99 @@ +# `(link)` Helper + +The `(link)` helper returns a `Link` instance. + +## Invocation Styles + +### Positional Parameters + +```hbs +{{#let + (link + "blogs.posts.post" + @post.blog.id + @post.id + (query-params showFullPost=true) + ) + as |l| +}} + + Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! + +{{/let}} +``` + +### Named Parameters + +```hbs +{{#let + (link + route="blogs.posts.post" + models=(array @post.blog.id @post.id) + query=(hash showFullPost=true) + ) + as |l| +}} + + Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! + +{{/let}} +``` + +When passing a single model, you can use `model` instead of `models`: + +```hbs +{{#let + (link + route="blogs.posts" + model=@post.blog.id + ) + as |l| +}} + + Read more stories in the {{@post.blog.name}} blog! + +{{/let}} +``` + +### Mix & Match + +You can also mix and match the parameter styles, however you like. + +```hbs +{{#let + (link + "blogs.posts.post" + @post.blog.id + @post.id + query=(hash showFullPost=true) + ) + as |l| +}} + + Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! + +{{/let}} +``` + +### `fromURL` + +Instead of the positional & named link parameters described above, you can also +create a `Link` instance from a serialized URL. + +```hbs +{{! someURL = "/blogs/tech/posts/dont-break-the-web" }} +{{#let (link fromURL=this.someURL) as |l|}} + + Read the next great post. + +{{/let}} +``` + +`fromURL` is mutually exclusive with the other link parameters: `route`, `model` +& `models`, `query` + +## Parameters + +In addition to the parameters shown above, the `(link)` helper also accepts a +`behavior` parameter. This gives a chance to use a locally change the +[behavior](./behavior.md) of a link. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..7aca6edb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "ember-link" + #text: "A VitePress Site" + tagline: Links as Primitives + actions: + - theme: brand + text: Markdown Examples + link: /markdown-examples + - theme: alt + text: API Examples + link: /api-examples + +features: + - title: Feature A + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit + - title: Feature B + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit + - title: Feature C + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit +--- + diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..e1aa5646 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,7 @@ +# Installation + +Install `ember-link` with: + +```sh +ember install ember-link +``` diff --git a/docs/link-builder.md b/docs/link-builder.md new file mode 100644 index 00000000..2c37df34 --- /dev/null +++ b/docs/link-builder.md @@ -0,0 +1,98 @@ +# Link Builder + +Not always, you have the full information at hand when creating a link, as the last pieces +of information are missing. For example in list-details routing situations, you +do have the detail route ready, but the final parameter for the id of that +particular entity you are looking at is expected to be available _later_. + +For such situations link builders are a great way to pass down and postpone the +construction of the final link, when all information is available. + +## Creating a Link Builder + +Let's take ember's [super-rental](https://github.com/ember-learn/super-rentals/) +app as an example. The route for a rental is defined as follows: + +```js +Router.map(function () { + // ... + this.route('rental', { path: '/rentals/:rental_id' }); +}); +``` + +The builder is crafted at route level, where other routes are known. This allows +us to do this in the `model()` hook of a `Route` or within the `Controller`. +When no controller is present `model()` seems the place: + +```ts +import Route from '@ember/routing/route'; +import { service } from '@ember/service'; + +export default class IndexRoute extends Route { + @service store; + + buildRequestOfferLink = ( + rentalId: string + ): Link => { + return this.linkManager.createLink({ + route: 'rental', + models: [rentalId] + }); + }; + + async model() { + return { + rentals: this.store.findAll('rental') + buildRentalLink: this.buildRentalLink + }; + } +} +``` + +And using it from the template: + +```hbs + +``` + +## Using the Link Builder + +At +[``](https://github.com/ember-learn/super-rentals/blob/cac43c0c7ffa340003bce5b07b6048ee42c59d55/app/components/rentals.hbs) +we pass down the link builder to +[``](https://github.com/ember-learn/super-rentals/blob/cac43c0c7ffa340003bce5b07b6048ee42c59d55/app/components/rental.hbs): + +```hbs + +``` + +At +[``](https://github.com/ember-learn/super-rentals/blob/cac43c0c7ffa340003bce5b07b6048ee42c59d55/app/components/rental.hbs) +accepting the link builder with an appropriate signature: + +```ts +import type { Rental } from '../models/rental'; +import type { Link } from 'ember-link'; + +interface RentalSignature { + Args: { + rental: Rental; + buildRentalLink: (rentalId) => Link; + } +} +``` + +And then use it from the template: + +```hbs +

+ {{#let (@buildRentalLink @rental.id) as |link|}} + + {{@rental.title}} + + {{/let}} +

+``` diff --git a/docs/markdown-examples.md b/docs/markdown-examples.md new file mode 100644 index 00000000..8e55eb8a --- /dev/null +++ b/docs/markdown-examples.md @@ -0,0 +1,85 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +**Input** + +```` +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**Output** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +## Custom Containers + +**Input** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**Output** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +## More + +Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 00000000..6cf1cf47 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,10 @@ +# Migration + +## From v2 to v3 + +- Search and replace `UILink` with `Link` +- `preventDefault` parameter for `(link)` helper has changed into:
+ `(link ".." behavior=(hash prevent=true))` +- `` component has been removed, use `(link)` helper instead +- Instead of `link.transitionTo` you want to switch to `link.open` to give + control over how this should be opened to definition side of links diff --git a/docs/references.md b/docs/references.md new file mode 100644 index 00000000..e64c73e3 --- /dev/null +++ b/docs/references.md @@ -0,0 +1,15 @@ +# References + +## Related Projects + +- [`ember-command`](https://github.com/gossi/ember-command): Primitives for + actions backpacking with links +- [`ember-engine-router-service`](https://github.com/buschtoens/ember-engine-router-service): + Allows you to use `ember-link` inside engines +- [`ember-router-helpers`](https://github.com/rwjblue/ember-router-helpers) + +## Related RFCs + +- [RFC 391 "Router Helpers"](https://github.com/emberjs/rfcs/blob/master/text/0391-router-helpers.md) +- [RFC 339 "Router link component and routing helpers"](https://github.com/emberjs/rfcs/pull/339) +- [RFC 459 "Angle Bracket Invocations For Built-in Components"](https://github.com/emberjs/rfcs/blob/angle-built-ins/text/0459-angle-bracket-built-in-components.md#linkto) diff --git a/docs/service.md b/docs/service.md new file mode 100644 index 00000000..ad402267 --- /dev/null +++ b/docs/service.md @@ -0,0 +1,25 @@ +# `LinkManager` Service + +The `LinkManager` service is used by the [`(link) helper`](./helper.md) and +to create a [`Link`](./api/classes/ember_link.Link.md) instances. + +You can also use this service directly to programmatically create link +references. + +## `createLink(linkParams: LinkParams): Link` + +Will create a [`Link`](./api/classes/ember_link.Link.md) to pass around using +the same parameters as [`(link) helper`](./helper.md). + +::: info +API Docs: [createLink](./api/classes/ember_link.LinkManagerService.md#createlink) +::: + +## `getLinkParamsFromURL(url: string): LinkParams` + +Use this method to derive `LinkParams` from a serialized, recognizable URL, that +you can then pass into `createLink`. + +::: info +API Docs: [getLinkParamsFromURL](./api/classes/ember_link.LinkManagerService.md#getlinkparamsfromurl) +::: diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..48608213 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,47 @@ +# Testing + +ember-link has testing support on board to give you great DX for writing tests. + +## Application Tests + +In [acceptance / application tests (`setupApplicationTest(hooks)`)](https://guides.emberjs.com/release/testing/testing-application/) +your app boots with a fully-fledged router, so `ember-link` just works normally. + +## Rendering Tests + +In [integration / render tests (`setupRenderingTest(hooks)`)](https://guides.emberjs.com/release/testing/testing-components/) the +router is not initialized, so `ember-link` can't operate normally. To still +support using `(link)` & friends in render tests, you can use the +[`setupLink(hooks)` test helper](./api/modules/ember_link_test_support.md#setuplink). + +```ts +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +import { setupLink, linkFor, TestLink } from 'ember-link/test-support'; + +module('`setupLink` example', function (hooks) { + setupRenderingTest(hooks); + setupLink(hooks); + + test('`(link)` works in render tests', async function (assert) { + // arrange + const link = linkFor('some.route'); + link.onTransitionTo = assert.step('link clicked'); + + await render(hbs` + {{#let (link @route="some.route") as |l|}} + Click me + {{/let}} + `); + + // act + await click('a'); + + // assert + assert.verifySteps(['link clicked']); + }); +}); +``` diff --git a/docs/using-locales.md b/docs/using-locales.md new file mode 100644 index 00000000..e5817a22 --- /dev/null +++ b/docs/using-locales.md @@ -0,0 +1,82 @@ +# Using Locales + +Using links in locales with [`ember-intl`](https://ember-intl.github.io) is +still a hard problem today and `ember-link` allows for a decent solution. + +At first define a locale and pass it the url from a link: + +```yaml +terms: +| Our Terms and Conditions apply. Also find out our +| Privacy guidelines. +``` + +then use the link: + +```hbs +{{#let (link "terms") (link "privacy") as |termsLink privacyLink|}} + {{t "terms" htmlSafe=true termsHref=termsLink.url privacyHref=privacyLink.url}} +{{/let}} +``` + +that so far is not enough. So wrap it in a modifier to intercept the browser +default behavior for links and use embers router system. + +```ts [open-links.ts] +import Modifier from 'ember-modifier'; +import type { LinkManagerService } from 'ember-link'; + +export interface OpenLinksSignature { + Element: HTMLElement; +} + +export default class OpenLinks extends Modifier { + @service declare linkManager: LinkManagerService; + + handleClick(event: MouseEvent) => { + const anchor = (event.target as HTMLElement | undefined)?.closest('a'); + if (!anchor) return; + + const url = anchor.getAttribute('href'); + event.preventDefault(); + + const params = this.linkManager.getLinkParamsFromURL(url); + const link = this.linkManager.createLink(params); + + link.open(); + }; + + modify(element: HTMLElement) { + element.addEventListener('click', handleClick); + + return () => { + element.removeEventListener('click', handleClick); + }; + } +} +``` + +which then be used in a footer for a page: + +```gts +import { link } from 'ember-link'; +import { t } from 'ember-intl/helpers/t'; +import openLinks from ./open-links.ts + + +``` + +That's schematic usage of an `openLinks` modifier in combination. Unfortunately +this is not using the original link instance created in the template, but this +assures the URL is correct, which we can pick up again from the modifier. + +An extension of the usage might include: + +- Using the apps in your in-app browser (when used in a mobile app) +- Add tracking events diff --git a/docs/using-primitives.md b/docs/using-primitives.md new file mode 100644 index 00000000..906cbcc7 --- /dev/null +++ b/docs/using-primitives.md @@ -0,0 +1,72 @@ +# Using Primitives + +Routes are known to other routes within a given host application, that is +especially relevant in engines, where the same route may have a different name +in different engines. Hardcoding a route in a component that is used in multiple +engines is a very error prone concept. + +`ember-link` separates the two from each other, allowing link creation and +attaching them to elements be handled by different parties. + +## Creating Links + +Creating links happens at best at route level, either in the route template +using the [`(link)` helper](./helper.md) or in the controller using the +[`LinkManager` serivice](./service.md). Then pass it down into components to +attach them. + +Here is an idea for a route template, passing a link to the home screen down to +a component managing the entire screen. + +```hbs + +``` + +You can use [customizations](./customization.md) if you want pass more +information along with the link. + +## Components for Accepting Links + +From the checkout reward screen this links is passed down and down until it +reaches it final destination to be attached to an element. Tailor your +components to work with links as arguments. + +```ts +import type { Link } from 'ember-link'; + +interface CheckoutRewardScreenSignature { + Args: { + home: Link; + } +} +``` + +That also prepares them to properly work with [testing](./testing.md). + +## Low Level Components + +Make [`Link`](./api/classes/ember_link.Link.md) a first-class +primitive in your app architecture! Instead of manually wiring up +[`Link#url`](./api/classes/ember_link.Link.md#url) and +[`Link#open()`](./api/classes/ember_link.Link.md#open) every time, rather +create your own ready-to-use, style-guide-compliant link and button components +that accept `@link` as an argument. + +```hbs + + Become a Premium member + +``` + +```hbs + + {{yield}} + +``` + +Works even better with [commands](./commands.md). diff --git a/ember-link/README.md b/ember-link/README.md index 0397f06c..fa9e1bd3 100644 --- a/ember-link/README.md +++ b/ember-link/README.md @@ -18,12 +18,6 @@ usage in templates. — [/r/whatjawsdid](https://www.reddit.com/r/whatjawsdid/) -## Installation - -``` -ember install ember-link -``` - ## Usage - [`{{link}}` helper](#link-helper) diff --git a/ember-link/package.json b/ember-link/package.json index daeb27c3..0998aab7 100644 --- a/ember-link/package.json +++ b/ember-link/package.json @@ -79,6 +79,8 @@ "rollup": "3.25.1", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-ts": "^3.2.0", + "typedoc": "^0.24.8", + "typedoc-plugin-markdown": "^3.15.3", "typescript": "^5.1.3" }, "engines": { diff --git a/ember-link/src/helpers/link.ts b/ember-link/src/helpers/link.ts index d87155b5..90e0ff22 100644 --- a/ember-link/src/helpers/link.ts +++ b/ember-link/src/helpers/link.ts @@ -9,9 +9,9 @@ import type { QueryParams } from '../-params'; import type Link from '../link'; import type LinkManagerService from '../services/link-manager'; -type PositionalParams = [] | RouteArgs; +export type PositionalParams = [] | RouteArgs; -interface NamedParams extends Partial { +export interface NamedParams extends Partial { /** * Optional shortcut for `models={{array model}}`. */ diff --git a/ember-link/src/index.ts b/ember-link/src/index.ts index c67e3c02..dbed13d6 100644 --- a/ember-link/src/index.ts +++ b/ember-link/src/index.ts @@ -1,5 +1,14 @@ +/** + * This is the doc comment for file1.ts + * + * Specify this is a module comment and rename it to my-module: + * @module ember-link + */ + +export type { Behavior } from './-behavior'; export { prevent } from './-behavior'; export type { LinkParams } from './-params'; +export type { LinkSignature, NamedParams, PositionalParams } from './helpers/link'; export { default as link } from './helpers/link'; export { default as Link } from './link'; export type { default as LinkManagerService } from './services/link-manager'; diff --git a/ember-link/src/test-support/index.ts b/ember-link/src/test-support/index.ts index 90c93e02..668c07c8 100644 --- a/ember-link/src/test-support/index.ts +++ b/ember-link/src/test-support/index.ts @@ -1,3 +1,10 @@ +/** + * This is the doc comment for file1.ts + * + * Specify this is a module comment and rename it to my-module: + * @module ember-link/test-support + */ + export { default as TestInstrumentedLinkManagerService } from './-services/test-instrumented-link-manager'; export { default as linkFor } from './link-for'; export { default as setupLink } from './setup-link'; diff --git a/ember-link/typedoc.json b/ember-link/typedoc.json new file mode 100644 index 00000000..61609b8b --- /dev/null +++ b/ember-link/typedoc.json @@ -0,0 +1,16 @@ +{ + "entryPoints": [ + "src/index.ts", + "src/test-support/index.ts" + ], + "out": "../docs/api", + "json": "../docs/api/data.json", + "plugin": [ + "typedoc-plugin-markdown" + ], + "excludePrivate": true, + "excludeProtected": true, + "excludeExternals": true, + // typedoc-plugin-markdown + "hideBreadcrumbs": true +} \ No newline at end of file diff --git a/package.json b/package.json index 9d7d0c1e..1054679c 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,24 @@ "lint:fix": "pnpm --filter '*' lint:fix", "start": "concurrently 'npm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", "start:addon": "pnpm --filter ember-link start", - "start:test-app": "pnpm --filter test-app start" + "start:test-app": "pnpm --filter test-app start", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, "devDependencies": { - "concurrently": "^7.6.0" + "concurrently": "^7.6.0", + "vitepress": "^1.0.0-beta.3" }, "volta": { "node": "16.20.0" + }, + "pnpm": { + "peerDependencyRules": { + "ignoreMissing": [ + "@algolia/client-search", + "search-insights" + ] + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10aa56f8..dc1f089c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,12 @@ importers: concurrently: specifier: ^7.6.0 version: 7.6.0 + typedoc-json-parser: + specifier: ~8.2.0 + version: 8.2.0(typescript@5.1.3) + vitepress: + specifier: ^1.0.0-beta.3 + version: 1.0.0-beta.3 ember-link: dependencies: @@ -92,6 +98,15 @@ importers: rollup-plugin-ts: specifier: ^3.2.0 version: 3.2.0(@babel/core@7.22.5)(@babel/preset-typescript@7.22.5)(@babel/runtime@7.22.5)(rollup@3.25.1)(typescript@5.1.3) + typedoc: + specifier: ^0.24.8 + version: 0.24.8(typescript@5.1.3) + typedoc-json-parser: + specifier: ^8.2.0 + version: 8.2.0(typescript@5.1.3) + typedoc-plugin-markdown: + specifier: ^3.15.3 + version: 3.15.3(typedoc@0.24.8) typescript: specifier: ^5.1.3 version: 5.1.3 @@ -243,6 +258,146 @@ importers: packages: + /@algolia/autocomplete-core@1.9.3(algoliasearch@4.18.0): + resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(algoliasearch@4.18.0) + '@algolia/autocomplete-shared': 1.9.3(algoliasearch@4.18.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + dev: true + + /@algolia/autocomplete-plugin-algolia-insights@1.9.3(algoliasearch@4.18.0): + resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + peerDependencies: + search-insights: '>= 1 < 3' + peerDependenciesMeta: + search-insights: + optional: true + dependencies: + '@algolia/autocomplete-shared': 1.9.3(algoliasearch@4.18.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + dev: true + + /@algolia/autocomplete-preset-algolia@1.9.3(algoliasearch@4.18.0): + resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true + dependencies: + '@algolia/autocomplete-shared': 1.9.3(algoliasearch@4.18.0) + algoliasearch: 4.18.0 + dev: true + + /@algolia/autocomplete-shared@1.9.3(algoliasearch@4.18.0): + resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + peerDependenciesMeta: + '@algolia/client-search': + optional: true + dependencies: + algoliasearch: 4.18.0 + dev: true + + /@algolia/cache-browser-local-storage@4.18.0: + resolution: {integrity: sha512-rUAs49NLlO8LVLgGzM4cLkw8NJLKguQLgvFmBEe3DyzlinoqxzQMHfKZs6TSq4LZfw/z8qHvRo8NcTAAUJQLcw==} + dependencies: + '@algolia/cache-common': 4.18.0 + dev: true + + /@algolia/cache-common@4.18.0: + resolution: {integrity: sha512-BmxsicMR4doGbeEXQu8yqiGmiyvpNvejYJtQ7rvzttEAMxOPoWEHrWyzBQw4x7LrBY9pMrgv4ZlUaF8PGzewHg==} + dev: true + + /@algolia/cache-in-memory@4.18.0: + resolution: {integrity: sha512-evD4dA1nd5HbFdufBxLqlJoob7E2ozlqJZuV3YlirNx5Na4q1LckIuzjNYZs2ddLzuTc/Xd5O3Ibf7OwPskHxw==} + dependencies: + '@algolia/cache-common': 4.18.0 + dev: true + + /@algolia/client-account@4.18.0: + resolution: {integrity: sha512-XsDnlROr3+Z1yjxBJjUMfMazi1V155kVdte6496atvBgOEtwCzTs3A+qdhfsAnGUvaYfBrBkL0ThnhMIBCGcew==} + dependencies: + '@algolia/client-common': 4.18.0 + '@algolia/client-search': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + + /@algolia/client-analytics@4.18.0: + resolution: {integrity: sha512-chEUSN4ReqU7uRQ1C8kDm0EiPE+eJeAXiWcBwLhEynfNuTfawN9P93rSZktj7gmExz0C8XmkbBU19IQ05wCNrQ==} + dependencies: + '@algolia/client-common': 4.18.0 + '@algolia/client-search': 4.18.0 + '@algolia/requester-common': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + + /@algolia/client-common@4.18.0: + resolution: {integrity: sha512-7N+soJFP4wn8tjTr3MSUT/U+4xVXbz4jmeRfWfVAzdAbxLAQbHa0o/POSdTvQ8/02DjCLelloZ1bb4ZFVKg7Wg==} + dependencies: + '@algolia/requester-common': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + + /@algolia/client-personalization@4.18.0: + resolution: {integrity: sha512-+PeCjODbxtamHcPl+couXMeHEefpUpr7IHftj4Y4Nia1hj8gGq4VlIcqhToAw8YjLeCTfOR7r7xtj3pJcYdP8A==} + dependencies: + '@algolia/client-common': 4.18.0 + '@algolia/requester-common': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + + /@algolia/client-search@4.18.0: + resolution: {integrity: sha512-F9xzQXTjm6UuZtnsLIew6KSraXQ0AzS/Ee+OD+mQbtcA/K1sg89tqb8TkwjtiYZ0oij13u3EapB3gPZwm+1Y6g==} + dependencies: + '@algolia/client-common': 4.18.0 + '@algolia/requester-common': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + + /@algolia/logger-common@4.18.0: + resolution: {integrity: sha512-46etYgSlkoKepkMSyaoriSn2JDgcrpc/nkOgou/lm0y17GuMl9oYZxwKKTSviLKI5Irk9nSKGwnBTQYwXOYdRg==} + dev: true + + /@algolia/logger-console@4.18.0: + resolution: {integrity: sha512-3P3VUYMl9CyJbi/UU1uUNlf6Z8N2ltW3Oqhq/nR7vH0CjWv32YROq3iGWGxB2xt3aXobdUPXs6P0tHSKRmNA6g==} + dependencies: + '@algolia/logger-common': 4.18.0 + dev: true + + /@algolia/requester-browser-xhr@4.18.0: + resolution: {integrity: sha512-/AcWHOBub2U4TE/bPi4Gz1XfuLK6/7dj4HJG+Z2SfQoS1RjNLshZclU3OoKIkFp8D2NC7+BNsPvr9cPLyW8nyQ==} + dependencies: + '@algolia/requester-common': 4.18.0 + dev: true + + /@algolia/requester-common@4.18.0: + resolution: {integrity: sha512-xlT8R1qYNRBCi1IYLsx7uhftzdfsLPDGudeQs+xvYB4sQ3ya7+ppolB/8m/a4F2gCkEO6oxpp5AGemM7kD27jA==} + dev: true + + /@algolia/requester-node-http@4.18.0: + resolution: {integrity: sha512-TGfwj9aeTVgOUhn5XrqBhwUhUUDnGIKlI0kCBMdR58XfXcfdwomka+CPIgThRbfYw04oQr31A6/95ZH2QVJ9UQ==} + dependencies: + '@algolia/requester-common': 4.18.0 + dev: true + + /@algolia/transporter@4.18.0: + resolution: {integrity: sha512-xbw3YRUGtXQNG1geYFEDDuFLZt4Z8YNKbamHPkzr3rWc6qp4/BqEeXcI2u/P/oMq2yxtXgMxrCxOPA8lyIe5jw==} + dependencies: + '@algolia/cache-common': 4.18.0 + '@algolia/logger-common': 4.18.0 + '@algolia/requester-common': 4.18.0 + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -1447,6 +1602,46 @@ packages: minimist: 1.2.7 dev: true + /@docsearch/css@3.5.1: + resolution: {integrity: sha512-2Pu9HDg/uP/IT10rbQ+4OrTQuxIWdKVUEdcw9/w7kZJv9NeHS6skJx1xuRiFyoGKwAzcHXnLp7csE99sj+O1YA==} + dev: true + + /@docsearch/js@3.5.1: + resolution: {integrity: sha512-EXi8de5njxgP6TV3N9ytnGRLG9zmBNTEZjR4VzwPcpPLbZxxTLG2gaFyJyKiFVQxHW/DPlMrDJA3qoRRGEkgZw==} + dependencies: + '@docsearch/react': 3.5.1 + preact: 10.15.1 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + dev: true + + /@docsearch/react@3.5.1: + resolution: {integrity: sha512-t5mEODdLzZq4PTFAm/dvqcvZFdPDMdfPE5rJS5SC8OUq9mPzxEy6b+9THIqNM9P0ocCb4UC5jqBrxKclnuIbzQ==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + '@algolia/autocomplete-core': 1.9.3(algoliasearch@4.18.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(algoliasearch@4.18.0) + '@docsearch/css': 3.5.1 + algoliasearch: 4.18.0 + transitivePeerDependencies: + - '@algolia/client-search' + - search-insights + dev: true + /@ember-data/rfc395-data@0.0.4: resolution: {integrity: sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==} @@ -1636,6 +1831,204 @@ packages: resolve: 1.22.3 dev: true + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.43.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1673,6 +2066,13 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@favware/colorette-spinner@1.0.1: + resolution: {integrity: sha512-PPYtcLzhSafdylp8NBOxMCYIcLqTUMNiQc7ciBoAIvxNG2egM+P7e2nNPui5+Svyk89Q+Tnbrp139ZRIIBw3IA==} + engines: {node: '>=v16'} + dependencies: + colorette: 2.0.20 + dev: true + /@glimmer/component@1.1.2(@babel/core@7.22.5): resolution: {integrity: sha512-XyAsEEa4kWOPy+gIdMjJ8XlzA3qrGH55ZDv6nA16ibalCR17k74BI0CztxuRds+Rm6CtbUVgheCVlcCULuqD7A==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1961,7 +2361,7 @@ packages: fs-extra: 9.1.0 proper-lockfile: 4.1.2 slash: 3.0.0 - tslib: 2.4.1 + tslib: 2.6.0 upath: 2.0.1 dev: true @@ -2019,6 +2419,11 @@ packages: rollup: 3.25.1 dev: true + /@sapphire/node-utilities@1.0.0: + resolution: {integrity: sha512-xFC4UyzSKs6juyFtsUV4VNybg0KIpwaThED2TH3TGtNT5b0VFpILTXQtqXpPf+Rfmj+O/mLCm319xy4ohsQqxQ==} + engines: {node: '>=v16.0.0', npm: '>=7.0.0'} + dev: true + /@simple-dom/interface@1.4.0: resolution: {integrity: sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA==} dev: true @@ -2255,6 +2660,10 @@ packages: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: true + /@types/web-bluetooth@0.0.17: + resolution: {integrity: sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==} + dev: true + /@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3): resolution: {integrity: sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2385,6 +2794,179 @@ packages: eslint-visitor-keys: 3.4.1 dev: true + /@vitejs/plugin-vue@4.2.3(vite@4.3.9)(vue@3.3.4): + resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 + vue: ^3.2.25 + dependencies: + vite: 4.3.9 + vue: 3.3.4 + dev: true + + /@vue/compiler-core@3.3.4: + resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} + dependencies: + '@babel/parser': 7.22.5 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-dom@3.3.4: + resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} + dependencies: + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 + dev: true + + /@vue/compiler-sfc@3.3.4: + resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} + dependencies: + '@babel/parser': 7.22.5 + '@vue/compiler-core': 3.3.4 + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-ssr': 3.3.4 + '@vue/reactivity-transform': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.0 + postcss: 8.4.24 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-ssr@3.3.4: + resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} + dependencies: + '@vue/compiler-dom': 3.3.4 + '@vue/shared': 3.3.4 + dev: true + + /@vue/devtools-api@6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} + dev: true + + /@vue/reactivity-transform@3.3.4: + resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} + dependencies: + '@babel/parser': 7.22.5 + '@vue/compiler-core': 3.3.4 + '@vue/shared': 3.3.4 + estree-walker: 2.0.2 + magic-string: 0.30.0 + dev: true + + /@vue/reactivity@3.3.4: + resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} + dependencies: + '@vue/shared': 3.3.4 + dev: true + + /@vue/runtime-core@3.3.4: + resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} + dependencies: + '@vue/reactivity': 3.3.4 + '@vue/shared': 3.3.4 + dev: true + + /@vue/runtime-dom@3.3.4: + resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} + dependencies: + '@vue/runtime-core': 3.3.4 + '@vue/shared': 3.3.4 + csstype: 3.1.2 + dev: true + + /@vue/server-renderer@3.3.4(vue@3.3.4): + resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} + peerDependencies: + vue: 3.3.4 + dependencies: + '@vue/compiler-ssr': 3.3.4 + '@vue/shared': 3.3.4 + vue: 3.3.4 + dev: true + + /@vue/shared@3.3.4: + resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} + dev: true + + /@vueuse/core@10.2.1(vue@3.3.4): + resolution: {integrity: sha512-c441bfMbkAwTNwVRHQ0zdYZNETK//P84rC01aP2Uy/aRFCiie9NE/k9KdIXbno0eDYP5NPUuWv0aA/I4Unr/7w==} + dependencies: + '@types/web-bluetooth': 0.0.17 + '@vueuse/metadata': 10.2.1 + '@vueuse/shared': 10.2.1(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/integrations@10.2.1(focus-trap@7.4.3)(vue@3.3.4): + resolution: {integrity: sha512-FDP5lni+z9FjHE9H3xuvwSjoRV9U8jmDvJpmHPCBjUgPGYRynwb60eHWXCFJXLUtb4gSIHy0e+iaEbrKdalCkQ==} + peerDependencies: + async-validator: '*' + axios: '*' + change-case: '*' + drauu: '*' + focus-trap: '*' + fuse.js: '*' + idb-keyval: '*' + jwt-decode: '*' + nprogress: '*' + qrcode: '*' + sortablejs: '*' + universal-cookie: '*' + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + dependencies: + '@vueuse/core': 10.2.1(vue@3.3.4) + '@vueuse/shared': 10.2.1(vue@3.3.4) + focus-trap: 7.4.3 + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/metadata@10.2.1: + resolution: {integrity: sha512-3Gt68mY/i6bQvFqx7cuGBzrCCQu17OBaGWS5JdwISpMsHnMKKjC2FeB5OAfMcCQ0oINfADP3i9A4PPRo0peHdQ==} + dev: true + + /@vueuse/shared@10.2.1(vue@3.3.4): + resolution: {integrity: sha512-QWHq2bSuGptkcxx4f4M/fBYC3Y8d3M2UYyLsyzoPgEoVzJURQ0oJeWXu79OiLlBb8gTKkqe4mO85T/sf39mmiw==} + dependencies: + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + /@webassemblyjs/ast@1.11.5: resolution: {integrity: sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==} dependencies: @@ -2628,6 +3210,25 @@ packages: uri-js: 4.4.1 dev: true + /algoliasearch@4.18.0: + resolution: {integrity: sha512-pCuVxC1SVcpc08ENH32T4sLKSyzoU7TkRIDBMwSLfIiW+fq4znOmWDkAygHZ6pRcO9I1UJdqlfgnV7TRj+MXrA==} + dependencies: + '@algolia/cache-browser-local-storage': 4.18.0 + '@algolia/cache-common': 4.18.0 + '@algolia/cache-in-memory': 4.18.0 + '@algolia/client-account': 4.18.0 + '@algolia/client-analytics': 4.18.0 + '@algolia/client-common': 4.18.0 + '@algolia/client-personalization': 4.18.0 + '@algolia/client-search': 4.18.0 + '@algolia/logger-common': 4.18.0 + '@algolia/logger-console': 4.18.0 + '@algolia/requester-browser-xhr': 4.18.0 + '@algolia/requester-common': 4.18.0 + '@algolia/requester-node-http': 4.18.0 + '@algolia/transporter': 4.18.0 + dev: true + /amd-name-resolver@1.3.1: resolution: {integrity: sha512-26qTEWqZQ+cxSYygZ4Cf8tsjDBLceJahhtewxtKZA3SRa4PluuqYCuheemDQD+7Mf5B7sr+zhTDWAHDh02a1Dw==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2683,6 +3284,10 @@ packages: engines: {node: '>=8'} dev: true + /ansi-sequence-parser@1.1.0: + resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} + dev: true + /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -3149,6 +3754,10 @@ packages: - supports-color dev: true + /body-scroll-lock@4.0.0-beta.0: + resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + dev: true + /body@5.1.0: resolution: {integrity: sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==} dependencies: @@ -4034,6 +4643,10 @@ packages: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} dev: true + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + /colors@1.0.3: resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} engines: {node: '>=0.1.90'} @@ -4051,6 +4664,11 @@ packages: delayed-stream: 1.0.0 dev: true + /commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true @@ -4535,6 +5153,10 @@ packages: cssom: 0.3.8 dev: true + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + dev: true + /dag-map@2.0.2: resolution: {integrity: sha512-xnsprIzYuDeiyu5zSKwilV/ajRHxnoMlAhEREfyfTgTSViMVY2fGP1ZcHJbtwup26oCkofySU/m6oKJ3HrkW7w==} dev: true @@ -4766,7 +5388,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.6.0 dev: true /dot-prop@5.3.0: @@ -5661,6 +6283,36 @@ packages: is-symbol: 1.0.4 dev: true + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: true + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6541,6 +7193,12 @@ packages: resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==} dev: true + /focus-trap@7.4.3: + resolution: {integrity: sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==} + dependencies: + tabbable: 6.2.0 + dev: true + /follow-redirects@1.14.5: resolution: {integrity: sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==} engines: {node: '>=4.0'} @@ -8252,7 +8910,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.4.1 + tslib: 2.6.0 dev: true /lowercase-keys@1.0.1: @@ -8281,6 +8939,10 @@ packages: engines: {node: '>=12'} dev: true + /lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + dev: true + /magic-string@0.25.7: resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==} dependencies: @@ -8333,6 +8995,10 @@ packages: object-visit: 1.0.1 dev: true + /mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + dev: true + /markdown-it-terminal@0.4.0(markdown-it@13.0.1): resolution: {integrity: sha512-NeXtgpIK6jBciHTm9UhiPnyHDdqyVIdRPJ+KdQtZaf/wR74gvhCNbw5li4TYsxRp5u3ZoHEF4DwpECeZqyCw+w==} peerDependencies: @@ -8356,6 +9022,12 @@ packages: uc.micro: 1.0.6 dev: true + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + dev: true + /matcher-collection@1.1.2: resolution: {integrity: sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==} dependencies: @@ -8513,6 +9185,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch@9.0.2: + resolution: {integrity: sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@0.2.1: resolution: {integrity: sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==} dev: true @@ -8527,6 +9206,10 @@ packages: yallist: 3.1.1 dev: true + /minisearch@6.1.0: + resolution: {integrity: sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==} + dev: true + /mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} engines: {node: '>=0.10.0'} @@ -8591,8 +9274,8 @@ packages: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true - /nanoid@3.1.30: - resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true @@ -8651,7 +9334,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.4.1 + tslib: 2.6.0 dev: true /node-fetch@2.6.7: @@ -9272,11 +9955,24 @@ packages: resolution: {integrity: sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.1.30 + nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 0.6.2 dev: true + /postcss@8.4.24: + resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /preact@10.15.1: + resolution: {integrity: sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==} + dev: true + /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -9850,13 +10546,13 @@ packages: /rxjs@7.8.0: resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} dependencies: - tslib: 2.4.1 + tslib: 2.6.0 dev: true /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.4.1 + tslib: 2.6.0 dev: true /safe-buffer@5.1.2: @@ -10082,6 +10778,15 @@ packages: resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} dev: true + /shiki@0.14.3: + resolution: {integrity: sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==} + dependencies: + ansi-sequence-parser: 1.1.0 + jsonc-parser: 3.2.0 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + dev: true + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -10537,6 +11242,10 @@ packages: - supports-color dev: true + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: true + /tap-parser@7.0.0: resolution: {integrity: sha512-05G8/LrzqOOFvZhhAk32wsGiPZ1lfUrl+iV7+OkKgfofZxiceZWMHkKmow71YsyVQ8IvGBP2EjcIjE5gL4l5lA==} hasBin: true @@ -10889,6 +11598,10 @@ packages: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} dev: true + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + dev: true + /tsutils@3.21.0(typescript@5.1.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -10947,6 +11660,45 @@ packages: is-typedarray: 1.0.0 dev: true + /typedoc-json-parser@8.2.0(typescript@5.1.3): + resolution: {integrity: sha512-/aEvjQAEJOb5vPFXbM4w2cOZf3h6PuNTHpiK/ZKAZf5xHu3kM6LxlgGBGgt9JyR2VGeQEJBvxHZODqiBGrB8Sw==} + engines: {node: '>=16', npm: '>=6'} + hasBin: true + dependencies: + '@favware/colorette-spinner': 1.0.1 + '@sapphire/node-utilities': 1.0.0 + colorette: 2.0.20 + commander: 11.0.0 + js-yaml: 4.1.0 + tslib: 2.6.0 + typedoc: 0.24.8(typescript@5.1.3) + transitivePeerDependencies: + - typescript + dev: true + + /typedoc-plugin-markdown@3.15.3(typedoc@0.24.8): + resolution: {integrity: sha512-idntFYu3vfaY3eaD+w9DeRd0PmNGqGuNLKihPU9poxFGnATJYGn9dPtEhn2QrTdishFMg7jPXAhos+2T6YCWRQ==} + peerDependencies: + typedoc: '>=0.24.0' + dependencies: + handlebars: 4.7.7 + typedoc: 0.24.8(typescript@5.1.3) + dev: true + + /typedoc@0.24.8(typescript@5.1.3): + resolution: {integrity: sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==} + engines: {node: '>= 14.14'} + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x + dependencies: + lunr: 2.3.9 + marked: 4.3.0 + minimatch: 9.0.2 + shiki: 0.14.3 + typescript: 5.1.3 + dev: true + /typescript-memoize@1.1.0: resolution: {integrity: sha512-LQPKVXK8QrBBkL/zclE6YgSWn0I8ew5m0Lf+XL00IwMhlotqRLlzHV+BRrljVQIc+NohUAuQP7mg4HQwrx5Xbg==} @@ -11156,6 +11908,81 @@ packages: engines: {node: '>= 0.8'} dev: true + /vite@4.3.9: + resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.17.19 + postcss: 8.4.24 + rollup: 3.25.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitepress@1.0.0-beta.3: + resolution: {integrity: sha512-GR5Pvr/o343NN1M4Na1shhDYZRrQbjmLq7WE0lla0H8iDPAsHE8agTHLWfu3FWx+3q2KA29sv16+0O9RQKGjlA==} + hasBin: true + dependencies: + '@docsearch/css': 3.5.1 + '@docsearch/js': 3.5.1 + '@vitejs/plugin-vue': 4.2.3(vite@4.3.9)(vue@3.3.4) + '@vue/devtools-api': 6.5.0 + '@vueuse/core': 10.2.1(vue@3.3.4) + '@vueuse/integrations': 10.2.1(focus-trap@7.4.3)(vue@3.3.4) + body-scroll-lock: 4.0.0-beta.0 + focus-trap: 7.4.3 + mark.js: 8.11.1 + minisearch: 6.1.0 + shiki: 0.14.3 + vite: 4.3.9 + vue: 3.3.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - nprogress + - qrcode + - react + - react-dom + - sass + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - universal-cookie + dev: true + /vscode-json-languageservice@4.2.1: resolution: {integrity: sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==} dependencies: @@ -11178,10 +12005,43 @@ packages: resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} dev: true + /vscode-oniguruma@1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + dev: true + + /vscode-textmate@8.0.0: + resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + dev: true + /vscode-uri@3.0.7: resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} dev: true + /vue-demi@0.14.5(vue@3.3.4): + resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.3.4 + dev: true + + /vue@3.3.4: + resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} + dependencies: + '@vue/compiler-dom': 3.3.4 + '@vue/compiler-sfc': 3.3.4 + '@vue/runtime-dom': 3.3.4 + '@vue/server-renderer': 3.3.4(vue@3.3.4) + '@vue/shared': 3.3.4 + dev: true + /w3c-hr-time@1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} deprecated: Use your platform's native performance.now() and performance.timeOrigin. From 2cf234d12a25ceeb0b7fd4d0e0f52c4709d3cb4c Mon Sep 17 00:00:00 2001 From: gossi Date: Tue, 4 Jul 2023 21:44:29 +0200 Subject: [PATCH 2/5] More docs --- README.md | 623 +++----------------------------------- docs/.vitepress/config.ts | 1 + docs/index.md | 18 +- docs/params.md | 120 ++++++++ 4 files changed, 164 insertions(+), 598 deletions(-) create mode 100644 docs/params.md diff --git a/README.md b/README.md index 8886ae7b..d3baabe4 100644 --- a/README.md +++ b/README.md @@ -16,633 +16,80 @@ usage in templates. ## Installation +Install `ember-link` with: + ```sh ember install ember-link ``` ## Usage -- [`{{link}}` helper](#link-helper) -- [`` component](#link-component) -- [`Link` class](#link) -- [`UILink` class](#uilink) -- [`LinkManager` service](#linkmanager) -- [Testing](#testing) - -### `{{link}}` Helper - -The `{{link}}` helper returns a [`UILink`](#uilink) instance. - -#### Invocation Styles - -##### Positional Parameters - -```hbs -{{#let - (link - "blogs.posts.post" - @post.blog.id - @post.id - (query-params showFullPost=true) - ) - as |l| -}} - - Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! - -{{/let}} -``` - -##### Named Parameters - -```hbs -{{#let - (link - route="blogs.posts.post" - models=(array @post.blog.id @post.id) - query=(hash showFullPost=true) - ) - as |l| -}} - - Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! - -{{/let}} -``` - -When passing a single model, you can use `model` instead of `models`: - -```hbs -{{#let - (link - route="blogs.posts" - model=@post.blog.id - ) - as |l| -}} - - Read more stories in the {{@post.blog.name}} blog! - -{{/let}} -``` - -##### Mix & Match - -You can also mix and match the parameter styles, however you like. - -```hbs -{{#let - (link - "blogs.posts.post" - @post.blog.id - @post.id - query=(hash showFullPost=true) - ) - as |l| -}} - - Read the full "{{@post.title}}" story on our {{@post.blog.name}} blog! - -{{/let}} -``` +You can use `ember-link` in a declarative form with a [`(link)` +helper](helper.md) or imperatively with the [`LinkManager` +Service](./service.md). -##### `fromURL` +### `(link)` Helper Example -Instead of the positional & named link parameters described above, you can also -create a `Link` instance from a serialized URL. +Use the `(link)` helper to create a link primitive and attach it to an element. ```hbs -{{! someURL = "/blogs/tech/posts/dont-break-the-web" }} -{{#let (link fromURL=this.someURL) as |l|}} - - Read the next great post. +{{#let (link "about") as |l|}} + + About us {{/let}} ``` -`fromURL` is mutually exclusive with the other link parameters: `route`, `model` -& `models`, `query` - -### Parameters - -In addition to the parameters shown above, the `{{link}}` helper also accepts a -`preventDefault` default parameter. It defaults to `true` and intelligently -prevents hard browser transitions when clicking `` elements. - -See [`@preventDefault`](#preventdefault) and [`UILink`](#uilink). - -### 💡 Pro Tips - -Instead of using the `{{#let}}` helper, you can use the -[`` component](#link-component) to achieve the same scoping effect, with -subjectively nicer syntax. - -Even better yet, make [`Link`](#link) / [`UILink`](#uilink) a first-class -primitive in your app architecture! Instead of manually wiring up -[`Link#url`](#url) and [`Link#transitionTo()`](#transitionto) every time, rather -create your own ready-to-use, style-guide-compliant link and button components -that accept `@link` as an argument instead of `@href` and `@onClick`. - -This is akin to the -[popular](https://github.com/rwjblue/ember-cli-async-button) -[async](https://github.com/DockYard/ember-async-button) -[task](https://github.com/quipuapp/ember-task-button) -button component concept. - -```hbs - - Become a Premium member - -``` - -```hbs - - {{yield}} - -``` - -### `` Component - -Similar to the the [`{{link}}` helper](#link-helper), the `` component -yields a [`UILink`](#uilink) instance. - -```hbs - - - Click me - - -``` - -#### Arguments - -##### `@route` - -Required. - -The target route name. - -**Example** - -```hbs - - - Click me - - -``` - -**`{{link-to}}` equivalent** - -```hbs -{{#link-to "some.route"}} - Click me -{{/link-to}} -``` - -##### `@models` - -Optional. Mutually exclusive with [`@model`](#model). - -An array of models / dynamic segments. - -**Example** - -```hbs - - - Click me - - -``` - -**`{{link-to}}` equivalent** - -```hbs -{{#link-to "some.route" someModel someNestedModel}} - Click me -{{/link-to}} -``` - -##### `@model` - -Optional. Mutually exclusive with [`@models`](#models). - -Shorthand for providing a single model / dynamic segment. The following two -invocations are equivalent: - -```hbs - - -``` - -##### `@query` - -Optional. - -Query Params object. - -**Example** - -```hbs - - - Click me - - -``` - -**`{{link-to}}` equivalent** - -```hbs -{{#link-to "some.route" (query-params foo="bar")}} - Click me -{{/link-to}} -``` - -##### `@fromURL` - -Optional. Mutually exclusive with [`@route`](#route), [`@model`](#model) / -[`@models`](#models), [`@query`](#query). - -**Example** - -```hbs - - - Click me - - -``` - -##### `@preventDefault` - -Optional. Default: `true` - -If enabled, the [`transitionTo`](#transitionto) and -[`replaceWith`](#replacewith) actions will try to call -[`event.preventDefault()`][prevent-default] on the first argument, if it is an -event. This is an anti-foot-gun to make `` _just work™️_ with `` and -`