diff --git a/.ember-cli b/.ember-cli index ee64cfed2a8..46c316d1747 100644 --- a/.ember-cli +++ b/.ember-cli @@ -5,5 +5,17 @@ Setting `disableAnalytics` to true will prevent any data from being sent. */ - "disableAnalytics": false + "disableAnalytics": false, + + /** + Setting `componentAuthoringFormat` to "strict" will force the blueprint generators to generate GJS + or GTS files for the component and the component rendering test. "loose" is the default. + */ + "componentAuthoringFormat": "strict", + + /** + Setting `routeAuthoringFormat` to "strict" will force the blueprint generators to generate GJS + or GTS templates for routes. "loose" is the default + */ + "routeAuthoringFormat": "strict" } diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..587856642a7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +a0efcd29f514b404d568478423ca2c7074287a56 diff --git a/app/components/color-scheme-menu.gjs b/app/components/color-scheme-menu.gjs new file mode 100644 index 00000000000..c1ea6655a4d --- /dev/null +++ b/app/components/color-scheme-menu.gjs @@ -0,0 +1,48 @@ +import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import eq from 'ember-truth-helpers/helpers/eq'; + +import Dropdown from 'crates-io/components/dropdown'; + +export default class Header extends Component { + + /** @type {import("../services/dark-mode").default} */ + @service colorScheme; + + colorSchemes = [ + { mode: 'light', svg: 'sun' }, + { mode: 'dark', svg: 'moon' }, + { mode: 'system', svg: 'color-mode' }, + ]; + + get icon() { + return this.colorSchemes.find(({ mode }) => mode === this.colorScheme.scheme)?.svg; + } +} diff --git a/app/components/color-scheme-menu.hbs b/app/components/color-scheme-menu.hbs deleted file mode 100644 index 4f6843ec22f..00000000000 --- a/app/components/color-scheme-menu.hbs +++ /dev/null @@ -1,20 +0,0 @@ - - - {{svg-jar this.icon class=(scoped-class "icon")}} - Change color scheme - - - - {{#each this.colorSchemes as |colorScheme|}} - - - - {{/each}} - - \ No newline at end of file diff --git a/app/components/color-scheme-menu.js b/app/components/color-scheme-menu.js deleted file mode 100644 index 3d86a373f97..00000000000 --- a/app/components/color-scheme-menu.js +++ /dev/null @@ -1,17 +0,0 @@ -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -export default class Header extends Component { - /** @type {import("../services/dark-mode").default} */ - @service colorScheme; - - colorSchemes = [ - { mode: 'light', svg: 'sun' }, - { mode: 'dark', svg: 'moon' }, - { mode: 'system', svg: 'color-mode' }, - ]; - - get icon() { - return this.colorSchemes.find(({ mode }) => mode === this.colorScheme.scheme)?.svg; - } -} diff --git a/app/components/copy-button.js b/app/components/copy-button.gjs similarity index 68% rename from app/components/copy-button.js rename to app/components/copy-button.gjs index 184d8521730..17a376c89a2 100644 --- a/app/components/copy-button.js +++ b/app/components/copy-button.gjs @@ -1,9 +1,16 @@ +import { on } from '@ember/modifier'; import { service } from '@ember/service'; import Component from '@glimmer/component'; import { restartableTask } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; export default class CrateTomlCopy extends Component { + @service notifications; copyTask = restartableTask(async () => { diff --git a/app/components/copy-button.hbs b/app/components/copy-button.hbs deleted file mode 100644 index cae9419a9ae..00000000000 --- a/app/components/copy-button.hbs +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/app/components/crate-downloads-list.gjs b/app/components/crate-downloads-list.gjs new file mode 100644 index 00000000000..809f3e283ea --- /dev/null +++ b/app/components/crate-downloads-list.gjs @@ -0,0 +1,20 @@ +import { LinkTo } from '@ember/routing'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +import formatNum from 'crates-io/helpers/format-num'; + diff --git a/app/components/crate-downloads-list.hbs b/app/components/crate-downloads-list.hbs deleted file mode 100644 index a526b42c274..00000000000 --- a/app/components/crate-downloads-list.hbs +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/app/components/crate-header.gjs b/app/components/crate-header.gjs new file mode 100644 index 00000000000..b767bc60840 --- /dev/null +++ b/app/components/crate-header.gjs @@ -0,0 +1,115 @@ +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import { task } from 'ember-concurrency'; +import pluralize from 'ember-inflector/helpers/pluralize'; +import link_ from 'ember-link/helpers/link'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import { alias } from 'macro-decorators'; + +import FollowButton from 'crates-io/components/follow-button'; +import NavTabs from 'crates-io/components/nav-tabs'; +import PageHeader from 'crates-io/components/page-header'; +import Tooltip from 'crates-io/components/tooltip'; + +export default class CrateHeader extends Component { + + @service session; + + @alias('loadKeywordsTask.last.value') keywords; + + constructor() { + super(...arguments); + + this.loadKeywordsTask.perform().catch(() => { + // ignore all errors and just don't display keywords if the request fails + }); + } + + get isOwner() { + let userId = this.session.currentUser?.id; + return this.args.crate?.hasOwnerUser(userId) ?? false; + } + + loadKeywordsTask = task(async () => { + return (await this.args.crate?.keywords) ?? []; + }); +} diff --git a/app/components/crate-header.hbs b/app/components/crate-header.hbs deleted file mode 100644 index 4b010ad3661..00000000000 --- a/app/components/crate-header.hbs +++ /dev/null @@ -1,80 +0,0 @@ - -

- {{@crate.name}} - {{#if @version}} - v{{@version.num}} - - {{#if @version.yanked}} - - {{svg-jar "trash"}} - Yanked - - - This crate has been yanked, but it is still available for download for other crates that - may be depending on it. - - - {{/if}} - {{/if}} -

- - {{#if @crate.description}} -
- {{@crate.description}} -
- {{/if}} - - {{#if this.keywords}} - - {{/if}} - - {{#if this.session.currentUser}} - - {{/if}} -
- - - - Readme - - - - {{pluralize @crate.num_versions "Version"}} - - - - Dependencies - - - - Dependents - - - {{#if this.isOwner}} - - Settings - - {{/if}} - \ No newline at end of file diff --git a/app/components/crate-header.js b/app/components/crate-header.js deleted file mode 100644 index b5d76c9d1dc..00000000000 --- a/app/components/crate-header.js +++ /dev/null @@ -1,28 +0,0 @@ -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -import { task } from 'ember-concurrency'; -import { alias } from 'macro-decorators'; - -export default class CrateHeader extends Component { - @service session; - - @alias('loadKeywordsTask.last.value') keywords; - - constructor() { - super(...arguments); - - this.loadKeywordsTask.perform().catch(() => { - // ignore all errors and just don't display keywords if the request fails - }); - } - - get isOwner() { - let userId = this.session.currentUser?.id; - return this.args.crate?.hasOwnerUser(userId) ?? false; - } - - loadKeywordsTask = task(async () => { - return (await this.args.crate?.keywords) ?? []; - }); -} diff --git a/app/components/crate-list.gjs b/app/components/crate-list.gjs new file mode 100644 index 00000000000..dfee19edbce --- /dev/null +++ b/app/components/crate-list.gjs @@ -0,0 +1,13 @@ +import CrateRow from 'crates-io/components/crate-row'; + diff --git a/app/components/crate-list.hbs b/app/components/crate-list.hbs deleted file mode 100644 index a060264f8ce..00000000000 --- a/app/components/crate-list.hbs +++ /dev/null @@ -1,10 +0,0 @@ -
- {{!-- The extra div wrapper is needed for specificity issues with `margin` --}} -
    - {{#each @crates as |crate index|}} -
  1. - -
  2. - {{/each}} -
-
\ No newline at end of file diff --git a/app/components/crate-row.gjs b/app/components/crate-row.gjs new file mode 100644 index 00000000000..9547e0a2251 --- /dev/null +++ b/app/components/crate-row.gjs @@ -0,0 +1,88 @@ +import { on } from '@ember/modifier'; + +import link_ from 'ember-link/helpers/link'; +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import and from 'ember-truth-helpers/helpers/and'; +import not from 'ember-truth-helpers/helpers/not'; + +import CopyButton from 'crates-io/components/copy-button'; +import Tooltip from 'crates-io/components/tooltip'; +import dateFormatDistanceToNow from 'crates-io/helpers/date-format-distance-to-now'; +import dateFormatIso from 'crates-io/helpers/date-format-iso'; +import formatNum from 'crates-io/helpers/format-num'; +import truncateText from 'crates-io/helpers/truncate-text'; + diff --git a/app/components/crate-row.hbs b/app/components/crate-row.hbs deleted file mode 100644 index 15693fef103..00000000000 --- a/app/components/crate-row.hbs +++ /dev/null @@ -1,72 +0,0 @@ -
-
-
- {{#let (link "crate" @crate.id) as |l|}} - - {{@crate.name}} - - {{/let}} - {{#if (and @crate.default_version (not @crate.yanked))}} - v{{@crate.default_version}} - - {{svg-jar "copy" alt="Copy Cargo.toml snippet to clipboard"}} - - {{/if}} -
-
- {{ truncate-text @crate.description }} -
-
-
-
- {{svg-jar "download" class=(scoped-class "download-icon")}} - - - All-Time: - - - {{ format-num @crate.downloads }} - -
-
- {{svg-jar "download" class=(scoped-class "download-icon")}} - - - Recent: - - - {{ format-num @crate.recent_downloads }} - -
-
- {{svg-jar "latest-updates" height="32" width="32"}} - - - Updated: - - - - -
-
- - -
\ No newline at end of file diff --git a/app/components/crate-sidebar.gjs b/app/components/crate-sidebar.gjs new file mode 100644 index 00000000000..d5145eede4e --- /dev/null +++ b/app/components/crate-sidebar.gjs @@ -0,0 +1,220 @@ +import { hash } from '@ember/helper'; +import { action } from '@ember/object'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import { didCancel } from 'ember-concurrency'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import eq from 'ember-truth-helpers/helpers/eq'; +import not from 'ember-truth-helpers/helpers/not'; +import or from 'ember-truth-helpers/helpers/or'; + +import CopyButton from 'crates-io/components/copy-button'; +import InstallInstructions from 'crates-io/components/crate-sidebar/install-instructions'; +import Link from 'crates-io/components/crate-sidebar/link'; +import Edition from 'crates-io/components/edition'; +import LicenseExpression from 'crates-io/components/license-expression'; +import Msrv from 'crates-io/components/msrv'; +import OwnersList from 'crates-io/components/owners-list'; +import Tooltip from 'crates-io/components/tooltip'; +import dateFormat from 'crates-io/helpers/date-format'; +import dateFormatDistanceToNow from 'crates-io/helpers/date-format-distance-to-now'; +import dateFormatIso from 'crates-io/helpers/date-format-iso'; +import prettyBytes from 'crates-io/helpers/pretty-bytes'; + +import { simplifyUrl } from './crate-sidebar/link'; + +export default class CrateSidebar extends Component { + + @service notifications; + @service playground; + @service sentry; + + get showHomepage() { + let { repository, homepage } = this.args.crate; + return homepage && (!repository || simplifyUrl(repository) !== simplifyUrl(homepage)); + } + + get playgroundLink() { + let playgroundCrates = this.playground.crates; + if (!playgroundCrates) return; + + let playgroundCrate = playgroundCrates.find(it => it.name === this.args.crate.name); + if (!playgroundCrate) return; + + return `https://play.rust-lang.org/?edition=2021&code=use%20${playgroundCrate.id}%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20%2F%2F%20try%20using%20the%20%60${playgroundCrate.id}%60%20crate%20here%0A%7D`; + } + + get canHover() { + return window?.matchMedia('(hover: hover)').matches; + } + + constructor() { + super(...arguments); + + // load Rust Playground crates list, if necessary + this.playground.loadCrates().catch(error => { + if (!(didCancel(error) || error.isServerError || error.isNetworkError)) { + // report unexpected errors to Sentry + this.sentry.captureException(error); + } + }); + } + + @action + async copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + this.notifications.success('Copied to clipboard!'); + } catch { + this.notifications.error('Copy to clipboard failed!'); + } + } +} diff --git a/app/components/crate-sidebar.hbs b/app/components/crate-sidebar.hbs deleted file mode 100644 index 16f67154c08..00000000000 --- a/app/components/crate-sidebar.hbs +++ /dev/null @@ -1,165 +0,0 @@ - \ No newline at end of file diff --git a/app/components/crate-sidebar.js b/app/components/crate-sidebar.js deleted file mode 100644 index f0cd38545ae..00000000000 --- a/app/components/crate-sidebar.js +++ /dev/null @@ -1,54 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -import { didCancel } from 'ember-concurrency'; - -import { simplifyUrl } from './crate-sidebar/link'; - -export default class CrateSidebar extends Component { - @service notifications; - @service playground; - @service sentry; - - get showHomepage() { - let { repository, homepage } = this.args.crate; - return homepage && (!repository || simplifyUrl(repository) !== simplifyUrl(homepage)); - } - - get playgroundLink() { - let playgroundCrates = this.playground.crates; - if (!playgroundCrates) return; - - let playgroundCrate = playgroundCrates.find(it => it.name === this.args.crate.name); - if (!playgroundCrate) return; - - return `https://play.rust-lang.org/?edition=2021&code=use%20${playgroundCrate.id}%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20%2F%2F%20try%20using%20the%20%60${playgroundCrate.id}%60%20crate%20here%0A%7D`; - } - - get canHover() { - return window?.matchMedia('(hover: hover)').matches; - } - - constructor() { - super(...arguments); - - // load Rust Playground crates list, if necessary - this.playground.loadCrates().catch(error => { - if (!(didCancel(error) || error.isServerError || error.isNetworkError)) { - // report unexpected errors to Sentry - this.sentry.captureException(error); - } - }); - } - - @action - async copyToClipboard(text) { - try { - await navigator.clipboard.writeText(text); - this.notifications.success('Copied to clipboard!'); - } catch { - this.notifications.error('Copy to clipboard failed!'); - } - } -} diff --git a/app/components/crate-sidebar/install-instructions.gjs b/app/components/crate-sidebar/install-instructions.gjs new file mode 100644 index 00000000000..28585096aaf --- /dev/null +++ b/app/components/crate-sidebar/install-instructions.gjs @@ -0,0 +1,105 @@ +import { get } from '@ember/helper'; +import Component from '@glimmer/component'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import and from 'ember-truth-helpers/helpers/and'; +import eq from 'ember-truth-helpers/helpers/eq'; + +import CopyButton from 'crates-io/components/copy-button'; +import isClipboardSupported from 'crates-io/helpers/is-clipboard-supported'; +import sum from 'crates-io/helpers/sum'; + +export default class InstallInstructions extends Component { + + get cargoInstallCommand() { + return this.args.exactVersion + ? `cargo install ${this.args.crate}@${this.args.version}` + : `cargo install ${this.args.crate}`; + } + + get cargoAddCommand() { + return this.args.exactVersion + ? `cargo add ${this.args.crate}@=${this.args.version}` + : `cargo add ${this.args.crate}`; + } + + get tomlSnippet() { + let version = this.args.version.split('+')[0]; + let exact = this.args.exactVersion ? '=' : ''; + return `${this.args.crate} = "${exact}${version}"`; + } +} diff --git a/app/components/crate-sidebar/install-instructions.hbs b/app/components/crate-sidebar/install-instructions.hbs deleted file mode 100644 index 4bb63a8c06e..00000000000 --- a/app/components/crate-sidebar/install-instructions.hbs +++ /dev/null @@ -1,82 +0,0 @@ -{{#if @binNames}} - {{#if (is-clipboard-supported)}} - - {{this.cargoInstallCommand}} - {{svg-jar "copy" aria-hidden="true" class=(scoped-class "copy-icon")}} - - {{else}} - - {{this.cargoInstallCommand}} - - {{/if}} - -

- {{#if (eq @binNames.length 1)}} - Running the above command will globally install the - {{get @binNames 0}} - binary. - {{else if (eq @binNames.length 2)}} - Running the above command will globally install the - {{get @binNames 0}} - and - {{get @binNames 1}} - binaries. - {{else}} - Running the above command will globally install these binaries: - {{#each @binNames as |binName index|~}} - {{~#if (eq index 0)~}} - {{binName}} - {{~else if (eq index (sum @binNames.length -1))}} - and {{binName}} - {{~else~}} - , {{binName}} - {{~/if}} - {{~/each}} - {{/if}} -

- -{{/if}} - -{{#if (and @hasLib @binNames)}} -

Install as library

-{{/if}} - -{{#if @hasLib}} -

Run the following Cargo command in your project directory:

- - {{#if (is-clipboard-supported)}} - - {{this.cargoAddCommand}} - {{svg-jar "copy" aria-hidden="true" class=(scoped-class "copy-icon")}} - - {{else}} - - {{this.cargoAddCommand}} - - {{/if}} - -

Or add the following line to your Cargo.toml:

- - {{#if (is-clipboard-supported)}} - - {{this.tomlSnippet}} - {{svg-jar "copy" aria-hidden="true" class=(scoped-class "copy-icon")}} - - {{else}} - - {{this.tomlSnippet}} - - {{/if}} -{{/if}} \ No newline at end of file diff --git a/app/components/crate-sidebar/install-instructions.js b/app/components/crate-sidebar/install-instructions.js deleted file mode 100644 index e6a8ec185f4..00000000000 --- a/app/components/crate-sidebar/install-instructions.js +++ /dev/null @@ -1,21 +0,0 @@ -import Component from '@glimmer/component'; - -export default class InstallInstructions extends Component { - get cargoInstallCommand() { - return this.args.exactVersion - ? `cargo install ${this.args.crate}@${this.args.version}` - : `cargo install ${this.args.crate}`; - } - - get cargoAddCommand() { - return this.args.exactVersion - ? `cargo add ${this.args.crate}@=${this.args.version}` - : `cargo add ${this.args.crate}`; - } - - get tomlSnippet() { - let version = this.args.version.split('+')[0]; - let exact = this.args.exactVersion ? '=' : ''; - return `${this.args.crate} = "${exact}${version}"`; - } -} diff --git a/app/components/crate-sidebar/link.gjs b/app/components/crate-sidebar/link.gjs new file mode 100644 index 00000000000..5fc5cfdf0b4 --- /dev/null +++ b/app/components/crate-sidebar/link.gjs @@ -0,0 +1,54 @@ +import Component from '@glimmer/component'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +export default class CrateSidebarLink extends Component { + + get text() { + let { url } = this.args; + return simplifyUrl(url); + } + + get isDocsRs() { + return this.text.startsWith('docs.rs/'); + } + + get isGitHub() { + return this.text.startsWith('github.com/'); + } +} + +export function simplifyUrl(url) { + if (url.startsWith('https://')) { + url = url.slice('https://'.length); + } + if (url.startsWith('www.')) { + url = url.slice('www.'.length); + } + if (url.endsWith('/')) { + url = url.slice(0, -1); + } + if (url.startsWith('github.com/') && url.endsWith('.git')) { + url = url.slice(0, -4); + } + + return url; +} diff --git a/app/components/crate-sidebar/link.hbs b/app/components/crate-sidebar/link.hbs deleted file mode 100644 index 69c15822c5e..00000000000 --- a/app/components/crate-sidebar/link.hbs +++ /dev/null @@ -1,16 +0,0 @@ -
-

{{@title}}

-
- {{#if this.isDocsRs}} - {{svg-jar "docs-rs" class=(scoped-class "icon") data-test-icon="docs-rs"}} - {{else if this.isGitHub}} - {{svg-jar "github" class=(scoped-class "icon") data-test-icon="github"}} - {{else}} - {{svg-jar "link" class=(scoped-class "icon") data-test-icon="link"}} - {{/if}} - - - {{this.text}} - -
-
\ No newline at end of file diff --git a/app/components/crate-sidebar/link.js b/app/components/crate-sidebar/link.js deleted file mode 100644 index 687d1c6148b..00000000000 --- a/app/components/crate-sidebar/link.js +++ /dev/null @@ -1,33 +0,0 @@ -import Component from '@glimmer/component'; - -export default class CrateSidebarLink extends Component { - get text() { - let { url } = this.args; - return simplifyUrl(url); - } - - get isDocsRs() { - return this.text.startsWith('docs.rs/'); - } - - get isGitHub() { - return this.text.startsWith('github.com/'); - } -} - -export function simplifyUrl(url) { - if (url.startsWith('https://')) { - url = url.slice('https://'.length); - } - if (url.startsWith('www.')) { - url = url.slice('www.'.length); - } - if (url.endsWith('/')) { - url = url.slice(0, -1); - } - if (url.startsWith('github.com/') && url.endsWith('.git')) { - url = url.slice(0, -4); - } - - return url; -} diff --git a/app/components/dependency-list/row.gjs b/app/components/dependency-list/row.gjs new file mode 100644 index 00000000000..e4dd4d0cb6c --- /dev/null +++ b/app/components/dependency-list/row.gjs @@ -0,0 +1,122 @@ +import { array, fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import { task } from 'ember-concurrency'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import or from 'ember-truth-helpers/helpers/or'; + +import Placeholder from 'crates-io/components/placeholder'; +import Tooltip from 'crates-io/components/tooltip'; +import formatReq from 'crates-io/helpers/format-req'; + +export default class VersionRow extends Component { + + @service store; + + @tracked focused = false; + + @action setFocused(value) { + this.focused = value; + } + + constructor() { + super(...arguments); + + this.loadCrateTask.perform().catch(() => { + // ignore all errors and just don't display a description if the request fails + }); + } + + get description() { + return this.loadCrateTask.lastSuccessful?.value?.description; + } + + get featuresDescription() { + let { default_features: defaultFeatures, features } = this.args.dependency; + let numFeatures = features.length; + + if (numFeatures !== 0) { + return defaultFeatures + ? `${numFeatures} extra feature${numFeatures > 1 ? 's' : ''}` + : `only ${numFeatures} feature${numFeatures > 1 ? 's' : ''}`; + } else if (!defaultFeatures) { + return 'no default features'; + } + } + + loadCrateTask = task(async () => { + let { dependency } = this.args; + return await this.store.findRecord('crate', dependency.crate_id); + }); +} diff --git a/app/components/dependency-list/row.hbs b/app/components/dependency-list/row.hbs deleted file mode 100644 index 5189dc3a131..00000000000 --- a/app/components/dependency-list/row.hbs +++ /dev/null @@ -1,67 +0,0 @@ -
- - {{format-req @dependency.req}} - - -
- - - {{#if (or this.description this.loadCrateTask.isRunning)}} -
- {{#if this.loadCrateTask.isRunning}} - - {{else}} - {{this.description}} - {{/if}} -
- {{/if}} -
-
\ No newline at end of file diff --git a/app/components/dependency-list/row.js b/app/components/dependency-list/row.js deleted file mode 100644 index f9c2243c924..00000000000 --- a/app/components/dependency-list/row.js +++ /dev/null @@ -1,46 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; - -import { task } from 'ember-concurrency'; - -export default class VersionRow extends Component { - @service store; - - @tracked focused = false; - - @action setFocused(value) { - this.focused = value; - } - - constructor() { - super(...arguments); - - this.loadCrateTask.perform().catch(() => { - // ignore all errors and just don't display a description if the request fails - }); - } - - get description() { - return this.loadCrateTask.lastSuccessful?.value?.description; - } - - get featuresDescription() { - let { default_features: defaultFeatures, features } = this.args.dependency; - let numFeatures = features.length; - - if (numFeatures !== 0) { - return defaultFeatures - ? `${numFeatures} extra feature${numFeatures > 1 ? 's' : ''}` - : `only ${numFeatures} feature${numFeatures > 1 ? 's' : ''}`; - } else if (!defaultFeatures) { - return 'no default features'; - } - } - - loadCrateTask = task(async () => { - let { dependency } = this.args; - return await this.store.findRecord('crate', dependency.crate_id); - }); -} diff --git a/app/components/download-graph.js b/app/components/download-graph.gjs similarity index 83% rename from app/components/download-graph.js rename to app/components/download-graph.gjs index 2e30d02706d..3ad6e1d17ea 100644 --- a/app/components/download-graph.js +++ b/app/components/download-graph.gjs @@ -1,4 +1,9 @@ +/* eslint-disable ember/no-at-ember-render-modifiers */ +import { on } from '@ember/modifier'; import { action } from '@ember/object'; +import didInsert from '@ember/render-modifiers/modifiers/did-insert'; +import didUpdate from '@ember/render-modifiers/modifiers/did-update'; +import willDestroy from '@ember/render-modifiers/modifiers/will-destroy'; import { service } from '@ember/service'; import { waitForPromise } from '@ember/test-waiters'; import Component from '@glimmer/component'; @@ -8,12 +13,37 @@ import window from 'ember-window-mock'; import semverSort from 'semver/functions/sort'; // Colors by http://colorbrewer2.org/#type=diverging&scheme=RdBu&n=10 +import LoadingSpinner from 'crates-io/components/loading-spinner'; + const COLORS = ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#92c5de', '#4393c3', '#2166ac', '#053061']; const BG_COLORS = ['#d3b5bc', '#eabdc0', '#f3d0ca', '#fce4d9', '#deedf5', '#c9deed', '#2166ac', '#053061']; const ONE_DAY = 24 * 60 * 60 * 1000; export default class DownloadGraph extends Component { + @service chartjs; @service colorScheme; diff --git a/app/components/download-graph.hbs b/app/components/download-graph.hbs deleted file mode 100644 index 31dc60fadb7..00000000000 --- a/app/components/download-graph.hbs +++ /dev/null @@ -1,30 +0,0 @@ -{{!-- template-lint-disable no-at-ember-render-modifiers --}} -
- {{#if this.chartjs.loadTask.isRunning}} - - {{else if this.chartjs.loadTask.lastSuccessful.value}} - - {{else}} -
-

Sorry, there was a problem loading the graphing code.

- -
- {{/if}} -
\ No newline at end of file diff --git a/app/components/dropdown.gjs b/app/components/dropdown.gjs new file mode 100644 index 00000000000..84a8195a95e --- /dev/null +++ b/app/components/dropdown.gjs @@ -0,0 +1,36 @@ +import { fn, hash } from '@ember/helper'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import onClickOutside from 'ember-click-outside/modifiers/on-click-outside'; +import onKey from 'ember-keyboard/modifiers/on-key'; + +import DropdownContent from 'crates-io/components/dropdown/content'; +import DropdownMenu from 'crates-io/components/dropdown/menu'; +import DropdownTrigger from 'crates-io/components/dropdown/trigger'; + +export default class Dropdown extends Component { + + @tracked dropdownExpanded = false; + + @action + toggleDropdown() { + this.dropdownExpanded = !this.dropdownExpanded; + } +} diff --git a/app/components/dropdown.hbs b/app/components/dropdown.hbs deleted file mode 100644 index be7676065dc..00000000000 --- a/app/components/dropdown.hbs +++ /dev/null @@ -1,12 +0,0 @@ -
- {{yield (hash - Trigger=(component "dropdown/trigger" toggle=this.toggleDropdown) - Content=(component "dropdown/content" isExpanded=this.dropdownExpanded) - Menu=(component "dropdown/menu" Content=(component "dropdown/content" isExpanded=this.dropdownExpanded)) - )}} -
\ No newline at end of file diff --git a/app/components/dropdown.js b/app/components/dropdown.js deleted file mode 100644 index 43a1a08ac89..00000000000 --- a/app/components/dropdown.js +++ /dev/null @@ -1,12 +0,0 @@ -import { action } from '@ember/object'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; - -export default class Dropdown extends Component { - @tracked dropdownExpanded = false; - - @action - toggleDropdown() { - this.dropdownExpanded = !this.dropdownExpanded; - } -} diff --git a/app/components/dropdown/content.gjs b/app/components/dropdown/content.gjs new file mode 100644 index 00000000000..d13cf865b01 --- /dev/null +++ b/app/components/dropdown/content.gjs @@ -0,0 +1,3 @@ + diff --git a/app/components/dropdown/content.hbs b/app/components/dropdown/content.hbs deleted file mode 100644 index c7c87da0137..00000000000 --- a/app/components/dropdown/content.hbs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/components/dropdown/menu-item.gjs b/app/components/dropdown/menu-item.gjs new file mode 100644 index 00000000000..04dbbd48110 --- /dev/null +++ b/app/components/dropdown/menu-item.gjs @@ -0,0 +1,3 @@ + diff --git a/app/components/dropdown/menu-item.hbs b/app/components/dropdown/menu-item.hbs deleted file mode 100644 index bc005561414..00000000000 --- a/app/components/dropdown/menu-item.hbs +++ /dev/null @@ -1 +0,0 @@ -
  • {{yield}}
  • \ No newline at end of file diff --git a/app/components/dropdown/menu.gjs b/app/components/dropdown/menu.gjs new file mode 100644 index 00000000000..b084a74e1ff --- /dev/null +++ b/app/components/dropdown/menu.gjs @@ -0,0 +1,10 @@ +import { hash } from '@ember/helper'; + +import DropdownMenuItem from 'crates-io/components/dropdown/menu-item'; + diff --git a/app/components/dropdown/menu.hbs b/app/components/dropdown/menu.hbs deleted file mode 100644 index a3a5cb95b35..00000000000 --- a/app/components/dropdown/menu.hbs +++ /dev/null @@ -1,5 +0,0 @@ -<@Content ...attributes> - - \ No newline at end of file diff --git a/app/components/dropdown/trigger.gjs b/app/components/dropdown/trigger.gjs new file mode 100644 index 00000000000..51c267e463a --- /dev/null +++ b/app/components/dropdown/trigger.gjs @@ -0,0 +1,9 @@ +import { on } from '@ember/modifier'; + diff --git a/app/components/dropdown/trigger.hbs b/app/components/dropdown/trigger.hbs deleted file mode 100644 index 9446793054e..00000000000 --- a/app/components/dropdown/trigger.hbs +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/app/components/edition.gjs b/app/components/edition.gjs new file mode 100644 index 00000000000..f58cba4f099 --- /dev/null +++ b/app/components/edition.gjs @@ -0,0 +1,19 @@ +import Tooltip from 'crates-io/components/tooltip'; + diff --git a/app/components/edition.hbs b/app/components/edition.hbs deleted file mode 100644 index 839bbac3bd3..00000000000 --- a/app/components/edition.hbs +++ /dev/null @@ -1,14 +0,0 @@ - - {{@version.edition}} edition - - - This crate version does not declare a Minimum Supported Rust Version, but - does require the {{@version.edition}} Rust Edition. - -
    - {{@version.editionMsrv}} was the first version of Rust in this edition, - but this crate may require features that were added in later versions of - Rust. -
    -
    -
    \ No newline at end of file diff --git a/app/components/email-input.gjs b/app/components/email-input.gjs new file mode 100644 index 00000000000..125316a2566 --- /dev/null +++ b/app/components/email-input.gjs @@ -0,0 +1,159 @@ +import { Input } from '@ember/component'; +import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import { task } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; +import preventDefault from 'ember-event-helpers/helpers/prevent-default'; +import and from 'ember-truth-helpers/helpers/and'; +import not from 'ember-truth-helpers/helpers/not'; + +export default class EmailInput extends Component { + + @service notifications; + + @tracked value; + @tracked isEditing = false; + @tracked disableResend = false; + + resendEmailTask = task(async () => { + try { + await this.args.user.resendVerificationEmail(); + this.disableResend = true; + } catch (error) { + let detail = error.errors?.[0]?.detail; + if (detail && !detail.startsWith('{')) { + this.notifications.error(`Error in resending message: ${detail}`); + } else { + this.notifications.error('Unknown error in resending message'); + } + } + }); + + @action + editEmail() { + this.value = this.args.user.email; + this.isEditing = true; + } + + saveEmailTask = task(async () => { + let userEmail = this.value; + let user = this.args.user; + + try { + await user.changeEmail(userEmail); + + this.isEditing = false; + this.disableResend = false; + } catch (error) { + let detail = error.errors?.[0]?.detail; + + let msg = + detail && !detail.startsWith('{') + ? `An error occurred while saving this email, ${detail}` + : 'An unknown error occurred while saving this email.'; + + this.notifications.error(`Error in saving email: ${msg}`); + } + }); +} diff --git a/app/components/email-input.hbs b/app/components/email-input.hbs deleted file mode 100644 index 8f73dbd13bb..00000000000 --- a/app/components/email-input.hbs +++ /dev/null @@ -1,100 +0,0 @@ -
    - {{#unless @user.email}} -
    -

    - Please add your email address. We will only use - it to contact you about your account. We promise we'll never share it! -

    -
    - {{/unless}} - - {{#if this.isEditing }} -
    -
    - -
    - -
    - {{else}} -
    -
    -
    Email
    -
    - -
    - -
    -
    - {{#if (and @user.email (not @user.email_verified))}} -
    -
    - {{#if @user.email_verification_sent}} -

    We have sent a verification email to your address.

    - {{/if}} -

    Your email has not yet been verified.

    -
    -
    - -
    -
    - {{/if}} - {{/if}} - -
    \ No newline at end of file diff --git a/app/components/email-input.js b/app/components/email-input.js deleted file mode 100644 index 993958d9bef..00000000000 --- a/app/components/email-input.js +++ /dev/null @@ -1,55 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; - -import { task } from 'ember-concurrency'; - -export default class EmailInput extends Component { - @service notifications; - - @tracked value; - @tracked isEditing = false; - @tracked disableResend = false; - - resendEmailTask = task(async () => { - try { - await this.args.user.resendVerificationEmail(); - this.disableResend = true; - } catch (error) { - let detail = error.errors?.[0]?.detail; - if (detail && !detail.startsWith('{')) { - this.notifications.error(`Error in resending message: ${detail}`); - } else { - this.notifications.error('Unknown error in resending message'); - } - } - }); - - @action - editEmail() { - this.value = this.args.user.email; - this.isEditing = true; - } - - saveEmailTask = task(async () => { - let userEmail = this.value; - let user = this.args.user; - - try { - await user.changeEmail(userEmail); - - this.isEditing = false; - this.disableResend = false; - } catch (error) { - let detail = error.errors?.[0]?.detail; - - let msg = - detail && !detail.startsWith('{') - ? `An error occurred while saving this email, ${detail}` - : 'An unknown error occurred while saving this email.'; - - this.notifications.error(`Error in saving email: ${msg}`); - } - }); -} diff --git a/app/components/follow-button.js b/app/components/follow-button.gjs similarity index 60% rename from app/components/follow-button.js rename to app/components/follow-button.gjs index ce9b8ecd9f0..3cd6494d923 100644 --- a/app/components/follow-button.js +++ b/app/components/follow-button.gjs @@ -1,12 +1,37 @@ +import { on } from '@ember/modifier'; import { service } from '@ember/service'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { didCancel, dropTask, task } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; +import or from 'ember-truth-helpers/helpers/or'; + +import LoadingSpinner from 'crates-io/components/loading-spinner'; import ajax from '../utils/ajax'; export default class extends Component { + @service notifications; @tracked following = false; diff --git a/app/components/follow-button.hbs b/app/components/follow-button.hbs deleted file mode 100644 index 9a94fe98bb2..00000000000 --- a/app/components/follow-button.hbs +++ /dev/null @@ -1,22 +0,0 @@ - \ No newline at end of file diff --git a/app/components/footer.gjs b/app/components/footer.gjs new file mode 100644 index 00000000000..25eae8f3bca --- /dev/null +++ b/app/components/footer.gjs @@ -0,0 +1,59 @@ +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +export default class Footer extends Component { + + @service pristineQuery; + + get pristineSupportQuery() { + let params = this.pristineQuery.paramsFor('support'); + return params; + } +} diff --git a/app/components/footer.hbs b/app/components/footer.hbs deleted file mode 100644 index d2fcb080ef3..00000000000 --- a/app/components/footer.hbs +++ /dev/null @@ -1,42 +0,0 @@ - \ No newline at end of file diff --git a/app/components/footer.js b/app/components/footer.js deleted file mode 100644 index f2639df2d28..00000000000 --- a/app/components/footer.js +++ /dev/null @@ -1,11 +0,0 @@ -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -export default class Footer extends Component { - @service pristineQuery; - - get pristineSupportQuery() { - let params = this.pristineQuery.paramsFor('support'); - return params; - } -} diff --git a/app/components/front-page-list/item.gjs b/app/components/front-page-list/item.gjs new file mode 100644 index 00000000000..0c46ec2f5fa --- /dev/null +++ b/app/components/front-page-list/item.gjs @@ -0,0 +1,13 @@ +import { on } from '@ember/modifier'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + diff --git a/app/components/front-page-list/item.hbs b/app/components/front-page-list/item.hbs deleted file mode 100644 index f8b437c3a7a..00000000000 --- a/app/components/front-page-list/item.hbs +++ /dev/null @@ -1,7 +0,0 @@ - -
    -
    {{@title}}
    - {{#if @subtitle}}
    {{@subtitle}}
    {{/if}} -
    - {{svg-jar "chevron-right" class=(scoped-class "right")}} -
    \ No newline at end of file diff --git a/app/components/front-page-list/item/placeholder.gjs b/app/components/front-page-list/item/placeholder.gjs new file mode 100644 index 00000000000..97c83abf83f --- /dev/null +++ b/app/components/front-page-list/item/placeholder.gjs @@ -0,0 +1,13 @@ +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +import Placeholder from 'crates-io/components/placeholder'; + diff --git a/app/components/front-page-list/item/placeholder.hbs b/app/components/front-page-list/item/placeholder.hbs deleted file mode 100644 index 5d134acfbd5..00000000000 --- a/app/components/front-page-list/item/placeholder.hbs +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file diff --git a/app/components/header.gjs b/app/components/header.gjs new file mode 100644 index 00000000000..e5c6a582707 --- /dev/null +++ b/app/components/header.gjs @@ -0,0 +1,177 @@ +import { hash } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +// Six hours. +import perform from 'ember-concurrency/helpers/perform'; +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +import ColorSchemeMenu from 'crates-io/components/color-scheme-menu'; +import Dropdown from 'crates-io/components/dropdown'; +import LoadingSpinner from 'crates-io/components/loading-spinner'; +import SearchForm from 'crates-io/components/search-form'; +import UserAvatar from 'crates-io/components/user-avatar'; +import dateFormat from 'crates-io/helpers/date-format'; + +const SUDO_SESSION_DURATION_MS = 6 * 60 * 60 * 1000; + +export default class Header extends Component { + + /** @type {import("../services/session").default} */ + @service session; + + @action + enableSudo() { + this.session.setSudo(SUDO_SESSION_DURATION_MS); + } + + @action + disableSudo() { + this.session.setSudo(0); + } +} diff --git a/app/components/header.hbs b/app/components/header.hbs deleted file mode 100644 index abf847d1069..00000000000 --- a/app/components/header.hbs +++ /dev/null @@ -1,130 +0,0 @@ -
    -
    - - -

    crates.io

    -
    - -
    -

    - The Rust community’s crate registry -

    - - -
    - - - - -
    -
    \ No newline at end of file diff --git a/app/components/header.js b/app/components/header.js deleted file mode 100644 index 96efa8a535a..00000000000 --- a/app/components/header.js +++ /dev/null @@ -1,21 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -// Six hours. -const SUDO_SESSION_DURATION_MS = 6 * 60 * 60 * 1000; - -export default class Header extends Component { - /** @type {import("../services/session").default} */ - @service session; - - @action - enableSudo() { - this.session.setSudo(SUDO_SESSION_DURATION_MS); - } - - @action - disableSudo() { - this.session.setSudo(0); - } -} diff --git a/app/components/license-expression.gjs b/app/components/license-expression.gjs new file mode 100644 index 00000000000..60a4ad31534 --- /dev/null +++ b/app/components/license-expression.gjs @@ -0,0 +1,14 @@ +import parseLicense from 'crates-io/helpers/parse-license'; + diff --git a/app/components/license-expression.hbs b/app/components/license-expression.hbs deleted file mode 100644 index c5f96437c7a..00000000000 --- a/app/components/license-expression.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#each (parse-license @license) as |part|}} - {{#if part.isKeyword}} - {{part.text}} - {{else if part.link}} - - {{part.text}} - - {{else}} - {{part.text}} - {{/if}} -{{/each}} \ No newline at end of file diff --git a/app/components/loading-spinner.gjs b/app/components/loading-spinner.gjs new file mode 100644 index 00000000000..b4b986dcad6 --- /dev/null +++ b/app/components/loading-spinner.gjs @@ -0,0 +1,6 @@ +import eq from 'ember-truth-helpers/helpers/eq'; + diff --git a/app/components/loading-spinner.hbs b/app/components/loading-spinner.hbs deleted file mode 100644 index 324a54586d7..00000000000 --- a/app/components/loading-spinner.hbs +++ /dev/null @@ -1,6 +0,0 @@ -
    - Loading… -
    \ No newline at end of file diff --git a/app/components/msrv.gjs b/app/components/msrv.gjs new file mode 100644 index 00000000000..814e513af97 --- /dev/null +++ b/app/components/msrv.gjs @@ -0,0 +1,13 @@ +import Tooltip from 'crates-io/components/tooltip'; + diff --git a/app/components/msrv.hbs b/app/components/msrv.hbs deleted file mode 100644 index fd14d11491f..00000000000 --- a/app/components/msrv.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - v{{@version.msrv}} - - - "Minimum Supported Rust Version" - {{#if @version.edition}} -
    requires Rust Edition {{@version.edition}}
    - {{/if}} -
    -
    \ No newline at end of file diff --git a/app/components/nav-tabs.gjs b/app/components/nav-tabs.gjs new file mode 100644 index 00000000000..c0a5bf419a5 --- /dev/null +++ b/app/components/nav-tabs.gjs @@ -0,0 +1,10 @@ +import { hash } from '@ember/helper'; + +import NavTabsTab from 'crates-io/components/nav-tabs/tab'; + diff --git a/app/components/nav-tabs.hbs b/app/components/nav-tabs.hbs deleted file mode 100644 index bae81817d69..00000000000 --- a/app/components/nav-tabs.hbs +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/app/components/nav-tabs/tab.gjs b/app/components/nav-tabs/tab.gjs new file mode 100644 index 00000000000..e1661ee0017 --- /dev/null +++ b/app/components/nav-tabs/tab.gjs @@ -0,0 +1,13 @@ +import { on } from '@ember/modifier'; + diff --git a/app/components/nav-tabs/tab.hbs b/app/components/nav-tabs/tab.hbs deleted file mode 100644 index 06ff03a555f..00000000000 --- a/app/components/nav-tabs/tab.hbs +++ /dev/null @@ -1,10 +0,0 @@ -
  • - - {{yield}} - -
  • \ No newline at end of file diff --git a/app/components/owned-crate-row.gjs b/app/components/owned-crate-row.gjs new file mode 100644 index 00000000000..a7f50ce0d6c --- /dev/null +++ b/app/components/owned-crate-row.gjs @@ -0,0 +1,36 @@ +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +export default class OwnedCrateRow extends Component { + + @action setEmailNotifications(event) { + let { checked } = event.target; + this.args.ownedCrate.set('email_notifications', checked); + } +} diff --git a/app/components/owned-crate-row.hbs b/app/components/owned-crate-row.hbs deleted file mode 100644 index e0e8677336b..00000000000 --- a/app/components/owned-crate-row.hbs +++ /dev/null @@ -1,22 +0,0 @@ - \ No newline at end of file diff --git a/app/components/owned-crate-row.js b/app/components/owned-crate-row.js deleted file mode 100644 index 6c312ae24c6..00000000000 --- a/app/components/owned-crate-row.js +++ /dev/null @@ -1,9 +0,0 @@ -import { action } from '@ember/object'; -import Component from '@glimmer/component'; - -export default class OwnedCrateRow extends Component { - @action setEmailNotifications(event) { - let { checked } = event.target; - this.args.ownedCrate.set('email_notifications', checked); - } -} diff --git a/app/components/owners-list.gjs b/app/components/owners-list.gjs new file mode 100644 index 00000000000..fc527d8fea5 --- /dev/null +++ b/app/components/owners-list.gjs @@ -0,0 +1,33 @@ +import { LinkTo } from '@ember/routing'; +import Component from '@glimmer/component'; + +import eq from 'ember-truth-helpers/helpers/eq'; +import or from 'ember-truth-helpers/helpers/or'; + +import UserAvatar from 'crates-io/components/user-avatar'; + +export default class VersionRow extends Component { + + get showDetailedList() { + return this.args.owners.length <= 5; + } +} diff --git a/app/components/owners-list.hbs b/app/components/owners-list.hbs deleted file mode 100644 index 27fc0b14824..00000000000 --- a/app/components/owners-list.hbs +++ /dev/null @@ -1,19 +0,0 @@ - \ No newline at end of file diff --git a/app/components/owners-list.js b/app/components/owners-list.js deleted file mode 100644 index b00cce81c75..00000000000 --- a/app/components/owners-list.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@glimmer/component'; - -export default class VersionRow extends Component { - get showDetailedList() { - return this.args.owners.length <= 5; - } -} diff --git a/app/components/page-header.gjs b/app/components/page-header.gjs new file mode 100644 index 00000000000..a33a461eab8 --- /dev/null +++ b/app/components/page-header.gjs @@ -0,0 +1,18 @@ +import LoadingSpinner from 'crates-io/components/loading-spinner'; + diff --git a/app/components/page-header.hbs b/app/components/page-header.hbs deleted file mode 100644 index d615a9b7727..00000000000 --- a/app/components/page-header.hbs +++ /dev/null @@ -1,15 +0,0 @@ -
    - {{#if (has-block)}} - {{yield}} - {{else}} -

    - {{@title}} - {{#if @suffix}} - {{@suffix}} - {{/if}} - {{#if @showSpinner}} - - {{/if}} -

    - {{/if}} -
    \ No newline at end of file diff --git a/app/components/pagination.gjs b/app/components/pagination.gjs new file mode 100644 index 00000000000..51d69a5d0d5 --- /dev/null +++ b/app/components/pagination.gjs @@ -0,0 +1,35 @@ +import { concat, hash } from '@ember/helper'; +import { LinkTo } from '@ember/routing'; + +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + diff --git a/app/components/pagination.hbs b/app/components/pagination.hbs deleted file mode 100644 index 33c707b636d..00000000000 --- a/app/components/pagination.hbs +++ /dev/null @@ -1,17 +0,0 @@ - \ No newline at end of file diff --git a/app/components/pending-owner-invite-row.gjs b/app/components/pending-owner-invite-row.gjs new file mode 100644 index 00000000000..31c1a1d7b41 --- /dev/null +++ b/app/components/pending-owner-invite-row.gjs @@ -0,0 +1,95 @@ +import { on } from '@ember/modifier'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import { task } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; + +import dateFormatDistanceToNow from 'crates-io/helpers/date-format-distance-to-now'; + +export default class PendingOwnerInviteRow extends Component { + + @service notifications; + + @tracked isAccepted = false; + @tracked isDeclined = false; + + acceptInvitationTask = task(async () => { + this.args.invite.set('accepted', true); + + try { + await this.args.invite.save(); + this.isAccepted = true; + } catch (error) { + let detail = error.errors?.[0]?.detail; + if (detail && !detail.startsWith('{')) { + this.notifications.error(`Error in accepting invite: ${detail}`); + } else { + this.notifications.error('Error in accepting invite'); + } + } + }); + + declineInvitationTask = task(async () => { + this.args.invite.set('accepted', false); + + try { + await this.args.invite.save(); + this.isDeclined = true; + } catch (error) { + let detail = error.errors?.[0]?.detail; + if (detail && !detail.startsWith('{')) { + this.notifications.error(`Error in declining invite: ${detail}`); + } else { + this.notifications.error('Error in declining invite'); + } + } + }); +} diff --git a/app/components/pending-owner-invite-row.hbs b/app/components/pending-owner-invite-row.hbs deleted file mode 100644 index 43482ae7270..00000000000 --- a/app/components/pending-owner-invite-row.hbs +++ /dev/null @@ -1,34 +0,0 @@ -{{#if this.isAccepted }} -

    - Success! You've been added as an owner of crate - {{@invite.crate_name}}. -

    -{{else if this.isDeclined}} -

    - Declined. You have not been added as an owner of crate - {{@invite.crate_name}}. -

    -{{else}} -
    -
    -

    - - {{@invite.crate_name}} - -

    -
    -
    - Invited by: - - {{@invite.inviter.login}} - -
    -
    - {{date-format-distance-to-now @invite.created_at addSuffix=true}} -
    -
    - - -
    -
    -{{/if}} \ No newline at end of file diff --git a/app/components/pending-owner-invite-row.js b/app/components/pending-owner-invite-row.js deleted file mode 100644 index 3893ab2178f..00000000000 --- a/app/components/pending-owner-invite-row.js +++ /dev/null @@ -1,44 +0,0 @@ -import { service } from '@ember/service'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; - -import { task } from 'ember-concurrency'; - -export default class PendingOwnerInviteRow extends Component { - @service notifications; - - @tracked isAccepted = false; - @tracked isDeclined = false; - - acceptInvitationTask = task(async () => { - this.args.invite.set('accepted', true); - - try { - await this.args.invite.save(); - this.isAccepted = true; - } catch (error) { - let detail = error.errors?.[0]?.detail; - if (detail && !detail.startsWith('{')) { - this.notifications.error(`Error in accepting invite: ${detail}`); - } else { - this.notifications.error('Error in accepting invite'); - } - } - }); - - declineInvitationTask = task(async () => { - this.args.invite.set('accepted', false); - - try { - await this.args.invite.save(); - this.isDeclined = true; - } catch (error) { - let detail = error.errors?.[0]?.detail; - if (detail && !detail.startsWith('{')) { - this.notifications.error(`Error in declining invite: ${detail}`); - } else { - this.notifications.error('Error in declining invite'); - } - } - }); -} diff --git a/app/components/placeholder.gjs b/app/components/placeholder.gjs new file mode 100644 index 00000000000..c6f791ed8bf --- /dev/null +++ b/app/components/placeholder.gjs @@ -0,0 +1,3 @@ + diff --git a/app/components/placeholder.hbs b/app/components/placeholder.hbs deleted file mode 100644 index 041d8b1f3b6..00000000000 --- a/app/components/placeholder.hbs +++ /dev/null @@ -1 +0,0 @@ -
    \ No newline at end of file diff --git a/app/components/privileged-action.js b/app/components/privileged-action.gjs similarity index 70% rename from app/components/privileged-action.js rename to app/components/privileged-action.gjs index 814de90ebc4..073da1ac295 100644 --- a/app/components/privileged-action.js +++ b/app/components/privileged-action.gjs @@ -29,7 +29,36 @@ import Component from '@glimmer/component'; * Note that all blocks will be output with a wrapping `
    ` for technical * reasons, so be sure to style accordingly if necessary. */ +import Tooltip from 'crates-io/components/tooltip'; export default class PrivilegedAction extends Component { + /** @type {import("../services/session").default} */ @service session; diff --git a/app/components/privileged-action.hbs b/app/components/privileged-action.hbs deleted file mode 100644 index 8dc0c98b0d2..00000000000 --- a/app/components/privileged-action.hbs +++ /dev/null @@ -1,26 +0,0 @@ -{{#if this.isPrivileged}} -
    - {{yield}} -
    -{{else if this.canBePrivileged}} - {{#if (has-block 'placeholder')}} -
    - {{yield to='placeholder'}} -
    - {{else}} -
    -
    - {{yield}} -
    - - You must enable admin actions before you can perform this operation. - -
    - {{/if}} -{{else}} -
    - {{#if (has-block 'unprivileged')}} - {{yield to='unprivileged'}} - {{/if}} -
    -{{/if}} \ No newline at end of file diff --git a/app/components/progress-bar.js b/app/components/progress-bar.gjs similarity index 61% rename from app/components/progress-bar.js rename to app/components/progress-bar.gjs index 7460a24d892..1ef36a9a087 100644 --- a/app/components/progress-bar.js +++ b/app/components/progress-bar.gjs @@ -2,5 +2,8 @@ import { service } from '@ember/service'; import Component from '@glimmer/component'; export default class extends Component { + @service progress; } diff --git a/app/components/progress-bar.hbs b/app/components/progress-bar.hbs deleted file mode 100644 index 306159404fa..00000000000 --- a/app/components/progress-bar.hbs +++ /dev/null @@ -1 +0,0 @@ -
    \ No newline at end of file diff --git a/app/components/rendered-html.gjs b/app/components/rendered-html.gjs new file mode 100644 index 00000000000..7c24abc4ae6 --- /dev/null +++ b/app/components/rendered-html.gjs @@ -0,0 +1,26 @@ +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import TextContent from 'crates-io/components/text-content'; +import htmlSafe from 'crates-io/helpers/html-safe'; +import highlightSyntax from 'crates-io/modifiers/highlight-syntax'; +import renderMermaids from 'crates-io/modifiers/render-mermaids'; +import updateSourceMedia from 'crates-io/modifiers/update-source-media'; + +export default class extends Component { + + @service colorScheme; +} diff --git a/app/components/rendered-html.hbs b/app/components/rendered-html.hbs deleted file mode 100644 index 39867f16118..00000000000 --- a/app/components/rendered-html.hbs +++ /dev/null @@ -1,12 +0,0 @@ -{{!-- - This component renders raw HTML. Be very careful with this since it - can enable cross-site scripting attacks! ---}} - - {{html-safe @html}} - \ No newline at end of file diff --git a/app/components/rendered-html.js b/app/components/rendered-html.js deleted file mode 100644 index f6784d89d72..00000000000 --- a/app/components/rendered-html.js +++ /dev/null @@ -1,6 +0,0 @@ -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -export default class extends Component { - @service colorScheme; -} diff --git a/app/components/results-count.gjs b/app/components/results-count.gjs new file mode 100644 index 00000000000..900593c5154 --- /dev/null +++ b/app/components/results-count.gjs @@ -0,0 +1,9 @@ + diff --git a/app/components/results-count.hbs b/app/components/results-count.hbs deleted file mode 100644 index f1387bf8dd6..00000000000 --- a/app/components/results-count.hbs +++ /dev/null @@ -1,5 +0,0 @@ - - Displaying - {{@start}}-{{@end}} - of {{@total}} {{if @name @name "total results"}} - diff --git a/app/components/rev-dep-row.gjs b/app/components/rev-dep-row.gjs new file mode 100644 index 00000000000..dca89fb46cc --- /dev/null +++ b/app/components/rev-dep-row.gjs @@ -0,0 +1,78 @@ +import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import { task } from 'ember-concurrency'; +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import or from 'ember-truth-helpers/helpers/or'; + +import Placeholder from 'crates-io/components/placeholder'; +import formatNum from 'crates-io/helpers/format-num'; + +export default class VersionRow extends Component { + + @service store; + + @tracked focused = false; + + @action setFocused(value) { + this.focused = value; + } + + constructor() { + super(...arguments); + + this.loadCrateTask.perform().catch(() => { + // ignore all errors and just don't display a description if the request fails + }); + } + + get description() { + return this.loadCrateTask.lastSuccessful?.value?.description; + } + + loadCrateTask = task(async () => { + let { dependency } = this.args; + return await this.store.findRecord('crate', dependency.version.crateName); + }); +} diff --git a/app/components/rev-dep-row.hbs b/app/components/rev-dep-row.hbs deleted file mode 100644 index 157b9c92ab6..00000000000 --- a/app/components/rev-dep-row.hbs +++ /dev/null @@ -1,33 +0,0 @@ -
    -
    -
    - - {{@dependency.version.crateName}} - - - depends on {{@dependency.req}} - -
    -
    - {{svg-jar "download-arrow" class=(scoped-class "download-icon")}} - {{format-num @dependency.downloads}} -
    -
    - - {{#if (or this.description this.loadCrateTask.isRunning)}} -
    - {{#if this.loadCrateTask.isRunning}} - - {{else}} - {{this.description}} - {{/if}} -
    - {{/if}} -
    diff --git a/app/components/rev-dep-row.js b/app/components/rev-dep-row.js deleted file mode 100644 index b5c727144df..00000000000 --- a/app/components/rev-dep-row.js +++ /dev/null @@ -1,33 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; -import { tracked } from '@glimmer/tracking'; - -import { task } from 'ember-concurrency'; - -export default class VersionRow extends Component { - @service store; - - @tracked focused = false; - - @action setFocused(value) { - this.focused = value; - } - - constructor() { - super(...arguments); - - this.loadCrateTask.perform().catch(() => { - // ignore all errors and just don't display a description if the request fails - }); - } - - get description() { - return this.loadCrateTask.lastSuccessful?.value?.description; - } - - loadCrateTask = task(async () => { - let { dependency } = this.args; - return await this.store.findRecord('crate', dependency.version.crateName); - }); -} diff --git a/app/components/search-form.gjs b/app/components/search-form.gjs new file mode 100644 index 00000000000..8c1cec595b5 --- /dev/null +++ b/app/components/search-form.gjs @@ -0,0 +1,90 @@ +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import preventDefault from 'ember-event-helpers/helpers/prevent-default'; +import onKey from 'ember-keyboard/helpers/on-key'; +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import eq from 'ember-truth-helpers/helpers/eq'; + +import focus from 'crates-io/helpers/focus'; + +export default class Header extends Component { + + @service header; + @service router; + + @action updateSearchValue(event) { + let { value } = event.target; + this.header.searchValue = value; + } + + @action search() { + this.router.transitionTo('search', { + queryParams: { + q: this.header.searchValue, + page: 1, + }, + }); + } +} diff --git a/app/components/search-form.hbs b/app/components/search-form.hbs deleted file mode 100644 index d8dd2253837..00000000000 --- a/app/components/search-form.hbs +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/app/components/search-form.js b/app/components/search-form.js deleted file mode 100644 index 4b3aa5343f9..00000000000 --- a/app/components/search-form.js +++ /dev/null @@ -1,22 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -export default class Header extends Component { - @service header; - @service router; - - @action updateSearchValue(event) { - let { value } = event.target; - this.header.searchValue = value; - } - - @action search() { - this.router.transitionTo('search', { - queryParams: { - q: this.header.searchValue, - page: 1, - }, - }); - } -} diff --git a/app/components/settings-page.gjs b/app/components/settings-page.gjs new file mode 100644 index 00000000000..972661458a9 --- /dev/null +++ b/app/components/settings-page.gjs @@ -0,0 +1,15 @@ +import link_ from 'ember-link/helpers/link'; + +import SideMenu from 'crates-io/components/side-menu'; + diff --git a/app/components/settings-page.hbs b/app/components/settings-page.hbs deleted file mode 100644 index 00fdcfcaabf..00000000000 --- a/app/components/settings-page.hbs +++ /dev/null @@ -1,10 +0,0 @@ -
    - - Profile - API Tokens - - -
    - {{yield}} -
    -
    \ No newline at end of file diff --git a/app/components/settings/api-tokens.gjs b/app/components/settings/api-tokens.gjs new file mode 100644 index 00000000000..c39dad71df4 --- /dev/null +++ b/app/components/settings/api-tokens.gjs @@ -0,0 +1,233 @@ +import { hash } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { LinkTo } from '@ember/routing'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; + +import { task } from 'ember-concurrency'; +import perform from 'ember-concurrency/helpers/perform'; +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; +import eq from 'ember-truth-helpers/helpers/eq'; +import or from 'ember-truth-helpers/helpers/or'; + +import CopyButton from 'crates-io/components/copy-button'; +import LoadingSpinner from 'crates-io/components/loading-spinner'; +import Tooltip from 'crates-io/components/tooltip'; +import dateFormatDistanceToNow from 'crates-io/helpers/date-format-distance-to-now'; +import isClipboardSupported from 'crates-io/helpers/is-clipboard-supported'; + +import { patternDescription, scopeDescription } from '../../utils/token-scopes'; + +export default class ApiTokens extends Component { + + @service store; + @service notifications; + @service router; + + scopeDescription = scopeDescription; + patternDescription = patternDescription; + + get sortedTokens() { + return this.args.tokens + .filter(t => !t.isNew) + .sort((a, b) => { + // Expired tokens are always shown after active ones. + if (a.isExpired && !b.isExpired) { + return 1; + } else if (b.isExpired && !a.isExpired) { + return -1; + } + + // Otherwise, sort normally based on creation time. + return a.created_at < b.created_at ? 1 : -1; + }); + } + + listToParts(list) { + // We hardcode `en-US` here because the rest of the interface text is also currently displayed only in English. + return new Intl.ListFormat('en-US').formatToParts(list); + } + + @action startNewToken() { + this.router.transitionTo('settings.tokens.new'); + } + + revokeTokenTask = task(async token => { + try { + await token.destroyRecord(); + + let index = this.args.tokens.indexOf(token); + if (index !== -1) { + this.args.tokens.splice(index, 1); + } + } catch (error) { + let detail = error.errors?.[0]?.detail; + + let msg = + detail && !detail.startsWith('{') + ? `An error occurred while revoking this token, ${detail}` + : 'An unknown error occurred while revoking this token'; + + this.notifications.error(msg); + } + }); +} diff --git a/app/components/settings/api-tokens.hbs b/app/components/settings/api-tokens.hbs deleted file mode 100644 index 00e67ea503e..00000000000 --- a/app/components/settings/api-tokens.hbs +++ /dev/null @@ -1,155 +0,0 @@ -
    -

    API Tokens

    -
    - - New Token - -
    -
    - -

    - You can use the API tokens generated on this page to run cargo - commands that need write access to crates.io. If you want to publish your own - crates then this is required. -

    - -

    - To prevent keys being silently leaked they are stored on crates.io in hashed form. This means you - can only download keys when you first create them. If you have old unused keys you can safely delete - them and create a new one. -

    - -

    - To use an API token, run cargo login - on the command line and paste the key when prompted. This will save it to a - local credentials file. - For CI systems you can use the - CARGO_REGISTRY_TOKEN - environment variable, but make sure that the token stays secret! -

    - -{{#if this.sortedTokens}} - -{{else}} -
    -
    - You have not generated any API tokens yet. -
    - - - New Token - -
    -{{/if}} \ No newline at end of file diff --git a/app/components/settings/api-tokens.js b/app/components/settings/api-tokens.js deleted file mode 100644 index dc66cdd70b9..00000000000 --- a/app/components/settings/api-tokens.js +++ /dev/null @@ -1,61 +0,0 @@ -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -import { task } from 'ember-concurrency'; - -import { patternDescription, scopeDescription } from '../../utils/token-scopes'; - -export default class ApiTokens extends Component { - @service store; - @service notifications; - @service router; - - scopeDescription = scopeDescription; - patternDescription = patternDescription; - - get sortedTokens() { - return this.args.tokens - .filter(t => !t.isNew) - .sort((a, b) => { - // Expired tokens are always shown after active ones. - if (a.isExpired && !b.isExpired) { - return 1; - } else if (b.isExpired && !a.isExpired) { - return -1; - } - - // Otherwise, sort normally based on creation time. - return a.created_at < b.created_at ? 1 : -1; - }); - } - - listToParts(list) { - // We hardcode `en-US` here because the rest of the interface text is also currently displayed only in English. - return new Intl.ListFormat('en-US').formatToParts(list); - } - - @action startNewToken() { - this.router.transitionTo('settings.tokens.new'); - } - - revokeTokenTask = task(async token => { - try { - await token.destroyRecord(); - - let index = this.args.tokens.indexOf(token); - if (index !== -1) { - this.args.tokens.splice(index, 1); - } - } catch (error) { - let detail = error.errors?.[0]?.detail; - - let msg = - detail && !detail.startsWith('{') - ? `An error occurred while revoking this token, ${detail}` - : 'An unknown error occurred while revoking this token'; - - this.notifications.error(msg); - } - }); -} diff --git a/app/components/side-menu.gjs b/app/components/side-menu.gjs new file mode 100644 index 00000000000..913348b78ad --- /dev/null +++ b/app/components/side-menu.gjs @@ -0,0 +1,8 @@ +import { hash } from '@ember/helper'; + +import SideMenuItem from 'crates-io/components/side-menu/item'; + diff --git a/app/components/side-menu.hbs b/app/components/side-menu.hbs deleted file mode 100644 index e82c9fa141c..00000000000 --- a/app/components/side-menu.hbs +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/app/components/side-menu/item.gjs b/app/components/side-menu/item.gjs new file mode 100644 index 00000000000..f4fcbd8c248 --- /dev/null +++ b/app/components/side-menu/item.gjs @@ -0,0 +1,6 @@ +import { on } from '@ember/modifier'; + diff --git a/app/components/side-menu/item.hbs b/app/components/side-menu/item.hbs deleted file mode 100644 index ed05714cdbb..00000000000 --- a/app/components/side-menu/item.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
  • - {{yield}} -
  • \ No newline at end of file diff --git a/app/components/sort-dropdown.gjs b/app/components/sort-dropdown.gjs new file mode 100644 index 00000000000..0005e3a254a --- /dev/null +++ b/app/components/sort-dropdown.gjs @@ -0,0 +1,19 @@ +import { hash } from '@ember/helper'; + +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + +import Dropdown from 'crates-io/components/dropdown'; +import SortDropdownOption from 'crates-io/components/sort-dropdown/option'; + diff --git a/app/components/sort-dropdown.hbs b/app/components/sort-dropdown.hbs deleted file mode 100644 index dd6c157233b..00000000000 --- a/app/components/sort-dropdown.hbs +++ /dev/null @@ -1,10 +0,0 @@ - - - {{svg-jar "sort" class=(scoped-class "icon")}} - {{@current}} - - - - {{yield (hash Option=(component "sort-dropdown/option" menu=menu))}} - - \ No newline at end of file diff --git a/app/components/sort-dropdown/option.gjs b/app/components/sort-dropdown/option.gjs new file mode 100644 index 00000000000..a24c722b4a9 --- /dev/null +++ b/app/components/sort-dropdown/option.gjs @@ -0,0 +1,6 @@ +import { LinkTo } from '@ember/routing'; + diff --git a/app/components/sort-dropdown/option.hbs b/app/components/sort-dropdown/option.hbs deleted file mode 100644 index c0322393a52..00000000000 --- a/app/components/sort-dropdown/option.hbs +++ /dev/null @@ -1,3 +0,0 @@ -<@menu.Item ...attributes> - {{yield}} - \ No newline at end of file diff --git a/app/components/stats-value.gjs b/app/components/stats-value.gjs new file mode 100644 index 00000000000..96b34338740 --- /dev/null +++ b/app/components/stats-value.gjs @@ -0,0 +1,9 @@ +import scopedClass from 'ember-scoped-css/helpers/scoped-class'; +import svgJar from 'ember-svg-jar/helpers/svg-jar'; + diff --git a/app/components/stats-value.hbs b/app/components/stats-value.hbs deleted file mode 100644 index 7d0cc254bb6..00000000000 --- a/app/components/stats-value.hbs +++ /dev/null @@ -1,5 +0,0 @@ -
    - {{@value}} - {{@label}} - {{svg-jar @icon role="img" aria-hidden="true" class=(scoped-class "icon")}} -
    \ No newline at end of file diff --git a/app/components/support/crate-report-form.gjs b/app/components/support/crate-report-form.gjs new file mode 100644 index 00000000000..9af9b2e9b91 --- /dev/null +++ b/app/components/support/crate-report-form.gjs @@ -0,0 +1,198 @@ +import { Input, Textarea } from '@ember/component'; +import { fn, uniqueId } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import autoFocus from '@zestia/ember-auto-focus/modifiers/auto-focus'; +import preventDefault from 'ember-event-helpers/helpers/prevent-default'; +import window from 'ember-window-mock'; + +const REASONS = [ + { + reason: 'spam', + description: 'it contains spam', + }, + { + reason: 'name-squatting', + description: 'it is name-squatting (reserving a crate name without content)', + }, + { + reason: 'abuse', + description: 'it is abusive or otherwise harmful', + }, + { + reason: 'security', + description: 'it contains a vulnerability (please try to contact the crate author first)', + }, + { + reason: 'other', + description: 'it is violating the usage policy in some other way (please specify below)', + }, +]; + +export default class CrateReportForm extends Component { +