diff --git a/web/app/components/document/sidebar/header.ts b/web/app/components/document/sidebar/header.ts index f80d2c075..9cf17e5d3 100644 --- a/web/app/components/document/sidebar/header.ts +++ b/web/app/components/document/sidebar/header.ts @@ -2,6 +2,7 @@ import Component from "@glimmer/component"; import ConfigService from "hermes/services/config"; import { inject as service } from "@ember/service"; import { HermesDocument } from "hermes/types/document"; +import isValidURL from "hermes/utils/is-valid-u-r-l"; interface DocumentSidebarHeaderComponentSignature { Args: { @@ -12,15 +13,6 @@ interface DocumentSidebarHeaderComponentSignature { }; } -export function isValidURL(input: string) { - try { - new URL(input); - return true; - } catch { - return false; - } -} - export default class DocumentSidebarHeaderComponent extends Component { @service("config") declare configSvc: ConfigService; diff --git a/web/app/components/document/sidebar/related-resources/add.ts b/web/app/components/document/sidebar/related-resources/add.ts index d4b48523c..f89da313d 100644 --- a/web/app/components/document/sidebar/related-resources/add.ts +++ b/web/app/components/document/sidebar/related-resources/add.ts @@ -13,6 +13,7 @@ import { RelatedHermesDocument, } from "hermes/components/document/sidebar/related-resources"; import Ember from "ember"; +import isValidURL from "hermes/utils/is-valid-u-r-l"; interface DocumentSidebarRelatedResourcesAddComponentSignature { Element: null; @@ -48,7 +49,6 @@ export default class DocumentSidebarRelatedResourcesAddComponent extends Compone @tracked externalLinkTitle = ""; - get noMatchesFound(): boolean { const objectEntriesLengthIsZero = Object.entries(this.args.shownDocuments).length === 0; @@ -127,6 +127,7 @@ export default class DocumentSidebarRelatedResourcesAddComponent extends Compone return link.url === externalLink.url; }); + if (isDuplicate) { this.showDuplicateMessage(); } else { @@ -218,15 +219,9 @@ export default class DocumentSidebarRelatedResourcesAddComponent extends Compone }); protected checkURL = restartableTask(async () => { - const url = this.query; - try { - this.queryIsURL = Boolean(new URL(url)); - } catch (e) { - this.queryIsURL = false; - } finally { - if (this.queryIsURL) { - void this.fetchURLInfo.perform(); - } + this.queryIsURL = await isValidURL(this.query); + if (this.queryIsURL) { + void this.fetchURLInfo.perform(); } }); } diff --git a/web/app/components/document/sidebar/related-resources/add/external-resource.hbs b/web/app/components/document/sidebar/related-resources/add/external-resource.hbs index 76e2d14b7..119bd6d7c 100644 --- a/web/app/components/document/sidebar/related-resources/add/external-resource.hbs +++ b/web/app/components/document/sidebar/related-resources/add/external-resource.hbs @@ -12,21 +12,22 @@ {{else}}
-
+ + > + Title +
-
{{@url}}
+
+ {{@url}} +
diff --git a/web/app/components/document/sidebar/related-resources/list-item.hbs b/web/app/components/document/sidebar/related-resources/list-item.hbs index 93619e376..41b773ee7 100644 --- a/web/app/components/document/sidebar/related-resources/list-item.hbs +++ b/web/app/components/document/sidebar/related-resources/list-item.hbs @@ -16,7 +16,6 @@ /> {{else}} - {{log @resource}} {{/if}} diff --git a/web/app/components/document/sidebar/related-resources/list-item/edit.hbs b/web/app/components/document/sidebar/related-resources/list-item/edit.hbs index 0a13de5d8..7c74107ee 100644 --- a/web/app/components/document/sidebar/related-resources/list-item/edit.hbs +++ b/web/app/components/document/sidebar/related-resources/list-item/edit.hbs @@ -6,10 +6,15 @@ > Edit resource -
+ URL + {{#if this.errorMessageIsShown}} + + A valid URL is required. + + {{/if}} {{! This adds enter-to-submit functionality }} @@ -37,13 +47,19 @@ @text="Save changes" @color="primary" {{! Should it show a flashMessage? }} - {{on "click" this.onSave}} + {{on "click" (perform this.onSave)}} /> + diff --git a/web/app/components/document/sidebar/related-resources/list-item/edit.ts b/web/app/components/document/sidebar/related-resources/list-item/edit.ts index e0f181ea9..cda37fdf3 100644 --- a/web/app/components/document/sidebar/related-resources/list-item/edit.ts +++ b/web/app/components/document/sidebar/related-resources/list-item/edit.ts @@ -2,15 +2,15 @@ import { assert } from "@ember/debug"; import { action } from "@ember/object"; import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; +import { restartableTask } from "ember-concurrency"; import { RelatedExternalLink } from "hermes/components/document/sidebar/related-resources"; +import isValidURL from "hermes/utils/is-valid-u-r-l"; interface DocumentSidebarRelatedResourcesListItemEditComponentSignature { Args: { resource: RelatedExternalLink; hideModal: () => void; onSave: (resource: RelatedExternalLink) => void; - // Temporary workaround until this is an attribute of the resource - url: string; }; Blocks: { default: []; @@ -20,11 +20,15 @@ interface DocumentSidebarRelatedResourcesListItemEditComponentSignature { export default class DocumentSidebarRelatedResourcesListItemEditComponent extends Component { @tracked resource = this.args.resource; - @tracked url = this.args.url; + @tracked url = this.args.resource.url; @tracked title = this.args.resource.title; + @tracked errorMessageIsShown = false; + @tracked _form: HTMLFormElement | null = null; + @tracked urlIsValid = false; + @action protected registerForm(form: HTMLFormElement): void { this._form = form; } @@ -45,20 +49,34 @@ export default class DocumentSidebarRelatedResourcesListItemEditComponent extend this.title = title; this.url = url; + + void this.validateURL.perform(); } - @action protected onSave(): void { + protected validateURL = restartableTask(async () => { + this.urlIsValid = await isValidURL(this.url); + this.errorMessageIsShown = !this.urlIsValid; + }); + + protected onSave = restartableTask(async (e: Event) => { + // prevent the form from submitting on enter + e.preventDefault(); + let newResource = this.args.resource; newResource.url = this.url; newResource.title = this.title; - // TODO: validate fields - // if the title is empty, use a fallback (url, domain) - // validate that the url is a valid url - // if the url is invalid, show an error message and start eager validation + await this.validateURL.perform(); - this.args.onSave(newResource); - } + if (this.urlIsValid) { + if (!this.args.resource.title.length) { + newResource.title = this.url; + } + this.args.onSave(newResource); + } else { + this.errorMessageIsShown = true; + } + }); } declare module "@glint/environment-ember-loose/registry" { diff --git a/web/app/components/document/sidebar/related-resources/list-item/resource.hbs b/web/app/components/document/sidebar/related-resources/list-item/resource.hbs index c754737e9..8688e10b7 100644 --- a/web/app/components/document/sidebar/related-resources/list-item/resource.hbs +++ b/web/app/components/document/sidebar/related-resources/list-item/resource.hbs @@ -8,7 +8,7 @@ {{else}} {{/if}}
@@ -18,7 +18,7 @@ @tagName="h4" class="text-body-200 text-color-foreground-strong font-medium" > - {{@resource.title}} + {{this.title}} {{#if this.resourceIsDocument}}
diff --git a/web/app/components/document/sidebar/related-resources/list-item/resource.ts b/web/app/components/document/sidebar/related-resources/list-item/resource.ts index eed234bf6..72ff9ada9 100644 --- a/web/app/components/document/sidebar/related-resources/list-item/resource.ts +++ b/web/app/components/document/sidebar/related-resources/list-item/resource.ts @@ -1,6 +1,9 @@ import { action } from "@ember/object"; import Component from "@glimmer/component"; -import { RelatedHermesDocument, RelatedResource } from "../../related-resources"; +import { + RelatedHermesDocument, + RelatedResource, +} from "../../related-resources"; import { assert } from "@ember/debug"; interface DocumentSidebarRelatedResourcesListItemResourceComponentSignature { @@ -25,6 +28,13 @@ export default class DocumentSidebarRelatedResourcesListItemResourceComponent ex return this.args.resource.documentNumber; } + protected get title() { + if ("url" in this.args.resource) { + return this.args.resource.title || this.url; + } + return this.args.resource.title; + } + assertResourceIsDocument( document: RelatedResource ): asserts document is RelatedHermesDocument { diff --git a/web/app/helpers/get-link-icon.ts b/web/app/helpers/get-link-icon.ts index a3597427c..304b6e9d1 100644 --- a/web/app/helpers/get-link-icon.ts +++ b/web/app/helpers/get-link-icon.ts @@ -42,7 +42,7 @@ const getLinkIconHelper = helper(([url]) => { } } } - return "external-link"; + return "file"; }); export default getLinkIconHelper; diff --git a/web/app/styles/components/document/related-resources.scss b/web/app/styles/components/document/related-resources.scss index ab8e4cffb..d6ba655db 100644 --- a/web/app/styles/components/document/related-resources.scss +++ b/web/app/styles/components/document/related-resources.scss @@ -115,6 +115,10 @@ .external-resource-title-input { @apply text-display-300 font-semibold h-11 px-2.5; + + &::placeholder { + @apply opacity-50; + } } .external-resource-url { diff --git a/web/app/utils/is-valid-u-r-l.ts b/web/app/utils/is-valid-u-r-l.ts new file mode 100644 index 000000000..e53a1a440 --- /dev/null +++ b/web/app/utils/is-valid-u-r-l.ts @@ -0,0 +1,8 @@ +export default async function isValidURL(string: string): Promise { + try { + new URL(string); + return true; + } catch { + return false; + } +}