@internetarchive/elements 1.1.3-webdev-9107.0 → 1.1.3-webdev-8840.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/src/elements/ia-reviews/ia-review-form.d.ts +2 -7
  2. package/dist/src/elements/ia-reviews/ia-review-form.d.ts.map +1 -1
  3. package/dist/src/elements/ia-reviews/ia-review-form.js +11 -43
  4. package/dist/src/elements/ia-reviews/ia-review-form.js.map +1 -1
  5. package/dist/src/elements/ia-reviews/ia-review.d.ts +2 -1
  6. package/dist/src/elements/ia-reviews/ia-review.d.ts.map +1 -1
  7. package/dist/src/elements/ia-reviews/ia-review.js +13 -25
  8. package/dist/src/elements/ia-reviews/ia-review.js.map +1 -1
  9. package/dist/src/elements/ia-reviews/ia-reviews.d.ts +9 -4
  10. package/dist/src/elements/ia-reviews/ia-reviews.d.ts.map +1 -1
  11. package/dist/src/elements/ia-reviews/ia-reviews.js +16 -20
  12. package/dist/src/elements/ia-reviews/ia-reviews.js.map +1 -1
  13. package/dist/src/elements/ia-reviews/mocks/mock-fetch-handler.d.ts +17 -2
  14. package/dist/src/elements/ia-reviews/mocks/mock-fetch-handler.d.ts.map +1 -1
  15. package/dist/src/elements/ia-reviews/mocks/mock-fetch-handler.js +24 -2
  16. package/dist/src/elements/ia-reviews/mocks/mock-fetch-handler.js.map +1 -1
  17. package/dist/src/elements/ia-reviews/mocks/mock-review-service.d.ts +12 -0
  18. package/dist/src/elements/ia-reviews/mocks/mock-review-service.d.ts.map +1 -0
  19. package/dist/src/elements/ia-reviews/mocks/mock-review-service.js +19 -0
  20. package/dist/src/elements/ia-reviews/mocks/mock-review-service.js.map +1 -0
  21. package/dist/src/elements/ia-reviews/models.d.ts +38 -0
  22. package/dist/src/elements/ia-reviews/models.d.ts.map +1 -0
  23. package/dist/src/elements/ia-reviews/models.js +2 -0
  24. package/dist/src/elements/ia-reviews/models.js.map +1 -0
  25. package/dist/src/elements/ia-reviews/review-service.d.ts +53 -0
  26. package/dist/src/elements/ia-reviews/review-service.d.ts.map +1 -0
  27. package/dist/src/elements/ia-reviews/review-service.js +97 -0
  28. package/dist/src/elements/ia-reviews/review-service.js.map +1 -0
  29. package/dist/src/elements/index.d.ts +2 -0
  30. package/dist/src/elements/index.d.ts.map +1 -1
  31. package/dist/src/elements/index.js +2 -0
  32. package/dist/src/elements/index.js.map +1 -1
  33. package/package.json +1 -1
@@ -1,17 +1,14 @@
1
1
  import { LitElement, PropertyValues, CSSResultGroup } from 'lit';
2
2
  import type { RecaptchaManagerInterface } from '@internetarchive/recaptcha-manager';
3
3
  import '../../elements/ia-button/ia-button.js';
4
- import type { FetchHandlerInterface } from '@internetarchive/fetch-handler';
5
4
  import { Review } from '@internetarchive/metadata-service';
5
+ import type { ReviewServiceInterface } from './models.js';
6
6
  import './ia-review.js';
7
7
  /**
8
8
  * Renders a form to edit a given IA review.
9
9
  */
10
10
  export declare class IAReviewForm extends LitElement {
11
11
  identifier?: string;
12
- token: string;
13
- baseHost: string;
14
- endpointPath: string;
15
12
  /** Form submitter's screenname, if applicable */
16
13
  submitterScreenname: string;
17
14
  /** Form submitter's itemname, if applicable */
@@ -20,7 +17,7 @@ export declare class IAReviewForm extends LitElement {
20
17
  unrecoverableError?: string;
21
18
  maxSubjectLength?: number;
22
19
  maxBodyLength?: number;
23
- fetchHandler?: FetchHandlerInterface;
20
+ reviewService?: ReviewServiceInterface;
24
21
  recaptchaManager?: RecaptchaManagerInterface;
25
22
  bypassRecaptcha: boolean;
26
23
  private recaptchaWidget?;
@@ -46,8 +43,6 @@ export declare class IAReviewForm extends LitElement {
46
43
  private get subjectInputTemplate();
47
44
  /** Body input and character counter */
48
45
  private get bodyInputTemplate();
49
- /** Hidden inputs we use to store other information for form submission */
50
- private get hiddenInputsTemplate();
51
46
  /** Buttons to render at bottom of form */
52
47
  private get actionButtonsTemplate();
53
48
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ia-review-form.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAGV,cAAc,EACd,cAAc,EACf,MAAM,KAAK,CAAC;AAQb,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAK3D,OAAO,aAAa,CAAC;AAErB;;GAEG;AACH,qBACa,YAAa,SAAQ,UAAU;IAEd,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,KAAK,EAAE,MAAM,CAAM;IAGnB,QAAQ,EAAE,MAAM,CAAyB;IAGzC,YAAY,EAAE,MAAM,CAAuB;IAEvE,iDAAiD;IACrB,mBAAmB,EAAE,MAAM,CAAe;IAEtE,+CAA+C;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAGrC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAG5C,eAAe,EAAE,OAAO,CAAS;IAG9D,OAAO,CAAC,eAAe,CAAC,CAA2B;IAInD,OAAO,CAAC,YAAY,CAAa;IAIjC,OAAO,CAAC,oBAAoB,CAAa;IAIzC,OAAO,CAAC,iBAAiB,CAAa;IAItC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAI1B,OAAO,CAAC,aAAa,CAAkB;IAIvC,oBAAoB,EAAE,OAAO,CAAS;IAItC,OAAO,CAAC,UAAU,CAAmB;IAGrC,OAAO,CAAC,uBAAuB,CACwB;IAEvD,OAAO,CAAC,qBAAqB,CAC2C;IAExE,MAAM;IAeN,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAiCnD,6CAA6C;IAC7C,OAAO,KAAK,0BAA0B,GAUrC;IAED,4CAA4C;IAC5C,OAAO,KAAK,wBAAwB,GAQnC;IAED,OAAO,KAAK,wBAAwB,GAwBnC;IAED,+BAA+B;IAC/B,OAAO,KAAK,kBAAkB,GAuB7B;IAED,0CAA0C;IAC1C,OAAO,KAAK,oBAAoB,GAqC/B;IAED,uCAAuC;IACvC,OAAO,KAAK,iBAAiB,GAkC5B;IAED,0EAA0E;IAC1E,OAAO,KAAK,oBAAoB,GAW/B;IAED,0CAA0C;IAC1C,OAAO,KAAK,qBAAqB,GAoBhC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IA2BlB;;;;OAIG;YACW,cAAc;IAQ5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAIxB,oEAAoE;YACtD,YAAY;IAmE1B;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAKpB;;;SAGK;YACS,iBAAiB;IAgB/B,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,sBAAsB;IA2B9B,MAAM,KAAK,MAAM,IAAI,cAAc,CAgJlC;CACF"}
1
+ {"version":3,"file":"ia-review-form.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAGV,cAAc,EACd,cAAc,EACf,MAAM,KAAK,CAAC;AAQb,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,mCAAmC,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAK3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,aAAa,CAAC;AAErB;;GAEG;AACH,qBACa,YAAa,SAAQ,UAAU;IAEd,UAAU,CAAC,EAAE,MAAM,CAAC;IAEhD,iDAAiD;IACrB,mBAAmB,EAAE,MAAM,CAAe;IAEtE,+CAA+C;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAGvC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAG5C,eAAe,EAAE,OAAO,CAAS;IAG9D,OAAO,CAAC,eAAe,CAAC,CAA2B;IAInD,OAAO,CAAC,YAAY,CAAa;IAIjC,OAAO,CAAC,oBAAoB,CAAa;IAIzC,OAAO,CAAC,iBAAiB,CAAa;IAItC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAI1B,OAAO,CAAC,aAAa,CAAkB;IAIvC,oBAAoB,EAAE,OAAO,CAAS;IAItC,OAAO,CAAC,UAAU,CAAmB;IAGrC,OAAO,CAAC,uBAAuB,CACwB;IAEvD,OAAO,CAAC,qBAAqB,CAC2C;IAExE,MAAM;IAeN,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAiCnD,6CAA6C;IAC7C,OAAO,KAAK,0BAA0B,GAUrC;IAED,4CAA4C;IAC5C,OAAO,KAAK,wBAAwB,GAQnC;IAED,OAAO,KAAK,wBAAwB,GAwBnC;IAED,+BAA+B;IAC/B,OAAO,KAAK,kBAAkB,GAuB7B;IAED,0CAA0C;IAC1C,OAAO,KAAK,oBAAoB,GAqC/B;IAED,uCAAuC;IACvC,OAAO,KAAK,iBAAiB,GAkC5B;IAED,0CAA0C;IAC1C,OAAO,KAAK,qBAAqB,GAoBhC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IA2BlB;;;;OAIG;YACW,cAAc;IAQ5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAIxB,oEAAoE;YACtD,YAAY;IAuD1B;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAKpB;;;SAGK;YACS,iBAAiB;IAgB/B,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,sBAAsB;IA2B9B,MAAM,KAAK,MAAM,IAAI,cAAc,CAgJlC;CACF"}
@@ -16,12 +16,6 @@ import './ia-review.js';
16
16
  let IAReviewForm = class IAReviewForm extends LitElement {
17
17
  constructor() {
18
18
  super(...arguments);
19
- /* The token for the review edit */
20
- this.token = '';
21
- /* The host for archive endpoints and data */
22
- this.baseHost = 'https://archive.org';
23
- /* The path for the endpoint we're submitting to */
24
- this.endpointPath = '/write-review.php';
25
19
  /** Form submitter's screenname, if applicable */
26
20
  this.submitterScreenname = 'Anonymous';
27
21
  /* Optionally avoid using recaptcha for the form */
@@ -47,7 +41,7 @@ let IAReviewForm = class IAReviewForm extends LitElement {
47
41
  : html `
48
42
  <span class="inputs">
49
43
  ${this.starsInputTemplate} ${this.subjectInputTemplate}
50
- ${this.bodyInputTemplate} ${this.hiddenInputsTemplate}
44
+ ${this.bodyInputTemplate}
51
45
  </span>
52
46
  `}
53
47
  ${this.recaptchaMessageTemplate} ${this.recoverableErrorTemplate}
@@ -214,19 +208,6 @@ let IAReviewForm = class IAReviewForm extends LitElement {
214
208
  </span>
215
209
  `;
216
210
  }
217
- /** Hidden inputs we use to store other information for form submission */
218
- get hiddenInputsTemplate() {
219
- return html `
220
- <input type="hidden" name="field_reviewtoken" .value=${this.token} />
221
- ${this.identifier
222
- ? html `<input
223
- type="hidden"
224
- name="identifier"
225
- .value=${this.identifier}
226
- />`
227
- : nothing}
228
- `;
229
- }
230
211
  /** Buttons to render at bottom of form */
231
212
  get actionButtonsTemplate() {
232
213
  return html `<div class="action-btns">
@@ -316,27 +297,23 @@ let IAReviewForm = class IAReviewForm extends LitElement {
316
297
  if (!this.reviewForm.reportValidity()) {
317
298
  return this.stopSubmission();
318
299
  }
319
- if (!this.fetchHandler) {
300
+ if (!this.reviewService || !this.identifier) {
320
301
  this.recoverableError = this.GENERIC_ERROR_MESSAGE;
321
302
  return this.stopSubmission();
322
303
  }
323
304
  try {
324
- const formData = new URLSearchParams();
305
+ let recaptchaToken;
325
306
  if (!this.bypassRecaptcha) {
326
- const recaptchaToken = await this.getRecaptchaToken();
307
+ recaptchaToken = await this.getRecaptchaToken();
327
308
  if (!recaptchaToken)
328
309
  return this.handleRecaptchaError();
329
- formData.append('g-recaptcha-response', recaptchaToken ?? '');
330
- }
331
- for (const entry of new FormData(this.reviewForm)) {
332
- formData.append(entry[0], entry[1]);
333
310
  }
334
- // Indicates to the backend that submission is intended
335
- formData.append('submitter', 'review-form');
336
- const result = await this.fetchHandler.fetchApiResponse(`${this.baseHost}${this.endpointPath}`, {
337
- method: 'POST',
338
- includeCredentials: true,
339
- body: formData,
311
+ const result = await this.reviewService.submitReview({
312
+ identifier: this.identifier,
313
+ title: this.reviewForm.field_reviewtitle.value,
314
+ body: this.reviewForm.field_reviewbody.value,
315
+ stars: this.reviewForm.field_stars.value,
316
+ recaptchaToken,
340
317
  });
341
318
  if (result?.success === true) {
342
319
  this.submissionInProgress = false;
@@ -614,15 +591,6 @@ let IAReviewForm = class IAReviewForm extends LitElement {
614
591
  __decorate([
615
592
  property({ type: String })
616
593
  ], IAReviewForm.prototype, "identifier", void 0);
617
- __decorate([
618
- property({ type: String })
619
- ], IAReviewForm.prototype, "token", void 0);
620
- __decorate([
621
- property({ type: String })
622
- ], IAReviewForm.prototype, "baseHost", void 0);
623
- __decorate([
624
- property({ type: String })
625
- ], IAReviewForm.prototype, "endpointPath", void 0);
626
594
  __decorate([
627
595
  property({ type: String })
628
596
  ], IAReviewForm.prototype, "submitterScreenname", void 0);
@@ -643,7 +611,7 @@ __decorate([
643
611
  ], IAReviewForm.prototype, "maxBodyLength", void 0);
644
612
  __decorate([
645
613
  property({ type: Object })
646
- ], IAReviewForm.prototype, "fetchHandler", void 0);
614
+ ], IAReviewForm.prototype, "reviewService", void 0);
647
615
  __decorate([
648
616
  property({ type: Object })
649
617
  ], IAReviewForm.prototype, "recaptchaManager", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ia-review-form.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review-form.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n html,\n css,\n LitElement,\n nothing,\n HTMLTemplateResult,\n PropertyValues,\n CSSResultGroup,\n} from 'lit';\nimport { property, state, query } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nimport DOMPurify from 'dompurify';\n\nimport type {\n RecaptchaManagerInterface,\n RecaptchaWidgetInterface,\n} from '@internetarchive/recaptcha-manager';\nimport '@src/elements/ia-button/ia-button';\nimport type { FetchHandlerInterface } from '@internetarchive/fetch-handler';\nimport { Review } from '@internetarchive/metadata-service';\n\nimport starSelected from './assets/star-selected.svg';\nimport starUnselected from './assets/star-unselected.svg';\n\nimport './ia-review';\n\n/**\n * Renders a form to edit a given IA review.\n */\n@customElement('ia-review-form')\nexport class IAReviewForm extends LitElement {\n /* The IA item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /* The token for the review edit */\n @property({ type: String }) token: string = '';\n\n /* The host for archive endpoints and data */\n @property({ type: String }) baseHost: string = 'https://archive.org';\n\n /* The path for the endpoint we're submitting to */\n @property({ type: String }) endpointPath: string = '/write-review.php';\n\n /** Form submitter's screenname, if applicable */\n @property({ type: String }) submitterScreenname: string = 'Anonymous';\n\n /** Form submitter's itemname, if applicable */\n @property({ type: String }) submitterItemname?: string;\n\n /* The previous review to pre-fill, if any */\n @property({ type: Object }) oldReview?: Review;\n\n /* An optional unrecoverable error to pass in instead of rendering the form inputs */\n @property({ type: String }) unrecoverableError?: string;\n\n /* Optional max length for review subject */\n @property({ type: Number }) maxSubjectLength?: number;\n\n /* Optional max length for review body */\n @property({ type: Number }) maxBodyLength?: number;\n\n /* Handler for form submission */\n @property({ type: Object }) fetchHandler?: FetchHandlerInterface;\n\n /* Service for activating the recaptcha challenge */\n @property({ type: Object }) recaptchaManager?: RecaptchaManagerInterface;\n\n /* Optionally avoid using recaptcha for the form */\n @property({ type: Boolean }) bypassRecaptcha: boolean = false;\n\n /* Recaptcha widget for the form */\n private recaptchaWidget?: RecaptchaWidgetInterface;\n\n /* Number of stars currently selected */\n @state()\n private currentStars: number = 0;\n\n /* Length of review subject currently filled in */\n @state()\n private currentSubjectLength: number = 0;\n\n /* Length of review body currently filled in */\n @state()\n private currentBodyLength: number = 0;\n\n /* Any recoverable error to display for the form. Will still allow submission. */\n @state()\n recoverableError?: string;\n\n /* Whether to enable the submit button */\n @state()\n private formCanSubmit: boolean = false;\n\n /* Whether to show a loading indicator for the form */\n @state()\n submissionInProgress: boolean = false;\n\n /* The form to be submitted */\n @query('#review-form')\n private reviewForm!: HTMLFormElement;\n\n /* Error to show if recaptcha cannot be loaded */\n private RECAPTCHA_ERROR_MESSAGE =\n 'Could not validate review. Please try again later.';\n\n private GENERIC_ERROR_MESSAGE =\n \"There's been a temporary error. Please wait a moment and try again.\";\n\n render() {\n return html`<form id=\"review-form\" @submit=${this.handleSubmit}>\n ${this.unrecoverableError\n ? this.unrecoverableErrorTemplate\n : html`\n <span class=\"inputs\">\n ${this.starsInputTemplate} ${this.subjectInputTemplate}\n ${this.bodyInputTemplate} ${this.hiddenInputsTemplate}\n </span>\n `}\n ${this.recaptchaMessageTemplate} ${this.recoverableErrorTemplate}\n ${this.actionButtonsTemplate}\n </form>`;\n }\n\n protected willUpdate(changed: PropertyValues): void {\n // Fill in previous review, if provided\n if (changed.has('oldReview')) {\n this.currentStars = this.oldReview?.stars ?? 0;\n this.currentSubjectLength = this.oldReview?.reviewtitle?.length ?? 0;\n this.currentBodyLength = this.oldReview?.reviewbody?.length ?? 0;\n }\n\n // Set up ReCaptcha, if possible\n if (\n changed.has('recaptchaManager') &&\n !this.bypassRecaptcha &&\n this.recaptchaManager\n ) {\n this.setupRecaptcha();\n }\n\n // Disable form if unrecoverable error added\n if (changed.has('unrecoverableError')) {\n this.formCanSubmit = this.checkSubmissionAllowed();\n }\n\n // Ensure new subject and body have correct length\n if (\n changed.has('currentSubjectLength') ||\n changed.has('currentBodyLength') ||\n changed.has('maxSubjectLength') ||\n changed.has('maxBodyLength')\n ) {\n this.formCanSubmit = this.checkSubmissionAllowed();\n }\n }\n\n /** Error to render instead of form inputs */\n private get unrecoverableErrorTemplate():\n | HTMLTemplateResult\n | typeof nothing {\n return this.unrecoverableError\n ? html`\n <div class=\"unrecoverable-error\">\n <span class=\"error-msg\">${msg(this.unrecoverableError)}</span>\n </div>\n `\n : nothing;\n }\n\n /** Error to render next to submit button */\n private get recoverableErrorTemplate(): HTMLTemplateResult | typeof nothing {\n return this.recoverableError\n ? html`\n <div class=\"recoverable-error\">\n ${unsafeHTML(this.sanitizeErrorMsg(msg(this.recoverableError)))}\n </div>\n `\n : nothing;\n }\n\n private get recaptchaMessageTemplate(): HTMLTemplateResult | typeof nothing {\n if (this.bypassRecaptcha) return nothing;\n\n return html`\n <span class=\"recaptcha-disclaimer\"\n >${msg(\n html`This site is protected by reCAPTCHA and the Google\n <a\n target=\"_blank\"\n class=\"inline-link\"\n href=\"https://policies.google.com/privacy\"\n >Privacy Policy</a\n >\n and\n <a\n target=\"_blank\"\n class=\"inline-link\"\n href=\"https://policies.google.com/terms\"\n >Terms of Service</a\n >\n apply.`,\n )}</span\n >\n `;\n }\n\n /** Clickable group of stars */\n private get starsInputTemplate(): HTMLTemplateResult {\n return html`\n <div class=\"form-heading rating\">\n <label for=\"stars-field\">${msg('Rating (optional)')}</label>\n </div>\n <input\n type=\"hidden\"\n name=\"field_stars\"\n id=\"stars-input\"\n .value=${this.currentStars.toString()}\n required\n />\n <div class=\"stars\">\n ${[1, 2, 3, 4, 5].map((num) => this.renderStar(num))}\n <button\n type=\"button\"\n class=\"clear-stars-btn\"\n @click=${this.handleClearBtnClicked}\n >\n ${msg('Clear')}\n </button>\n </div>\n `;\n }\n\n /** Subject input and character counter */\n private get subjectInputTemplate(): HTMLTemplateResult {\n return html`\n <span id=\"subject-input\" class=\"input-box ${\n this.maxSubjectLength &&\n this.currentSubjectLength > this.maxSubjectLength\n ? 'error'\n : ''\n }\"\n ><div class=\"form-heading\">\n <label for=\"field_reviewtitle\">${msg('Subject')}</label>\n ${\n this.maxSubjectLength\n ? html`<div class=\"char-count subject\">\n ${this.currentSubjectLength}/${this.maxSubjectLength}\n </div>`\n : nothing\n }\n </div>\n <input\n type=\"text\"\n name=\"field_reviewtitle\"\n id=\"field_reviewtitle\"\n .value=${this.oldReview?.reviewtitle ?? ''}\n @input=${this.handleSubjectChanged}\n required\n />${\n this.maxSubjectLength\n ? html`\n <div class=\"input-error\">\n ${msg(\n `Subject may only have ${this.maxSubjectLength} characters`,\n )}\n </div>\n `\n : nothing\n }</div></span>\n `;\n }\n\n /** Body input and character counter */\n private get bodyInputTemplate(): HTMLTemplateResult {\n return html`\n <span\n id=\"body-input\"\n class=\"input-box ${this.maxBodyLength &&\n this.currentBodyLength > this.maxBodyLength\n ? 'error'\n : ''}\"\n ><div class=\"form-heading\">\n <label for=\"field_reviewbody\">${msg('Review')}</label>\n ${this.maxBodyLength\n ? html`<div class=\"char-count body\">\n ${this.currentBodyLength}/${this.maxBodyLength}\n </div>`\n : nothing}\n </div>\n <textarea\n name=\"field_reviewbody\"\n id=\"field_reviewbody\"\n .value=${this.oldReview?.reviewbody ?? ''}\n rows=\"10\"\n cols=\"50\"\n required\n @input=${this.handleBodyChanged}\n ></textarea>\n ${this.maxBodyLength\n ? html`\n <div class=\"input-error\">\n ${msg(`Review may only have ${this.maxBodyLength} characters`)}\n </div>\n `\n : nothing}\n </span>\n `;\n }\n\n /** Hidden inputs we use to store other information for form submission */\n private get hiddenInputsTemplate(): HTMLTemplateResult {\n return html`\n <input type=\"hidden\" name=\"field_reviewtoken\" .value=${this.token} />\n ${this.identifier\n ? html`<input\n type=\"hidden\"\n name=\"identifier\"\n .value=${this.identifier}\n />`\n : nothing}\n `;\n }\n\n /** Buttons to render at bottom of form */\n private get actionButtonsTemplate(): HTMLTemplateResult {\n return html`<div class=\"action-btns\">\n <ia-button\n mode=\"secondary\"\n class=\"cancel-btn\"\n data-testid=\"cancel-btn\"\n @click=${this.cancelReviewEdit}\n >\n ${msg('Cancel')}\n </ia-button>\n <ia-button\n mode=\"primary\"\n type=\"submit\"\n class=\"submit-btn\"\n .disabled=${!this.formCanSubmit}\n .loading=${this.submissionInProgress}\n >\n ${msg('Submit review')}\n </ia-button>\n </div>`;\n }\n\n /**\n * Renders a star icon, using its number and the current star rating\n * to determine whether it should appear selected or not\n *\n * @param {number} num The number of the current star\n * @returns {HTMLTemplateResult} The correct icon, rendered within a button\n */\n private renderStar(num: number): HTMLTemplateResult {\n const isSelected = num === this.currentStars;\n const ratingLabel = msg(`Rate ${num > 1 ? `${num} stars` : '1 star'}`);\n\n return html`\n <button\n class=\"star star-${num}\"\n title=${isSelected ? msg('Clear rating') : ratingLabel}\n @click=${(e: Event) => this.handleStarClicked(e, num)}\n >\n ${num <= this.currentStars\n ? html`<img\n class=\"star-selected\"\n src=${starSelected}\n alt=\"\"\n aria-hidden=\"true\"\n />`\n : html`<img\n class=\"star-unselected\"\n src=${starUnselected}\n alt=\"\"\n aria-hidden=\"true\"\n />`}\n </button>\n `;\n }\n\n /**\n * Sets up ReCaptcha for the form, if possible.\n *\n * On failure, replaces form inputs with the ReCaptcha error message.\n */\n private async setupRecaptcha(): Promise<void> {\n try {\n this.recaptchaWidget = await this.recaptchaManager?.getRecaptchaWidget();\n } catch {\n this.unrecoverableError = this.RECAPTCHA_ERROR_MESSAGE;\n }\n }\n\n /**\n * Uses DOMpurify to sanitize any error message before render.\n *\n * @param {string} errorMsg The error message to be sanitized\n * @returns {string} The sanitized error message\n */\n private sanitizeErrorMsg(errorMsg: string): string {\n return DOMPurify.sanitize(errorMsg, { ALLOWED_TAGS: ['a', 'b', 'br'] });\n }\n\n /** Handles validation and recaptcha execution on form submission */\n private async handleSubmit(e: Event): Promise<void> {\n e.preventDefault();\n\n // Prevent disallowed or double submission\n if (!this.formCanSubmit || this.submissionInProgress) return;\n\n // Set loading behavior\n this.submissionInProgress = true;\n this.recoverableError = '';\n\n // Check for HTML errors\n if (!this.reviewForm.reportValidity()) {\n return this.stopSubmission();\n }\n\n if (!this.fetchHandler) {\n this.recoverableError = this.GENERIC_ERROR_MESSAGE;\n return this.stopSubmission();\n }\n\n try {\n const formData = new URLSearchParams();\n\n if (!this.bypassRecaptcha) {\n const recaptchaToken = await this.getRecaptchaToken();\n if (!recaptchaToken) return this.handleRecaptchaError();\n\n formData.append('g-recaptcha-response', recaptchaToken ?? '');\n }\n\n for (const entry of new FormData(this.reviewForm)) {\n formData.append(entry[0], entry[1] as string);\n }\n\n // Indicates to the backend that submission is intended\n formData.append('submitter', 'review-form');\n\n const result: { success: boolean; error?: string } =\n await this.fetchHandler.fetchApiResponse(\n `${this.baseHost}${this.endpointPath}`,\n {\n method: 'POST',\n includeCredentials: true,\n body: formData,\n },\n );\n\n if (result?.success === true) {\n this.submissionInProgress = false;\n const newReview = this.generateSubmittedReview();\n\n // Emit a reviewUpdated event\n const event = new CustomEvent<Review>('reviewUpdated', {\n detail: newReview,\n });\n this.dispatchEvent(event);\n } else {\n this.recoverableError = result.error ?? this.GENERIC_ERROR_MESSAGE;\n this.stopSubmission();\n }\n } catch (e) {\n console.error(e);\n this.recoverableError = this.GENERIC_ERROR_MESSAGE;\n this.stopSubmission();\n }\n }\n\n /**\n * Generates a review object from the submitted review,\n * for optimistic display on success.\n *\n * @returns {Review} The review to display\n */\n private generateSubmittedReview(): Review {\n const today = new Date().toDateString();\n\n const newReview = new Review({\n reviewtitle: this.reviewForm.field_reviewtitle.value,\n reviewbody: this.reviewForm.field_reviewbody.value,\n stars: this.reviewForm.field_stars.value,\n reviewdate: today,\n reviewer: this.oldReview?.reviewer ?? this.submitterScreenname,\n reviewer_itemname:\n this.oldReview?.reviewer_itemname ?? this.submitterItemname,\n createdate: this.dateToString(this.oldReview?.createdate) ?? today,\n });\n\n return newReview;\n }\n\n /**\n * Converts date to string if it is a Date, otherwise\n * leaves unchanged.\n */\n private dateToString(date: string | Date | undefined) {\n if (date instanceof Date) return date.toDateString();\n return date;\n }\n\n /**\n * Executes the ReCaptcha challenge for the form.\n * Attempts form submission on success.\n * */\n private async getRecaptchaToken(): Promise<string | undefined> {\n if (!this.recaptchaWidget) {\n this.handleRecaptchaError();\n return;\n }\n\n try {\n const recaptchaToken = await this.recaptchaWidget.execute();\n return recaptchaToken;\n } catch {\n this.handleRecaptchaError();\n return;\n }\n }\n\n /* Handles a recaptcha error during submission */\n private handleRecaptchaError(): void {\n this.recoverableError = this.RECAPTCHA_ERROR_MESSAGE;\n this.stopSubmission();\n }\n\n /* Handles canceled form submission */\n private stopSubmission(): void {\n if (this.submissionInProgress) {\n this.submissionInProgress = false;\n }\n }\n\n /* Emits an event to close the form */\n private cancelReviewEdit(): void {\n const event = new CustomEvent('reviewEditCanceled');\n this.dispatchEvent(event);\n }\n\n /* Prevents form submission and sets stars based on number clicked */\n private handleStarClicked(e: Event, num: number): void {\n e.preventDefault();\n this.setStars(num);\n }\n\n /* Clear stars if requested */\n private handleClearBtnClicked(e: Event): void {\n e.preventDefault();\n this.currentStars = 0;\n }\n\n /* Updates star count, resetting to 0 if the currently selected star is clicked */\n private setStars(num: number): void {\n this.currentStars = num === this.currentStars ? 0 : num;\n }\n\n /* Updates subject input length */\n private handleSubjectChanged(e: Event): void {\n const subjectInput = e.target as HTMLInputElement;\n this.currentSubjectLength = subjectInput.value.length;\n }\n\n /* Updates body input length */\n private handleBodyChanged(e: Event): void {\n const bodyInput = e.target as HTMLInputElement;\n this.currentBodyLength = bodyInput.value.length;\n }\n\n /* Checks if submission should be allowed */\n private checkSubmissionAllowed(): boolean {\n // Form must not have an unrecoverable error\n if (this.unrecoverableError) {\n return false;\n }\n\n // Subject and body must not be empty\n if (!this.currentBodyLength || !this.currentSubjectLength) {\n return false;\n }\n\n // Subject must be correct length\n if (\n !!this.maxSubjectLength &&\n this.currentSubjectLength > this.maxSubjectLength\n ) {\n return false;\n }\n\n // Body must be correct length\n if (!!this.maxBodyLength && this.currentBodyLength > this.maxBodyLength) {\n return false;\n }\n\n return true;\n }\n\n static get styles(): CSSResultGroup {\n return [\n css`\n .form-heading {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding-top: 15px;\n }\n\n .form-heading.rating {\n padding-top: 0;\n }\n\n .form-heading label {\n font-size: 1.6rem;\n font-weight: bold;\n }\n\n label {\n display: inline-block;\n margin-bottom: 5px;\n }\n\n textarea,\n input[type='text'],\n .unrecoverable-error {\n padding: 5px;\n width: calc(100% - 10px);\n font-family: inherit;\n border-radius: 5px;\n border: 1px solid var(--container-border-color, #999999);\n }\n\n .input-box.error input,\n .input-box.error textarea {\n border: 2px solid var(--container-error-color, #ea0202);\n }\n\n .input-box.error .char-count,\n .input-error,\n .unrecoverable-error,\n .recoverable-error {\n color: var(--container-error-color, #ea0202);\n }\n\n .input-error {\n display: none;\n }\n\n .input-box.error .input-error {\n display: block;\n text-align: right;\n padding-top: 5px;\n }\n\n .inline-link {\n color: var(--container-link-color, #4f65f5);\n text-decoration: none;\n }\n\n .inline-link:hover {\n text-decoration: underline;\n }\n\n .stars {\n display: flex;\n flex-direction: row;\n gap: 2px;\n align-items: center;\n }\n\n .star {\n all: unset;\n height: 30px;\n width: 30px;\n }\n\n .star-selected,\n .star-unselected {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .star:hover {\n cursor: pointer;\n }\n\n .clear-stars-btn,\n .recoverable-error a {\n padding: 0 5px;\n color: var(--container-link-color, #4f65f5);\n font-family: inherit;\n border: none;\n background: transparent;\n display: inline-block;\n padding-top: 5px;\n }\n\n .clear-stars-btn:hover,\n .recoverable-error a:hover {\n cursor: pointer;\n text-decoration: underline;\n }\n\n .action-btns {\n width: 100%;\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 15px;\n }\n\n .ia-button:disabled {\n opacity: 0.75;\n }\n\n .ia-button:disabled:hover {\n cursor: not-allowed;\n }\n\n .unrecoverable-error {\n min-height: 50px;\n padding: 5px;\n text-align: center;\n display: flex;\n flex-direction: column;\n justify-content: center;\n background-color: var(--container-bg-color, #fbfbfd);\n }\n\n .recaptcha-disclaimer {\n font-size: 1.2rem;\n }\n\n @media only screen and (max-width: 350px) {\n .action-btns {\n flex-direction: column-reverse;\n align-items: center;\n }\n }\n `,\n ];\n }\n}\n"]}
1
+ {"version":3,"file":"ia-review-form.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review-form.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n html,\n css,\n LitElement,\n nothing,\n HTMLTemplateResult,\n PropertyValues,\n CSSResultGroup,\n} from 'lit';\nimport { property, state, query } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nimport DOMPurify from 'dompurify';\n\nimport type {\n RecaptchaManagerInterface,\n RecaptchaWidgetInterface,\n} from '@internetarchive/recaptcha-manager';\nimport '@src/elements/ia-button/ia-button';\nimport { Review } from '@internetarchive/metadata-service';\n\nimport starSelected from './assets/star-selected.svg';\nimport starUnselected from './assets/star-unselected.svg';\n\nimport type { ReviewServiceInterface } from './models';\nimport './ia-review';\n\n/**\n * Renders a form to edit a given IA review.\n */\n@customElement('ia-review-form')\nexport class IAReviewForm extends LitElement {\n /* The IA item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /** Form submitter's screenname, if applicable */\n @property({ type: String }) submitterScreenname: string = 'Anonymous';\n\n /** Form submitter's itemname, if applicable */\n @property({ type: String }) submitterItemname?: string;\n\n /* The previous review to pre-fill, if any */\n @property({ type: Object }) oldReview?: Review;\n\n /* An optional unrecoverable error to pass in instead of rendering the form inputs */\n @property({ type: String }) unrecoverableError?: string;\n\n /* Optional max length for review subject */\n @property({ type: Number }) maxSubjectLength?: number;\n\n /* Optional max length for review body */\n @property({ type: Number }) maxBodyLength?: number;\n\n /* Handles the review submission request */\n @property({ type: Object }) reviewService?: ReviewServiceInterface;\n\n /* Service for activating the recaptcha challenge */\n @property({ type: Object }) recaptchaManager?: RecaptchaManagerInterface;\n\n /* Optionally avoid using recaptcha for the form */\n @property({ type: Boolean }) bypassRecaptcha: boolean = false;\n\n /* Recaptcha widget for the form */\n private recaptchaWidget?: RecaptchaWidgetInterface;\n\n /* Number of stars currently selected */\n @state()\n private currentStars: number = 0;\n\n /* Length of review subject currently filled in */\n @state()\n private currentSubjectLength: number = 0;\n\n /* Length of review body currently filled in */\n @state()\n private currentBodyLength: number = 0;\n\n /* Any recoverable error to display for the form. Will still allow submission. */\n @state()\n recoverableError?: string;\n\n /* Whether to enable the submit button */\n @state()\n private formCanSubmit: boolean = false;\n\n /* Whether to show a loading indicator for the form */\n @state()\n submissionInProgress: boolean = false;\n\n /* The form to be submitted */\n @query('#review-form')\n private reviewForm!: HTMLFormElement;\n\n /* Error to show if recaptcha cannot be loaded */\n private RECAPTCHA_ERROR_MESSAGE =\n 'Could not validate review. Please try again later.';\n\n private GENERIC_ERROR_MESSAGE =\n \"There's been a temporary error. Please wait a moment and try again.\";\n\n render() {\n return html`<form id=\"review-form\" @submit=${this.handleSubmit}>\n ${this.unrecoverableError\n ? this.unrecoverableErrorTemplate\n : html`\n <span class=\"inputs\">\n ${this.starsInputTemplate} ${this.subjectInputTemplate}\n ${this.bodyInputTemplate}\n </span>\n `}\n ${this.recaptchaMessageTemplate} ${this.recoverableErrorTemplate}\n ${this.actionButtonsTemplate}\n </form>`;\n }\n\n protected willUpdate(changed: PropertyValues): void {\n // Fill in previous review, if provided\n if (changed.has('oldReview')) {\n this.currentStars = this.oldReview?.stars ?? 0;\n this.currentSubjectLength = this.oldReview?.reviewtitle?.length ?? 0;\n this.currentBodyLength = this.oldReview?.reviewbody?.length ?? 0;\n }\n\n // Set up ReCaptcha, if possible\n if (\n changed.has('recaptchaManager') &&\n !this.bypassRecaptcha &&\n this.recaptchaManager\n ) {\n this.setupRecaptcha();\n }\n\n // Disable form if unrecoverable error added\n if (changed.has('unrecoverableError')) {\n this.formCanSubmit = this.checkSubmissionAllowed();\n }\n\n // Ensure new subject and body have correct length\n if (\n changed.has('currentSubjectLength') ||\n changed.has('currentBodyLength') ||\n changed.has('maxSubjectLength') ||\n changed.has('maxBodyLength')\n ) {\n this.formCanSubmit = this.checkSubmissionAllowed();\n }\n }\n\n /** Error to render instead of form inputs */\n private get unrecoverableErrorTemplate():\n | HTMLTemplateResult\n | typeof nothing {\n return this.unrecoverableError\n ? html`\n <div class=\"unrecoverable-error\">\n <span class=\"error-msg\">${msg(this.unrecoverableError)}</span>\n </div>\n `\n : nothing;\n }\n\n /** Error to render next to submit button */\n private get recoverableErrorTemplate(): HTMLTemplateResult | typeof nothing {\n return this.recoverableError\n ? html`\n <div class=\"recoverable-error\">\n ${unsafeHTML(this.sanitizeErrorMsg(msg(this.recoverableError)))}\n </div>\n `\n : nothing;\n }\n\n private get recaptchaMessageTemplate(): HTMLTemplateResult | typeof nothing {\n if (this.bypassRecaptcha) return nothing;\n\n return html`\n <span class=\"recaptcha-disclaimer\"\n >${msg(\n html`This site is protected by reCAPTCHA and the Google\n <a\n target=\"_blank\"\n class=\"inline-link\"\n href=\"https://policies.google.com/privacy\"\n >Privacy Policy</a\n >\n and\n <a\n target=\"_blank\"\n class=\"inline-link\"\n href=\"https://policies.google.com/terms\"\n >Terms of Service</a\n >\n apply.`,\n )}</span\n >\n `;\n }\n\n /** Clickable group of stars */\n private get starsInputTemplate(): HTMLTemplateResult {\n return html`\n <div class=\"form-heading rating\">\n <label for=\"stars-field\">${msg('Rating (optional)')}</label>\n </div>\n <input\n type=\"hidden\"\n name=\"field_stars\"\n id=\"stars-input\"\n .value=${this.currentStars.toString()}\n required\n />\n <div class=\"stars\">\n ${[1, 2, 3, 4, 5].map((num) => this.renderStar(num))}\n <button\n type=\"button\"\n class=\"clear-stars-btn\"\n @click=${this.handleClearBtnClicked}\n >\n ${msg('Clear')}\n </button>\n </div>\n `;\n }\n\n /** Subject input and character counter */\n private get subjectInputTemplate(): HTMLTemplateResult {\n return html`\n <span id=\"subject-input\" class=\"input-box ${\n this.maxSubjectLength &&\n this.currentSubjectLength > this.maxSubjectLength\n ? 'error'\n : ''\n }\"\n ><div class=\"form-heading\">\n <label for=\"field_reviewtitle\">${msg('Subject')}</label>\n ${\n this.maxSubjectLength\n ? html`<div class=\"char-count subject\">\n ${this.currentSubjectLength}/${this.maxSubjectLength}\n </div>`\n : nothing\n }\n </div>\n <input\n type=\"text\"\n name=\"field_reviewtitle\"\n id=\"field_reviewtitle\"\n .value=${this.oldReview?.reviewtitle ?? ''}\n @input=${this.handleSubjectChanged}\n required\n />${\n this.maxSubjectLength\n ? html`\n <div class=\"input-error\">\n ${msg(\n `Subject may only have ${this.maxSubjectLength} characters`,\n )}\n </div>\n `\n : nothing\n }</div></span>\n `;\n }\n\n /** Body input and character counter */\n private get bodyInputTemplate(): HTMLTemplateResult {\n return html`\n <span\n id=\"body-input\"\n class=\"input-box ${this.maxBodyLength &&\n this.currentBodyLength > this.maxBodyLength\n ? 'error'\n : ''}\"\n ><div class=\"form-heading\">\n <label for=\"field_reviewbody\">${msg('Review')}</label>\n ${this.maxBodyLength\n ? html`<div class=\"char-count body\">\n ${this.currentBodyLength}/${this.maxBodyLength}\n </div>`\n : nothing}\n </div>\n <textarea\n name=\"field_reviewbody\"\n id=\"field_reviewbody\"\n .value=${this.oldReview?.reviewbody ?? ''}\n rows=\"10\"\n cols=\"50\"\n required\n @input=${this.handleBodyChanged}\n ></textarea>\n ${this.maxBodyLength\n ? html`\n <div class=\"input-error\">\n ${msg(`Review may only have ${this.maxBodyLength} characters`)}\n </div>\n `\n : nothing}\n </span>\n `;\n }\n\n /** Buttons to render at bottom of form */\n private get actionButtonsTemplate(): HTMLTemplateResult {\n return html`<div class=\"action-btns\">\n <ia-button\n mode=\"secondary\"\n class=\"cancel-btn\"\n data-testid=\"cancel-btn\"\n @click=${this.cancelReviewEdit}\n >\n ${msg('Cancel')}\n </ia-button>\n <ia-button\n mode=\"primary\"\n type=\"submit\"\n class=\"submit-btn\"\n .disabled=${!this.formCanSubmit}\n .loading=${this.submissionInProgress}\n >\n ${msg('Submit review')}\n </ia-button>\n </div>`;\n }\n\n /**\n * Renders a star icon, using its number and the current star rating\n * to determine whether it should appear selected or not\n *\n * @param {number} num The number of the current star\n * @returns {HTMLTemplateResult} The correct icon, rendered within a button\n */\n private renderStar(num: number): HTMLTemplateResult {\n const isSelected = num === this.currentStars;\n const ratingLabel = msg(`Rate ${num > 1 ? `${num} stars` : '1 star'}`);\n\n return html`\n <button\n class=\"star star-${num}\"\n title=${isSelected ? msg('Clear rating') : ratingLabel}\n @click=${(e: Event) => this.handleStarClicked(e, num)}\n >\n ${num <= this.currentStars\n ? html`<img\n class=\"star-selected\"\n src=${starSelected}\n alt=\"\"\n aria-hidden=\"true\"\n />`\n : html`<img\n class=\"star-unselected\"\n src=${starUnselected}\n alt=\"\"\n aria-hidden=\"true\"\n />`}\n </button>\n `;\n }\n\n /**\n * Sets up ReCaptcha for the form, if possible.\n *\n * On failure, replaces form inputs with the ReCaptcha error message.\n */\n private async setupRecaptcha(): Promise<void> {\n try {\n this.recaptchaWidget = await this.recaptchaManager?.getRecaptchaWidget();\n } catch {\n this.unrecoverableError = this.RECAPTCHA_ERROR_MESSAGE;\n }\n }\n\n /**\n * Uses DOMpurify to sanitize any error message before render.\n *\n * @param {string} errorMsg The error message to be sanitized\n * @returns {string} The sanitized error message\n */\n private sanitizeErrorMsg(errorMsg: string): string {\n return DOMPurify.sanitize(errorMsg, { ALLOWED_TAGS: ['a', 'b', 'br'] });\n }\n\n /** Handles validation and recaptcha execution on form submission */\n private async handleSubmit(e: Event): Promise<void> {\n e.preventDefault();\n\n // Prevent disallowed or double submission\n if (!this.formCanSubmit || this.submissionInProgress) return;\n\n // Set loading behavior\n this.submissionInProgress = true;\n this.recoverableError = '';\n\n // Check for HTML errors\n if (!this.reviewForm.reportValidity()) {\n return this.stopSubmission();\n }\n\n if (!this.reviewService || !this.identifier) {\n this.recoverableError = this.GENERIC_ERROR_MESSAGE;\n return this.stopSubmission();\n }\n\n try {\n let recaptchaToken: string | undefined;\n if (!this.bypassRecaptcha) {\n recaptchaToken = await this.getRecaptchaToken();\n if (!recaptchaToken) return this.handleRecaptchaError();\n }\n\n const result = await this.reviewService.submitReview({\n identifier: this.identifier,\n title: this.reviewForm.field_reviewtitle.value,\n body: this.reviewForm.field_reviewbody.value,\n stars: this.reviewForm.field_stars.value,\n recaptchaToken,\n });\n\n if (result?.success === true) {\n this.submissionInProgress = false;\n const newReview = this.generateSubmittedReview();\n\n // Emit a reviewUpdated event\n const event = new CustomEvent<Review>('reviewUpdated', {\n detail: newReview,\n });\n this.dispatchEvent(event);\n } else {\n this.recoverableError = result.error ?? this.GENERIC_ERROR_MESSAGE;\n this.stopSubmission();\n }\n } catch (e) {\n console.error(e);\n this.recoverableError = this.GENERIC_ERROR_MESSAGE;\n this.stopSubmission();\n }\n }\n\n /**\n * Generates a review object from the submitted review,\n * for optimistic display on success.\n *\n * @returns {Review} The review to display\n */\n private generateSubmittedReview(): Review {\n const today = new Date().toDateString();\n\n const newReview = new Review({\n reviewtitle: this.reviewForm.field_reviewtitle.value,\n reviewbody: this.reviewForm.field_reviewbody.value,\n stars: this.reviewForm.field_stars.value,\n reviewdate: today,\n reviewer: this.oldReview?.reviewer ?? this.submitterScreenname,\n reviewer_itemname:\n this.oldReview?.reviewer_itemname ?? this.submitterItemname,\n createdate: this.dateToString(this.oldReview?.createdate) ?? today,\n });\n\n return newReview;\n }\n\n /**\n * Converts date to string if it is a Date, otherwise\n * leaves unchanged.\n */\n private dateToString(date: string | Date | undefined) {\n if (date instanceof Date) return date.toDateString();\n return date;\n }\n\n /**\n * Executes the ReCaptcha challenge for the form.\n * Attempts form submission on success.\n * */\n private async getRecaptchaToken(): Promise<string | undefined> {\n if (!this.recaptchaWidget) {\n this.handleRecaptchaError();\n return;\n }\n\n try {\n const recaptchaToken = await this.recaptchaWidget.execute();\n return recaptchaToken;\n } catch {\n this.handleRecaptchaError();\n return;\n }\n }\n\n /* Handles a recaptcha error during submission */\n private handleRecaptchaError(): void {\n this.recoverableError = this.RECAPTCHA_ERROR_MESSAGE;\n this.stopSubmission();\n }\n\n /* Handles canceled form submission */\n private stopSubmission(): void {\n if (this.submissionInProgress) {\n this.submissionInProgress = false;\n }\n }\n\n /* Emits an event to close the form */\n private cancelReviewEdit(): void {\n const event = new CustomEvent('reviewEditCanceled');\n this.dispatchEvent(event);\n }\n\n /* Prevents form submission and sets stars based on number clicked */\n private handleStarClicked(e: Event, num: number): void {\n e.preventDefault();\n this.setStars(num);\n }\n\n /* Clear stars if requested */\n private handleClearBtnClicked(e: Event): void {\n e.preventDefault();\n this.currentStars = 0;\n }\n\n /* Updates star count, resetting to 0 if the currently selected star is clicked */\n private setStars(num: number): void {\n this.currentStars = num === this.currentStars ? 0 : num;\n }\n\n /* Updates subject input length */\n private handleSubjectChanged(e: Event): void {\n const subjectInput = e.target as HTMLInputElement;\n this.currentSubjectLength = subjectInput.value.length;\n }\n\n /* Updates body input length */\n private handleBodyChanged(e: Event): void {\n const bodyInput = e.target as HTMLInputElement;\n this.currentBodyLength = bodyInput.value.length;\n }\n\n /* Checks if submission should be allowed */\n private checkSubmissionAllowed(): boolean {\n // Form must not have an unrecoverable error\n if (this.unrecoverableError) {\n return false;\n }\n\n // Subject and body must not be empty\n if (!this.currentBodyLength || !this.currentSubjectLength) {\n return false;\n }\n\n // Subject must be correct length\n if (\n !!this.maxSubjectLength &&\n this.currentSubjectLength > this.maxSubjectLength\n ) {\n return false;\n }\n\n // Body must be correct length\n if (!!this.maxBodyLength && this.currentBodyLength > this.maxBodyLength) {\n return false;\n }\n\n return true;\n }\n\n static get styles(): CSSResultGroup {\n return [\n css`\n .form-heading {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n padding-top: 15px;\n }\n\n .form-heading.rating {\n padding-top: 0;\n }\n\n .form-heading label {\n font-size: 1.6rem;\n font-weight: bold;\n }\n\n label {\n display: inline-block;\n margin-bottom: 5px;\n }\n\n textarea,\n input[type='text'],\n .unrecoverable-error {\n padding: 5px;\n width: calc(100% - 10px);\n font-family: inherit;\n border-radius: 5px;\n border: 1px solid var(--container-border-color, #999999);\n }\n\n .input-box.error input,\n .input-box.error textarea {\n border: 2px solid var(--container-error-color, #ea0202);\n }\n\n .input-box.error .char-count,\n .input-error,\n .unrecoverable-error,\n .recoverable-error {\n color: var(--container-error-color, #ea0202);\n }\n\n .input-error {\n display: none;\n }\n\n .input-box.error .input-error {\n display: block;\n text-align: right;\n padding-top: 5px;\n }\n\n .inline-link {\n color: var(--container-link-color, #4f65f5);\n text-decoration: none;\n }\n\n .inline-link:hover {\n text-decoration: underline;\n }\n\n .stars {\n display: flex;\n flex-direction: row;\n gap: 2px;\n align-items: center;\n }\n\n .star {\n all: unset;\n height: 30px;\n width: 30px;\n }\n\n .star-selected,\n .star-unselected {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .star:hover {\n cursor: pointer;\n }\n\n .clear-stars-btn,\n .recoverable-error a {\n padding: 0 5px;\n color: var(--container-link-color, #4f65f5);\n font-family: inherit;\n border: none;\n background: transparent;\n display: inline-block;\n padding-top: 5px;\n }\n\n .clear-stars-btn:hover,\n .recoverable-error a:hover {\n cursor: pointer;\n text-decoration: underline;\n }\n\n .action-btns {\n width: 100%;\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 15px;\n }\n\n .ia-button:disabled {\n opacity: 0.75;\n }\n\n .ia-button:disabled:hover {\n cursor: not-allowed;\n }\n\n .unrecoverable-error {\n min-height: 50px;\n padding: 5px;\n text-align: center;\n display: flex;\n flex-direction: column;\n justify-content: center;\n background-color: var(--container-bg-color, #fbfbfd);\n }\n\n .recaptcha-disclaimer {\n font-size: 1.2rem;\n }\n\n @media only screen and (max-width: 350px) {\n .action-btns {\n flex-direction: column-reverse;\n align-items: center;\n }\n }\n `,\n ];\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { LitElement, CSSResultGroup } from 'lit';
2
2
  import { Review } from '@internetarchive/metadata-service';
3
+ import type { ReviewServiceInterface } from './models.js';
3
4
  /**
4
5
  * Renders a single IA review
5
6
  */
@@ -9,7 +10,7 @@ export declare class IAReview extends LitElement {
9
10
  maxSubjectLength: number;
10
11
  maxBodyLength: number;
11
12
  baseHost: string;
12
- csrfToken: string;
13
+ reviewService?: ReviewServiceInterface;
13
14
  canDelete: boolean;
14
15
  bypassTruncation: boolean;
15
16
  private showTruncatedContent;
@@ -1 +1 @@
1
- {"version":3,"file":"ia-review.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAGV,cAAc,EACf,MAAM,KAAK,CAAC;AAMb,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAW3D;;GAEG;AACH,qBACa,QAAS,SAAQ,UAAU;IAEV,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,gBAAgB,SAAO;IAGvB,aAAa,SAAO;IAGpB,QAAQ,SAAyB;IAGjC,SAAS,EAAE,MAAM,CAAM;IAGtB,SAAS,UAAS;IAGlB,gBAAgB,UAAS;IAItD,OAAO,CAAC,oBAAoB,CAAkB;IAI9C,OAAO,CAAC,SAAS,CAAc;IAE/B,MAAM;IA2CN,OAAO,KAAK,eAAe,GAI1B;IAGD,OAAO,KAAK,YAAY,GAWvB;IAGD,OAAO,KAAK,yBAAyB,GAYpC;IAGD,OAAO,KAAK,kBAAkB,GAS7B;IAGD,OAAO,KAAK,kBAAkB,GAO7B;IAGD,OAAO,KAAK,gBAAgB,GAc3B;IAGD,OAAO,KAAK,aAAa,GAqBxB;IAGD,OAAO,KAAK,kBAAkB,GAgB7B;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAMrB,kDAAkD;IAClD,OAAO,CAAC,eAAe;IAMvB;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;YACW,YAAY;IA2B1B,MAAM,KAAK,MAAM,IAAI,cAAc,CAgFlC;CACF"}
1
+ {"version":3,"file":"ia-review.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EAGV,cAAc,EACf,MAAM,KAAK,CAAC;AAMb,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAWvD;;GAEG;AACH,qBACa,QAAS,SAAQ,UAAU;IAEV,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,gBAAgB,SAAO;IAGvB,aAAa,SAAO;IAGpB,QAAQ,SAAyB;IAGjC,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAGtC,SAAS,UAAS;IAGlB,gBAAgB,UAAS;IAItD,OAAO,CAAC,oBAAoB,CAAkB;IAI9C,OAAO,CAAC,SAAS,CAAc;IAE/B,MAAM;IA2CN,OAAO,KAAK,eAAe,GAI1B;IAGD,OAAO,KAAK,YAAY,GAWvB;IAGD,OAAO,KAAK,yBAAyB,GAYpC;IAGD,OAAO,KAAK,kBAAkB,GAS7B;IAGD,OAAO,KAAK,kBAAkB,GAO7B;IAGD,OAAO,KAAK,gBAAgB,GAc3B;IAGD,OAAO,KAAK,aAAa,GAqBxB;IAGD,OAAO,KAAK,kBAAkB,GAgB7B;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAMrB,kDAAkD;IAClD,OAAO,CAAC,eAAe;IAMvB;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;YACW,YAAY;IAoB1B,MAAM,KAAK,MAAM,IAAI,cAAc,CAgFlC;CACF"}
@@ -23,8 +23,6 @@ let IAReview = class IAReview extends LitElement {
23
23
  this.maxBodyLength = 150;
24
24
  /* Base for URLs */
25
25
  this.baseHost = 'https://archive.org';
26
- /* CSRF token to use for delete request submission */
27
- this.csrfToken = '';
28
26
  /* Whether the person viewing this review has the power to delete it */
29
27
  this.canDelete = false;
30
28
  /* Whether we should skip the truncation step for the subject/body */
@@ -203,32 +201,22 @@ let IAReview = class IAReview extends LitElement {
203
201
  * Deletes the review, following an extra confirmation.
204
202
  */
205
203
  async deleteReview() {
206
- if (!this.review || !this.identifier)
204
+ if (!this.review?.reviewer || !this.identifier)
207
205
  return;
208
206
  if (!confirm(msg('Are you sure you want to delete this review?')))
209
207
  return;
210
- // Screennames can contain spaces and ampersands, so the parameters are
211
- // encoded rather than interpolated straight into the query string.
212
- const params = new URLSearchParams({
208
+ if (!this.reviewService) {
209
+ this.deleteMsg = msg('Sorry, we were unable to delete this review.');
210
+ return;
211
+ }
212
+ const result = await this.reviewService.deleteReview({
213
213
  identifier: this.identifier,
214
- deleteReviewer: this.review.reviewer ?? '',
215
- deleteReviewerItemname: this.review.reviewer_itemname ?? '',
216
- csrf_token: this.csrfToken,
214
+ reviewer: this.review.reviewer,
215
+ reviewerItemname: this.review.reviewer_itemname,
217
216
  });
218
- const deleteUrl = `${this.baseHost}/edit-reviews.php?${params}`;
219
- try {
220
- const response = await fetch(deleteUrl, {
221
- method: 'POST',
222
- });
223
- // fetch only rejects on a network error, so a 4xx or 5xx arrives here as
224
- // a resolved response and has to be checked before reporting success.
225
- if (!response.ok)
226
- throw new Error(`Delete failed: ${response.status}`);
227
- this.deleteMsg = 'This review has been queued for deletion.';
228
- }
229
- catch {
230
- this.deleteMsg = 'Sorry, we were unable to delete this review.';
231
- }
217
+ this.deleteMsg = result.success
218
+ ? msg('This review has been queued for deletion.')
219
+ : (result.error ?? msg('Sorry, we were unable to delete this review.'));
232
220
  }
233
221
  static get styles() {
234
222
  return css `
@@ -328,8 +316,8 @@ __decorate([
328
316
  property({ type: String })
329
317
  ], IAReview.prototype, "baseHost", void 0);
330
318
  __decorate([
331
- property({ type: String })
332
- ], IAReview.prototype, "csrfToken", void 0);
319
+ property({ type: Object })
320
+ ], IAReview.prototype, "reviewService", void 0);
333
321
  __decorate([
334
322
  property({ type: Boolean })
335
323
  ], IAReview.prototype, "canDelete", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ia-review.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n html,\n css,\n LitElement,\n nothing,\n HTMLTemplateResult,\n CSSResultGroup,\n} from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nimport { Review } from '@internetarchive/metadata-service';\n\nimport starBasic from './assets/star-basic.svg';\nimport { truncateScreenname } from './utils/truncate-screenname';\nimport sanitizeReviewBody from './utils/sanitize-review-body';\nimport friendlyTruncate from './utils/friendly-truncate';\nimport linkUrlsInText from './utils/link-urls-in-text';\nimport collapseSpace from './utils/collapse-space';\n\nimport deleteIcon from './assets/delete-icon.svg';\n\n/**\n * Renders a single IA review\n */\n@customElement('ia-review')\nexport class IAReview extends LitElement {\n /* The review to be rendered */\n @property({ type: Object }) review?: Review;\n\n /* The identifier for the item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /* Maximum renderable length for subject */\n @property({ type: Number }) maxSubjectLength = 100;\n\n /* Maximum renderable length for body */\n @property({ type: Number }) maxBodyLength = 150;\n\n /* Base for URLs */\n @property({ type: String }) baseHost = 'https://archive.org';\n\n /* CSRF token to use for delete request submission */\n @property({ type: String }) csrfToken: string = '';\n\n /* Whether the person viewing this review has the power to delete it */\n @property({ type: Boolean }) canDelete = false;\n\n /* Whether we should skip the truncation step for the subject/body */\n @property({ type: Boolean }) bypassTruncation = false;\n\n /* Whether to show truncated review title / body */\n @state()\n private showTruncatedContent: boolean = false;\n\n /* An optional message created following attempted review deletion */\n @state()\n private deleteMsg: string = '';\n\n render() {\n return !this.review\n ? html`\n <div class=\"error\">\n ${msg('This review cannot be displayed at this time.')}\n </div>\n `\n : html`\n <article class=\"review\" id=${this.generateDomId()}>\n ${this.canDelete\n ? html`\n <button\n class=\"delete-btn\"\n title=\"Delete this review\"\n @click=${this.deleteReview}\n >\n <img\n class=\"delete-icon\"\n src=${deleteIcon}\n alt=\"\"\n aria-hidden=\"true\"\n />\n </button>\n `\n : nothing}\n <div class=\"top-line\">\n <b>${msg('Reviewer:')}</b> ${this.reviewerTemplate} -\n ${this.starsTemplate}${this.createDateTemplate}\n </div>\n <div class=\"subject\">\n <b>${msg('Subject: ')}</b>${this.subjectTemplate}\n </div>\n <div class=\"body\">\n ${!this.deleteMsg\n ? this.bodyTemplate\n : html`<i>${msg(this.deleteMsg)}</i>`}\n </div>\n ${this.truncationButtonsTemplate}\n </article>\n `;\n }\n\n /* Renders the review subject, truncating if necessary */\n private get subjectTemplate(): string {\n const subject = this.review?.reviewtitle;\n\n return this.truncateContent(subject ?? '', this.maxSubjectLength);\n }\n\n /* Renders the review body, truncating if necessary */\n private get bodyTemplate(): HTMLTemplateResult | typeof nothing {\n const body = this.review?.reviewbody;\n if (!body) return nothing;\n\n const sanitizedReview = sanitizeReviewBody(body);\n const truncatedReview = this.truncateContent(\n sanitizedReview,\n this.maxBodyLength,\n );\n\n return html`${unsafeHTML(this.prepReview(truncatedReview))}`;\n }\n\n /* Renders the More/Less button if review is truncated */\n private get truncationButtonsTemplate(): HTMLTemplateResult | typeof nothing {\n if (this.bypassTruncation) return nothing;\n\n const noTruncationNeeded =\n (this.review?.reviewtitle?.length ?? 0) <= this.maxSubjectLength &&\n (this.review?.reviewbody?.length ?? 0) <= this.maxBodyLength;\n\n if (noTruncationNeeded) return nothing;\n\n return this.showTruncatedContent\n ? this.lessButtonTemplate\n : this.moreButtonTemplate;\n }\n\n /* More button for truncation */\n private get moreButtonTemplate(): HTMLTemplateResult {\n return html`\n <button\n class=\"simple-link more-btn\"\n @click=${() => (this.showTruncatedContent = true)}\n >\n ${msg('More...')}\n </button>\n `;\n }\n\n /* Less button for truncation */\n private get lessButtonTemplate(): HTMLTemplateResult {\n return html`<button\n class=\"simple-link less-btn\"\n @click=${() => (this.showTruncatedContent = false)}\n >\n ${msg('...Less')}\n </button>`;\n }\n\n /* Reviewer name, with hyperlink and truncation if needed */\n private get reviewerTemplate(): HTMLTemplateResult | typeof nothing {\n return !this.review\n ? nothing\n : this.review.reviewer_itemname\n ? html`\n <a\n href=\"${this.baseHost}/details/${this.review.reviewer_itemname}\"\n class=\"reviewer-link simple-link\"\n data-event-click-tracking=\"ItemReviews|ReviewerLink\"\n >\n ${truncateScreenname(this.review.reviewer)}\n </a>\n `\n : html`${truncateScreenname(this.review.reviewer)}`;\n }\n\n /* Number of stars that corresponds to patron's rating */\n private get starsTemplate(): HTMLTemplateResult | typeof nothing {\n if (!this.review || !this.review.stars) return nothing;\n return html`\n <div\n class=\"review-stars\"\n title=\"${msg(`${this.review.stars} out of 5 stars`)}\"\n >\n ${new Array(Number(this.review.stars)).fill(null).map(\n () =>\n html`<div class=\"review-star\">\n <img\n class=\"star-basic\"\n src=${starBasic}\n alt=\"\"\n aria-hidden=\"true\"\n />\n </div>`,\n )}\n </div>\n -\n `;\n }\n\n /* Formats the review's date for render, adding (edited) if needed */\n private get createDateTemplate(): string | typeof nothing {\n if (!this.review?.createdate || !this.review?.reviewdate) return nothing;\n\n const reviewDate = new Date(this.review.reviewdate);\n const createDate = new Date(this.review.createdate);\n\n const prettyDate = createDate.toLocaleString('en-us', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n\n const editedMsg =\n reviewDate.getTime() !== createDate.getTime() ? '(edited)' : '';\n\n return msg(`${prettyDate} ${editedMsg}`);\n }\n\n /**\n * Generates a unique ID for the review, using its createdate.\n *\n * @returns {string} The ID to render\n */\n private generateDomId(): string {\n if (!this.review?.createdate) return '';\n\n return `review-${Date.parse(this.review.createdate.toString())}`;\n }\n\n /** Truncates the review subject/body if needed */\n private truncateContent(original: string, maxLength: number): string {\n if (this.showTruncatedContent || this.bypassTruncation) return original;\n\n return friendlyTruncate(original, maxLength);\n }\n\n /**\n * Uses a set of utils in sequence to prepare a review for render,\n * including transforming inline links into anchor links\n * and collapsing internal spaces.\n *\n * @param {string} review The review to be prepped\n * @returns {string} The review prepped for render\n */\n private prepReview(review: string): string {\n return collapseSpace(linkUrlsInText(review));\n }\n\n /**\n * Deletes the review, following an extra confirmation.\n */\n private async deleteReview(): Promise<void> {\n if (!this.review || !this.identifier) return;\n if (!confirm(msg('Are you sure you want to delete this review?'))) return;\n\n // Screennames can contain spaces and ampersands, so the parameters are\n // encoded rather than interpolated straight into the query string.\n const params = new URLSearchParams({\n identifier: this.identifier,\n deleteReviewer: this.review.reviewer ?? '',\n deleteReviewerItemname: this.review.reviewer_itemname ?? '',\n csrf_token: this.csrfToken,\n });\n const deleteUrl = `${this.baseHost}/edit-reviews.php?${params}`;\n try {\n const response = await fetch(deleteUrl, {\n method: 'POST',\n });\n // fetch only rejects on a network error, so a 4xx or 5xx arrives here as\n // a resolved response and has to be checked before reporting success.\n if (!response.ok) throw new Error(`Delete failed: ${response.status}`);\n\n this.deleteMsg = 'This review has been queued for deletion.';\n } catch {\n this.deleteMsg = 'Sorry, we were unable to delete this review.';\n }\n }\n\n static get styles(): CSSResultGroup {\n return css`\n :host {\n font-family: var(\n --ia-font-stack,\n 'Helvetica Neue',\n Helvetica,\n Arial,\n sans-serif\n );\n\n font-size: inherit;\n --container-bg-color: #fbfbfd;\n --container-border-color: #999999;\n }\n\n .review {\n position: relative;\n padding-right: 30px;\n }\n\n .error {\n color: var(--error-color, #cc0000);\n }\n\n .top-line {\n margin-bottom: 0.5rem;\n }\n\n .top-line > * {\n display: inline-block;\n }\n\n .review-star {\n width: 1rem;\n display: inline-block;\n }\n\n .star-basic {\n display: block;\n width: 100%;\n }\n\n .simple-link,\n .body a {\n color: var(--link-color, #4b64ff);\n text-decoration: none;\n background: transparent;\n border: none;\n padding: 0px;\n }\n\n .simple-link:hover,\n .body a:hover {\n cursor: pointer;\n text-decoration: underline;\n }\n\n .subject {\n margin-bottom: 0.5rem;\n }\n\n .delete-btn {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0;\n background: none;\n border: 0;\n }\n\n .delete-btn:hover {\n cursor: pointer;\n }\n\n .delete-icon {\n width: 20px;\n mix-blend-mode: multiply;\n }\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"ia-review.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-review.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n html,\n css,\n LitElement,\n nothing,\n HTMLTemplateResult,\n CSSResultGroup,\n} from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nimport { Review } from '@internetarchive/metadata-service';\n\nimport type { ReviewServiceInterface } from './models';\n\nimport starBasic from './assets/star-basic.svg';\nimport { truncateScreenname } from './utils/truncate-screenname';\nimport sanitizeReviewBody from './utils/sanitize-review-body';\nimport friendlyTruncate from './utils/friendly-truncate';\nimport linkUrlsInText from './utils/link-urls-in-text';\nimport collapseSpace from './utils/collapse-space';\n\nimport deleteIcon from './assets/delete-icon.svg';\n\n/**\n * Renders a single IA review\n */\n@customElement('ia-review')\nexport class IAReview extends LitElement {\n /* The review to be rendered */\n @property({ type: Object }) review?: Review;\n\n /* The identifier for the item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /* Maximum renderable length for subject */\n @property({ type: Number }) maxSubjectLength = 100;\n\n /* Maximum renderable length for body */\n @property({ type: Number }) maxBodyLength = 150;\n\n /* Base for URLs */\n @property({ type: String }) baseHost = 'https://archive.org';\n\n /* Handles the review deletion request */\n @property({ type: Object }) reviewService?: ReviewServiceInterface;\n\n /* Whether the person viewing this review has the power to delete it */\n @property({ type: Boolean }) canDelete = false;\n\n /* Whether we should skip the truncation step for the subject/body */\n @property({ type: Boolean }) bypassTruncation = false;\n\n /* Whether to show truncated review title / body */\n @state()\n private showTruncatedContent: boolean = false;\n\n /* An optional message created following attempted review deletion */\n @state()\n private deleteMsg: string = '';\n\n render() {\n return !this.review\n ? html`\n <div class=\"error\">\n ${msg('This review cannot be displayed at this time.')}\n </div>\n `\n : html`\n <article class=\"review\" id=${this.generateDomId()}>\n ${this.canDelete\n ? html`\n <button\n class=\"delete-btn\"\n title=\"Delete this review\"\n @click=${this.deleteReview}\n >\n <img\n class=\"delete-icon\"\n src=${deleteIcon}\n alt=\"\"\n aria-hidden=\"true\"\n />\n </button>\n `\n : nothing}\n <div class=\"top-line\">\n <b>${msg('Reviewer:')}</b> ${this.reviewerTemplate} -\n ${this.starsTemplate}${this.createDateTemplate}\n </div>\n <div class=\"subject\">\n <b>${msg('Subject: ')}</b>${this.subjectTemplate}\n </div>\n <div class=\"body\">\n ${!this.deleteMsg\n ? this.bodyTemplate\n : html`<i>${msg(this.deleteMsg)}</i>`}\n </div>\n ${this.truncationButtonsTemplate}\n </article>\n `;\n }\n\n /* Renders the review subject, truncating if necessary */\n private get subjectTemplate(): string {\n const subject = this.review?.reviewtitle;\n\n return this.truncateContent(subject ?? '', this.maxSubjectLength);\n }\n\n /* Renders the review body, truncating if necessary */\n private get bodyTemplate(): HTMLTemplateResult | typeof nothing {\n const body = this.review?.reviewbody;\n if (!body) return nothing;\n\n const sanitizedReview = sanitizeReviewBody(body);\n const truncatedReview = this.truncateContent(\n sanitizedReview,\n this.maxBodyLength,\n );\n\n return html`${unsafeHTML(this.prepReview(truncatedReview))}`;\n }\n\n /* Renders the More/Less button if review is truncated */\n private get truncationButtonsTemplate(): HTMLTemplateResult | typeof nothing {\n if (this.bypassTruncation) return nothing;\n\n const noTruncationNeeded =\n (this.review?.reviewtitle?.length ?? 0) <= this.maxSubjectLength &&\n (this.review?.reviewbody?.length ?? 0) <= this.maxBodyLength;\n\n if (noTruncationNeeded) return nothing;\n\n return this.showTruncatedContent\n ? this.lessButtonTemplate\n : this.moreButtonTemplate;\n }\n\n /* More button for truncation */\n private get moreButtonTemplate(): HTMLTemplateResult {\n return html`\n <button\n class=\"simple-link more-btn\"\n @click=${() => (this.showTruncatedContent = true)}\n >\n ${msg('More...')}\n </button>\n `;\n }\n\n /* Less button for truncation */\n private get lessButtonTemplate(): HTMLTemplateResult {\n return html`<button\n class=\"simple-link less-btn\"\n @click=${() => (this.showTruncatedContent = false)}\n >\n ${msg('...Less')}\n </button>`;\n }\n\n /* Reviewer name, with hyperlink and truncation if needed */\n private get reviewerTemplate(): HTMLTemplateResult | typeof nothing {\n return !this.review\n ? nothing\n : this.review.reviewer_itemname\n ? html`\n <a\n href=\"${this.baseHost}/details/${this.review.reviewer_itemname}\"\n class=\"reviewer-link simple-link\"\n data-event-click-tracking=\"ItemReviews|ReviewerLink\"\n >\n ${truncateScreenname(this.review.reviewer)}\n </a>\n `\n : html`${truncateScreenname(this.review.reviewer)}`;\n }\n\n /* Number of stars that corresponds to patron's rating */\n private get starsTemplate(): HTMLTemplateResult | typeof nothing {\n if (!this.review || !this.review.stars) return nothing;\n return html`\n <div\n class=\"review-stars\"\n title=\"${msg(`${this.review.stars} out of 5 stars`)}\"\n >\n ${new Array(Number(this.review.stars)).fill(null).map(\n () =>\n html`<div class=\"review-star\">\n <img\n class=\"star-basic\"\n src=${starBasic}\n alt=\"\"\n aria-hidden=\"true\"\n />\n </div>`,\n )}\n </div>\n -\n `;\n }\n\n /* Formats the review's date for render, adding (edited) if needed */\n private get createDateTemplate(): string | typeof nothing {\n if (!this.review?.createdate || !this.review?.reviewdate) return nothing;\n\n const reviewDate = new Date(this.review.reviewdate);\n const createDate = new Date(this.review.createdate);\n\n const prettyDate = createDate.toLocaleString('en-us', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n\n const editedMsg =\n reviewDate.getTime() !== createDate.getTime() ? '(edited)' : '';\n\n return msg(`${prettyDate} ${editedMsg}`);\n }\n\n /**\n * Generates a unique ID for the review, using its createdate.\n *\n * @returns {string} The ID to render\n */\n private generateDomId(): string {\n if (!this.review?.createdate) return '';\n\n return `review-${Date.parse(this.review.createdate.toString())}`;\n }\n\n /** Truncates the review subject/body if needed */\n private truncateContent(original: string, maxLength: number): string {\n if (this.showTruncatedContent || this.bypassTruncation) return original;\n\n return friendlyTruncate(original, maxLength);\n }\n\n /**\n * Uses a set of utils in sequence to prepare a review for render,\n * including transforming inline links into anchor links\n * and collapsing internal spaces.\n *\n * @param {string} review The review to be prepped\n * @returns {string} The review prepped for render\n */\n private prepReview(review: string): string {\n return collapseSpace(linkUrlsInText(review));\n }\n\n /**\n * Deletes the review, following an extra confirmation.\n */\n private async deleteReview(): Promise<void> {\n if (!this.review?.reviewer || !this.identifier) return;\n if (!confirm(msg('Are you sure you want to delete this review?'))) return;\n\n if (!this.reviewService) {\n this.deleteMsg = msg('Sorry, we were unable to delete this review.');\n return;\n }\n\n const result = await this.reviewService.deleteReview({\n identifier: this.identifier,\n reviewer: this.review.reviewer,\n reviewerItemname: this.review.reviewer_itemname,\n });\n\n this.deleteMsg = result.success\n ? msg('This review has been queued for deletion.')\n : (result.error ?? msg('Sorry, we were unable to delete this review.'));\n }\n\n static get styles(): CSSResultGroup {\n return css`\n :host {\n font-family: var(\n --ia-font-stack,\n 'Helvetica Neue',\n Helvetica,\n Arial,\n sans-serif\n );\n\n font-size: inherit;\n --container-bg-color: #fbfbfd;\n --container-border-color: #999999;\n }\n\n .review {\n position: relative;\n padding-right: 30px;\n }\n\n .error {\n color: var(--error-color, #cc0000);\n }\n\n .top-line {\n margin-bottom: 0.5rem;\n }\n\n .top-line > * {\n display: inline-block;\n }\n\n .review-star {\n width: 1rem;\n display: inline-block;\n }\n\n .star-basic {\n display: block;\n width: 100%;\n }\n\n .simple-link,\n .body a {\n color: var(--link-color, #4b64ff);\n text-decoration: none;\n background: transparent;\n border: none;\n padding: 0px;\n }\n\n .simple-link:hover,\n .body a:hover {\n cursor: pointer;\n text-decoration: underline;\n }\n\n .subject {\n margin-bottom: 0.5rem;\n }\n\n .delete-btn {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0;\n background: none;\n border: 0;\n }\n\n .delete-btn:hover {\n cursor: pointer;\n }\n\n .delete-icon {\n width: 20px;\n mix-blend-mode: multiply;\n }\n `;\n }\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import { CSSResultGroup, HTMLTemplateResult, LitElement, PropertyValues } from 'lit';
2
2
  import type { Review } from '@internetarchive/metadata-service';
3
3
  import type { RecaptchaManagerInterface } from '@internetarchive/recaptcha-manager';
4
- import { FetchHandlerInterface } from '@internetarchive/fetch-handler';
5
4
  import '../../elements/ia-button/ia-button.js';
5
+ import type { ReviewServiceInterface } from './models.js';
6
6
  import './ia-review.js';
7
7
  import './ia-review-form.js';
8
8
  /**
@@ -20,8 +20,6 @@ export declare class IAReviews extends LitElement {
20
20
  maxBodyLength?: number;
21
21
  baseHost: string;
22
22
  /** REVIEW FORM PROPERTIES */
23
- token: string;
24
- endpointPath: string;
25
23
  /** Form submitter's screenname, if applicable */
26
24
  submitterScreenname: string;
27
25
  /** Form submitter's itemname, if applicable */
@@ -30,7 +28,14 @@ export declare class IAReviews extends LitElement {
30
28
  bypassRecaptcha: boolean;
31
29
  reviewSubmissionError?: string;
32
30
  reviewAddEditRequested: boolean;
33
- fetchHandler: FetchHandlerInterface;
31
+ /**
32
+ * Handles the review write and delete requests.
33
+ *
34
+ * Writing and deleting need one, since the service is what carries the CSRF token. Left unset,
35
+ * reviews still render and the form still opens, but a submission reports a generic failure.
36
+ */
37
+ reviewService?: ReviewServiceInterface;
38
+ private activeReviewService?;
34
39
  displayReviewForm: boolean;
35
40
  private displayReviews;
36
41
  private filteredReviews;
@@ -1 +1 @@
1
- {"version":3,"file":"ia-reviews.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-reviews.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EAEd,kBAAkB,EAClB,UAAU,EAEV,cAAc,EACf,MAAM,KAAK,CAAC;AAKb,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EACL,qBAAqB,EAEtB,MAAM,gCAAgC,CAAC;AAExC,OAAO,mCAAmC,CAAC;AAE3C,OAAO,aAAa,CAAC;AACrB,OAAO,kBAAkB,CAAC;AAE1B;;GAEG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,yCAAyC;IAEb,UAAU,CAAC,EAAE,MAAM,CAAC;IAGrB,OAAO,EAAE,MAAM,EAAE,CAAM;IAGrB,eAAe,UAAS;IAGxB,aAAa,UAAS;IAGtB,SAAS,UAAS;IAGlB,uBAAuB,UAAS;IAGjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,QAAQ,SAAyB;IAE7D,6BAA6B;IAED,KAAK,EAAE,MAAM,CAAM;IAGnB,YAAY,EAAE,MAAM,CAAuB;IAEvE,iDAAiD;IACrB,mBAAmB,EAAE,MAAM,CAAe;IAEtE,+CAA+C;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAG5C,eAAe,EAAE,OAAO,CAAS;IAGlC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG9B,sBAAsB,EAAE,OAAO,CAAS;IAGzC,YAAY,EAAE,qBAAqB,CACxC;IAIvB,iBAAiB,EAAE,OAAO,CAAS;IAInC,OAAO,CAAC,cAAc,CAAkB;IAIxC,OAAO,CAAC,eAAe,CAAgB;IAIvC,OAAO,CAAC,aAAa,CAAC,CAAS;IAI/B,OAAO,CAAC,YAAY,CAAa;IAIjC,OAAO,CAAC,kBAAkB,CAAkB;IAE5C,MAAM;IAuBN,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAqBnD,OAAO,KAAK,uBAAuB,GAIlC;IAGD,OAAO,KAAK,oBAAoB,GAuB/B;IAGD,OAAO,KAAK,yBAAyB,GAiBpC;IAED;;;OAGG;IACH,OAAO,KAAK,6BAA6B,GA4BxC;IAED,oFAAoF;IACpF,OAAO,CAAC,iBAAiB;IAiBzB,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;IAapB,oDAAoD;IACpD,OAAO,CAAC,aAAa;IAKrB,2CAA2C;IAC3C,OAAO,CAAC,kBAAkB;IAS1B,0CAA0C;IAC1C,OAAO,CAAC,kBAAkB;IAS1B,MAAM,KAAK,MAAM,IAAI,cAAc,CAuDlC;CACF"}
1
+ {"version":3,"file":"ia-reviews.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-reviews.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EAEd,kBAAkB,EAClB,UAAU,EAEV,cAAc,EACf,MAAM,KAAK,CAAC;AAKb,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAEpF,OAAO,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAEvD,OAAO,aAAa,CAAC;AACrB,OAAO,kBAAkB,CAAC;AAE1B;;GAEG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,yCAAyC;IAEb,UAAU,CAAC,EAAE,MAAM,CAAC;IAGrB,OAAO,EAAE,MAAM,EAAE,CAAM;IAGrB,eAAe,UAAS;IAGxB,aAAa,UAAS;IAGtB,SAAS,UAAS;IAGlB,uBAAuB,UAAS;IAGjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAG1B,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,QAAQ,SAAyB;IAE7D,6BAA6B;IAE7B,iDAAiD;IACrB,mBAAmB,EAAE,MAAM,CAAe;IAEtE,+CAA+C;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAG5C,eAAe,EAAE,OAAO,CAAS;IAGlC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG9B,sBAAsB,EAAE,OAAO,CAAS;IAErE;;;;;OAKG;IACyB,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAInE,OAAO,CAAC,mBAAmB,CAAC,CAAyB;IAIrD,iBAAiB,EAAE,OAAO,CAAS;IAInC,OAAO,CAAC,cAAc,CAAkB;IAIxC,OAAO,CAAC,eAAe,CAAgB;IAIvC,OAAO,CAAC,aAAa,CAAC,CAAS;IAI/B,OAAO,CAAC,YAAY,CAAa;IAIjC,OAAO,CAAC,kBAAkB,CAAkB;IAE5C,MAAM;IAuBN,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAgCnD,OAAO,KAAK,uBAAuB,GAIlC;IAGD,OAAO,KAAK,oBAAoB,GAuB/B;IAGD,OAAO,KAAK,yBAAyB,GAiBpC;IAED;;;OAGG;IACH,OAAO,KAAK,6BAA6B,GAyBxC;IAED,oFAAoF;IACpF,OAAO,CAAC,iBAAiB;IAiBzB,8CAA8C;IAC9C,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;IAapB,oDAAoD;IACpD,OAAO,CAAC,aAAa;IAKrB,2CAA2C;IAC3C,OAAO,CAAC,kBAAkB;IAS1B,0CAA0C;IAC1C,OAAO,CAAC,kBAAkB;IAS1B,MAAM,KAAK,MAAM,IAAI,cAAc,CAuDlC;CACF"}
@@ -3,8 +3,8 @@ import { css, html, LitElement, nothing, } from 'lit';
3
3
  import { property, state } from 'lit/decorators.js';
4
4
  import { customElement } from '../../util/custom-element.js';
5
5
  import { msg } from '@lit/localize';
6
- import { IaFetchHandler, } from '@internetarchive/fetch-handler';
7
6
  import '../../elements/ia-button/ia-button.js';
7
+ import { ReviewService } from './review-service.js';
8
8
  import './ia-review.js';
9
9
  import './ia-review-form.js';
10
10
  /**
@@ -23,21 +23,15 @@ let IAReviews = class IAReviews extends LitElement {
23
23
  this.canDelete = false;
24
24
  /* Whether we should bypass the show/hide and more/less toggles */
25
25
  this.displayReviewsByDefault = false;
26
- /* Base for URLs */
26
+ /* Base for URLs. Also used for the reviewer profile links. */
27
27
  this.baseHost = 'https://archive.org';
28
28
  /** REVIEW FORM PROPERTIES */
29
- /* The token for the review edit */
30
- this.token = '';
31
- /* The path for the endpoint we're submitting to */
32
- this.endpointPath = '/write-review.php';
33
29
  /** Form submitter's screenname, if applicable */
34
30
  this.submitterScreenname = 'Anonymous';
35
31
  /* Optionally avoid using recaptcha for the form */
36
32
  this.bypassRecaptcha = false;
37
33
  /* Whether the add/edit button has been clicked */
38
34
  this.reviewAddEditRequested = false;
39
- /* An optional handler for form submission to pass along to the form */
40
- this.fetchHandler = new IaFetchHandler();
41
35
  /* Whether to display the review form or the editable review */
42
36
  this.displayReviewForm = false;
43
37
  /* Whether to display the existing reviews */
@@ -71,6 +65,14 @@ let IAReviews = class IAReviews extends LitElement {
71
65
  `;
72
66
  }
73
67
  willUpdate(changed) {
68
+ if (!this.activeReviewService ||
69
+ changed.has('reviewService') ||
70
+ changed.has('baseHost')) {
71
+ // the fallback keeps rendering and the form working for a consumer that supplies no
72
+ // service; it carries no CSRF token, so a submission through it fails
73
+ this.activeReviewService =
74
+ this.reviewService ?? new ReviewService({ baseHost: this.baseHost });
75
+ }
74
76
  if (changed.has('reviews') || changed.has('submitterItemname')) {
75
77
  this.reviewsCount = this.reviews.length;
76
78
  this.sortFilterReviews();
@@ -145,15 +147,12 @@ let IAReviews = class IAReviews extends LitElement {
145
147
  : html `<ia-review-form
146
148
  .identifier=${this.identifier}
147
149
  .oldReview=${this.currentReview}
148
- .baseHost=${this.baseHost}
149
- .endpointPath=${this.endpointPath}
150
150
  .submitterItemname=${this.submitterItemname}
151
151
  .submitterScreenname=${this.submitterScreenname}
152
152
  .maxSubjectLength=${this.maxSubjectLength}
153
153
  .maxBodyLength=${this.maxBodyLength}
154
- .token=${this.token}
155
154
  .unrecoverableError=${this.reviewSubmissionError}
156
- .fetchHandler=${this.fetchHandler}
155
+ .reviewService=${this.activeReviewService}
157
156
  .recaptchaManager=${this.recaptchaActivated
158
157
  ? this.recaptchaManager
159
158
  : undefined}
@@ -193,7 +192,7 @@ let IAReviews = class IAReviews extends LitElement {
193
192
  .review=${review}
194
193
  .identifier=${this.identifier}
195
194
  .baseHost=${this.baseHost}
196
- .csrfToken=${this.token}
195
+ .reviewService=${this.activeReviewService}
197
196
  ?canDelete=${this.canDelete}
198
197
  ?bypassTruncation=${this.displayReviewsByDefault}
199
198
  ></ia-review>`;
@@ -305,12 +304,6 @@ __decorate([
305
304
  __decorate([
306
305
  property({ type: String })
307
306
  ], IAReviews.prototype, "baseHost", void 0);
308
- __decorate([
309
- property({ type: String })
310
- ], IAReviews.prototype, "token", void 0);
311
- __decorate([
312
- property({ type: String })
313
- ], IAReviews.prototype, "endpointPath", void 0);
314
307
  __decorate([
315
308
  property({ type: String })
316
309
  ], IAReviews.prototype, "submitterScreenname", void 0);
@@ -331,7 +324,10 @@ __decorate([
331
324
  ], IAReviews.prototype, "reviewAddEditRequested", void 0);
332
325
  __decorate([
333
326
  property({ type: Object })
334
- ], IAReviews.prototype, "fetchHandler", void 0);
327
+ ], IAReviews.prototype, "reviewService", void 0);
328
+ __decorate([
329
+ state()
330
+ ], IAReviews.prototype, "activeReviewService", void 0);
335
331
  __decorate([
336
332
  state()
337
333
  ], IAReviews.prototype, "displayReviewForm", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ia-reviews.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-reviews.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n css,\n CSSResultGroup,\n html,\n HTMLTemplateResult,\n LitElement,\n nothing,\n PropertyValues,\n} from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\n\nimport type { Review } from '@internetarchive/metadata-service';\nimport type { RecaptchaManagerInterface } from '@internetarchive/recaptcha-manager';\nimport {\n FetchHandlerInterface,\n IaFetchHandler,\n} from '@internetarchive/fetch-handler';\n\nimport '@src/elements/ia-button/ia-button';\n\nimport './ia-review';\nimport './ia-review-form';\n\n/**\n * Renders the full IA reviews piece\n */\n@customElement('ia-reviews')\nexport class IAReviews extends LitElement {\n /** GLOBAL/REVIEW-RENDERING PROPERTIES */\n /* The identifier for the item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /* The list of reviews to be rendered */\n @property({ type: Array }) reviews: Review[] = [];\n\n /* Whether reviews are disabled for the item */\n @property({ type: Boolean }) reviewsDisabled = false;\n\n /* Whether reviews are frozen for the item */\n @property({ type: Boolean }) reviewsFrozen = false;\n\n /* Whether the person viewing the reviews section has the power to delete reviews */\n @property({ type: Boolean }) canDelete = false;\n\n /* Whether we should bypass the show/hide and more/less toggles */\n @property({ type: Boolean }) displayReviewsByDefault = false;\n\n /* Maximum allowable length for subject */\n @property({ type: Number }) maxSubjectLength?: number;\n\n /* Maximum allowable length for body */\n @property({ type: Number }) maxBodyLength?: number;\n\n /* Base for URLs */\n @property({ type: String }) baseHost = 'https://archive.org';\n\n /** REVIEW FORM PROPERTIES */\n /* The token for the review edit */\n @property({ type: String }) token: string = '';\n\n /* The path for the endpoint we're submitting to */\n @property({ type: String }) endpointPath: string = '/write-review.php';\n\n /** Form submitter's screenname, if applicable */\n @property({ type: String }) submitterScreenname: string = 'Anonymous';\n\n /** Form submitter's itemname, if applicable */\n @property({ type: String }) submitterItemname?: string;\n\n /* Service for activating the recaptcha challenge */\n @property({ type: Object }) recaptchaManager?: RecaptchaManagerInterface;\n\n /* Optionally avoid using recaptcha for the form */\n @property({ type: Boolean }) bypassRecaptcha: boolean = false;\n\n /* An optional error message to be displayed instead of the review form */\n @property({ type: String }) reviewSubmissionError?: string;\n\n /* Whether the add/edit button has been clicked */\n @property({ type: Boolean }) reviewAddEditRequested: boolean = false;\n\n /* An optional handler for form submission to pass along to the form */\n @property({ type: Object }) fetchHandler: FetchHandlerInterface =\n new IaFetchHandler();\n\n /* Whether to display the review form or the editable review */\n @state()\n displayReviewForm: boolean = false;\n\n /* Whether to display the existing reviews */\n @state()\n private displayReviews: boolean = false;\n\n /* The sorted and filtered reviews to render */\n @state()\n private filteredReviews: Review[] = [];\n\n /* The current version of the review */\n @state()\n private currentReview?: Review;\n\n /* The number of reviews */\n @state()\n private reviewsCount: number = 0;\n\n /* Whether recaptcha has been activated (by clicking the add/edit button) */\n @state()\n private recaptchaActivated: boolean = false;\n\n render() {\n if (this.reviewsDisabled) return this.reviewsDisabledTemplate;\n if (this.reviewsCount === 0 && !this.displayReviewForm)\n return this.noReviewsMsgTemplate;\n if (!this.displayReviews) return this.displayReviewsMsgTemplate;\n\n return html`\n <div class=\"reviews-list\">\n ${this.reviewsFrozen\n ? html`<div class=\"message\">\n ${msg('Reviews can no longer be added to this item.')}\n </div>`\n : nothing}\n ${this.editableCurrentReviewTemplate}\n ${this.filteredReviews.map((review) =>\n review.reviewer_itemname !== this.submitterItemname\n ? this.renderReview(review)\n : nothing,\n )}\n </div>\n `;\n }\n\n protected willUpdate(changed: PropertyValues): void {\n if (changed.has('reviews') || changed.has('submitterItemname')) {\n this.reviewsCount = this.reviews.length;\n this.sortFilterReviews();\n }\n\n if (changed.has('displayReviewForm') && this.displayReviewForm === true) {\n if (!this.bypassRecaptcha && !this.recaptchaActivated)\n this.recaptchaActivated = true;\n this.displayReviews = true;\n }\n\n if (\n changed.has('displayReviewsByDefault') &&\n this.displayReviewsByDefault\n ) {\n this.displayReviews = true;\n }\n }\n\n /* Message to display if reviews are disabled */\n private get reviewsDisabledTemplate(): HTMLTemplateResult {\n return html`<div class=\"message\">\n ${msg('Reviews have been disabled for this item.')}\n </div>`;\n }\n\n /* Message to display instead of the reviews list if there are no reviews yet */\n private get noReviewsMsgTemplate(): HTMLTemplateResult {\n if (this.reviewsFrozen)\n return html`\n <div class=\"message\">\n ${msg('Reviews cannot be added to this item.')}\n </div>\n `;\n\n return html`\n <div class=\"message\">\n ${msg('There are no reviews yet.')}\n ${msg(html`\n Be the first one to\n <ia-button\n mode=\"link\"\n class=\"no-reviews-btn\"\n @click=${this.addEditReview}\n >\n write a review</ia-button\n >.\n `)}\n </div>\n `;\n }\n\n /* Message to display instead of the reviews list if reviews are hidden */\n private get displayReviewsMsgTemplate(): HTMLTemplateResult {\n return html`\n <div class=\"message\">\n ${this.reviewsCount === 1\n ? msg('There is 1 review for this item.')\n : msg(`There are ${this.reviewsCount} reviews for this item.`)}\n <ia-button\n mode=\"link\"\n class=\"display-reviews-btn\"\n @click=${() => (this.displayReviews = true)}\n >\n ${msg(\n `Display ${this.reviewsCount === 1 ? 'review' : 'reviews'}`,\n )}</ia-button\n >.\n </div>\n `;\n }\n\n /**\n * Renders the review form, passing along all necessary properties; this will also render\n * the patron's own review if applicable and/or following submission.\n */\n private get editableCurrentReviewTemplate():\n | HTMLTemplateResult\n | typeof nothing {\n if (!this.displayReviewForm && !this.currentReview) return nothing;\n\n return html`<div class=\"own-review-container\">\n ${!this.displayReviewForm\n ? this.renderReview(this.currentReview)\n : html`<ia-review-form\n .identifier=${this.identifier}\n .oldReview=${this.currentReview}\n .baseHost=${this.baseHost}\n .endpointPath=${this.endpointPath}\n .submitterItemname=${this.submitterItemname}\n .submitterScreenname=${this.submitterScreenname}\n .maxSubjectLength=${this.maxSubjectLength}\n .maxBodyLength=${this.maxBodyLength}\n .token=${this.token}\n .unrecoverableError=${this.reviewSubmissionError}\n .fetchHandler=${this.fetchHandler}\n .recaptchaManager=${this.recaptchaActivated\n ? this.recaptchaManager\n : undefined}\n ?bypassRecaptcha=${this.bypassRecaptcha}\n @reviewUpdated=${this.handleReviewUpdate}\n @reviewEditCanceled=${this.handleEditCanceled}\n ></ia-review-form>`}\n </div>`;\n }\n\n /** Sorts the reviews and splits the patron's own review out of the reviews array */\n private sortFilterReviews(): void {\n let patronsOwnReview: Review | undefined;\n const filteredReviews: Review[] = [];\n\n this.reviews.forEach((review) => {\n if (\n !patronsOwnReview &&\n review.reviewer_itemname === this.submitterItemname\n ) {\n patronsOwnReview = review;\n } else filteredReviews.push(review);\n });\n\n this.currentReview = patronsOwnReview;\n this.filteredReviews = this.sortReviews(filteredReviews);\n }\n\n /** Sorts reviews by create date for render */\n private sortReviews(reviews: Review[]): Review[] {\n const sortedReviews = [...reviews].sort((a, b) =>\n a.createdate && b.createdate\n ? new Date(b.createdate).getTime() - new Date(a.createdate).getTime()\n : 0,\n );\n\n return sortedReviews;\n }\n\n /* Renders the given review, using the ia-review component */\n private renderReview(review?: Review): HTMLTemplateResult | typeof nothing {\n if (!review) return nothing;\n\n return html`<ia-review\n .review=${review}\n .identifier=${this.identifier}\n .baseHost=${this.baseHost}\n .csrfToken=${this.token}\n ?canDelete=${this.canDelete}\n ?bypassTruncation=${this.displayReviewsByDefault}\n ></ia-review>`;\n }\n\n /** Prepare the review form for adding or editing */\n private addEditReview(): void {\n if (!this.bypassRecaptcha) this.recaptchaActivated = true;\n this.displayReviewForm = true;\n }\n\n /** Handles successful review submission */\n private handleReviewUpdate(e: CustomEvent<Review>): void {\n if (!this.currentReview && e.detail) {\n this.dispatchEvent(new CustomEvent<void>('newReviewAdded'));\n this.reviewsCount += 1;\n }\n this.currentReview = e.detail;\n this.displayReviewForm = false;\n }\n\n /** Closes the review form if requested */\n private handleEditCanceled(): void {\n this.displayReviewForm = false;\n\n // Display no-reviews message again if no new review was added\n if (this.reviewsCount === 0) {\n this.displayReviews = false;\n }\n }\n\n static get styles(): CSSResultGroup {\n return [\n css`\n :host {\n font-family: var(\n --ia-font-stack,\n 'Helvetica Neue',\n Helvetica,\n Arial,\n sans-serif\n );\n\n color: var(--ia-text-color, #2c2c2c);\n }\n\n .reviews-list {\n display: flex;\n flex-direction: column;\n gap: 2rem;\n width: 100%;\n }\n\n .own-review-container {\n --error-color: var(--container-error-color, #ea0202);\n --link-color: var(--container-link-color, #4f65f5);\n --container-error-color: #ea0202;\n --container-link-color: #4f65f5;\n --container-bg-color: #fbfbfd;\n --container-border-color: #999999;\n\n border: 2px solid var(--container-border-color, #999999);\n border-radius: 5px;\n background-color: var(--container-bg-color, #fbfbfd);\n padding: 10px;\n }\n\n .message {\n font-weight: 200;\n }\n\n .message ia-button {\n display: inline;\n vertical-align: baseline;\n }\n\n /* The button inside ia-button is a flex box, which would break these\n out of the sentence they sit in. */\n .message ia-button::part(button) {\n display: inline;\n min-height: 0;\n padding: 0;\n font-weight: 600;\n }\n `,\n ];\n }\n}\n"]}
1
+ {"version":3,"file":"ia-reviews.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/ia-reviews.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n css,\n CSSResultGroup,\n html,\n HTMLTemplateResult,\n LitElement,\n nothing,\n PropertyValues,\n} from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { customElement } from '@src/util/custom-element';\nimport { msg } from '@lit/localize';\n\nimport type { Review } from '@internetarchive/metadata-service';\nimport type { RecaptchaManagerInterface } from '@internetarchive/recaptcha-manager';\n\nimport '@src/elements/ia-button/ia-button';\n\nimport type { ReviewServiceInterface } from './models';\nimport { ReviewService } from './review-service';\nimport './ia-review';\nimport './ia-review-form';\n\n/**\n * Renders the full IA reviews piece\n */\n@customElement('ia-reviews')\nexport class IAReviews extends LitElement {\n /** GLOBAL/REVIEW-RENDERING PROPERTIES */\n /* The identifier for the item being reviewed */\n @property({ type: String }) identifier?: string;\n\n /* The list of reviews to be rendered */\n @property({ type: Array }) reviews: Review[] = [];\n\n /* Whether reviews are disabled for the item */\n @property({ type: Boolean }) reviewsDisabled = false;\n\n /* Whether reviews are frozen for the item */\n @property({ type: Boolean }) reviewsFrozen = false;\n\n /* Whether the person viewing the reviews section has the power to delete reviews */\n @property({ type: Boolean }) canDelete = false;\n\n /* Whether we should bypass the show/hide and more/less toggles */\n @property({ type: Boolean }) displayReviewsByDefault = false;\n\n /* Maximum allowable length for subject */\n @property({ type: Number }) maxSubjectLength?: number;\n\n /* Maximum allowable length for body */\n @property({ type: Number }) maxBodyLength?: number;\n\n /* Base for URLs. Also used for the reviewer profile links. */\n @property({ type: String }) baseHost = 'https://archive.org';\n\n /** REVIEW FORM PROPERTIES */\n\n /** Form submitter's screenname, if applicable */\n @property({ type: String }) submitterScreenname: string = 'Anonymous';\n\n /** Form submitter's itemname, if applicable */\n @property({ type: String }) submitterItemname?: string;\n\n /* Service for activating the recaptcha challenge */\n @property({ type: Object }) recaptchaManager?: RecaptchaManagerInterface;\n\n /* Optionally avoid using recaptcha for the form */\n @property({ type: Boolean }) bypassRecaptcha: boolean = false;\n\n /* An optional error message to be displayed instead of the review form */\n @property({ type: String }) reviewSubmissionError?: string;\n\n /* Whether the add/edit button has been clicked */\n @property({ type: Boolean }) reviewAddEditRequested: boolean = false;\n\n /**\n * Handles the review write and delete requests.\n *\n * Writing and deleting need one, since the service is what carries the CSRF token. Left unset,\n * reviews still render and the form still opens, but a submission reports a generic failure.\n */\n @property({ type: Object }) reviewService?: ReviewServiceInterface;\n\n /* The service the components actually call, whether injected or built here */\n @state()\n private activeReviewService?: ReviewServiceInterface;\n\n /* Whether to display the review form or the editable review */\n @state()\n displayReviewForm: boolean = false;\n\n /* Whether to display the existing reviews */\n @state()\n private displayReviews: boolean = false;\n\n /* The sorted and filtered reviews to render */\n @state()\n private filteredReviews: Review[] = [];\n\n /* The current version of the review */\n @state()\n private currentReview?: Review;\n\n /* The number of reviews */\n @state()\n private reviewsCount: number = 0;\n\n /* Whether recaptcha has been activated (by clicking the add/edit button) */\n @state()\n private recaptchaActivated: boolean = false;\n\n render() {\n if (this.reviewsDisabled) return this.reviewsDisabledTemplate;\n if (this.reviewsCount === 0 && !this.displayReviewForm)\n return this.noReviewsMsgTemplate;\n if (!this.displayReviews) return this.displayReviewsMsgTemplate;\n\n return html`\n <div class=\"reviews-list\">\n ${this.reviewsFrozen\n ? html`<div class=\"message\">\n ${msg('Reviews can no longer be added to this item.')}\n </div>`\n : nothing}\n ${this.editableCurrentReviewTemplate}\n ${this.filteredReviews.map((review) =>\n review.reviewer_itemname !== this.submitterItemname\n ? this.renderReview(review)\n : nothing,\n )}\n </div>\n `;\n }\n\n protected willUpdate(changed: PropertyValues): void {\n if (\n !this.activeReviewService ||\n changed.has('reviewService') ||\n changed.has('baseHost')\n ) {\n // the fallback keeps rendering and the form working for a consumer that supplies no\n // service; it carries no CSRF token, so a submission through it fails\n this.activeReviewService =\n this.reviewService ?? new ReviewService({ baseHost: this.baseHost });\n }\n\n if (changed.has('reviews') || changed.has('submitterItemname')) {\n this.reviewsCount = this.reviews.length;\n this.sortFilterReviews();\n }\n\n if (changed.has('displayReviewForm') && this.displayReviewForm === true) {\n if (!this.bypassRecaptcha && !this.recaptchaActivated)\n this.recaptchaActivated = true;\n this.displayReviews = true;\n }\n\n if (\n changed.has('displayReviewsByDefault') &&\n this.displayReviewsByDefault\n ) {\n this.displayReviews = true;\n }\n }\n\n /* Message to display if reviews are disabled */\n private get reviewsDisabledTemplate(): HTMLTemplateResult {\n return html`<div class=\"message\">\n ${msg('Reviews have been disabled for this item.')}\n </div>`;\n }\n\n /* Message to display instead of the reviews list if there are no reviews yet */\n private get noReviewsMsgTemplate(): HTMLTemplateResult {\n if (this.reviewsFrozen)\n return html`\n <div class=\"message\">\n ${msg('Reviews cannot be added to this item.')}\n </div>\n `;\n\n return html`\n <div class=\"message\">\n ${msg('There are no reviews yet.')}\n ${msg(html`\n Be the first one to\n <ia-button\n mode=\"link\"\n class=\"no-reviews-btn\"\n @click=${this.addEditReview}\n >\n write a review</ia-button\n >.\n `)}\n </div>\n `;\n }\n\n /* Message to display instead of the reviews list if reviews are hidden */\n private get displayReviewsMsgTemplate(): HTMLTemplateResult {\n return html`\n <div class=\"message\">\n ${this.reviewsCount === 1\n ? msg('There is 1 review for this item.')\n : msg(`There are ${this.reviewsCount} reviews for this item.`)}\n <ia-button\n mode=\"link\"\n class=\"display-reviews-btn\"\n @click=${() => (this.displayReviews = true)}\n >\n ${msg(\n `Display ${this.reviewsCount === 1 ? 'review' : 'reviews'}`,\n )}</ia-button\n >.\n </div>\n `;\n }\n\n /**\n * Renders the review form, passing along all necessary properties; this will also render\n * the patron's own review if applicable and/or following submission.\n */\n private get editableCurrentReviewTemplate():\n | HTMLTemplateResult\n | typeof nothing {\n if (!this.displayReviewForm && !this.currentReview) return nothing;\n\n return html`<div class=\"own-review-container\">\n ${!this.displayReviewForm\n ? this.renderReview(this.currentReview)\n : html`<ia-review-form\n .identifier=${this.identifier}\n .oldReview=${this.currentReview}\n .submitterItemname=${this.submitterItemname}\n .submitterScreenname=${this.submitterScreenname}\n .maxSubjectLength=${this.maxSubjectLength}\n .maxBodyLength=${this.maxBodyLength}\n .unrecoverableError=${this.reviewSubmissionError}\n .reviewService=${this.activeReviewService}\n .recaptchaManager=${this.recaptchaActivated\n ? this.recaptchaManager\n : undefined}\n ?bypassRecaptcha=${this.bypassRecaptcha}\n @reviewUpdated=${this.handleReviewUpdate}\n @reviewEditCanceled=${this.handleEditCanceled}\n ></ia-review-form>`}\n </div>`;\n }\n\n /** Sorts the reviews and splits the patron's own review out of the reviews array */\n private sortFilterReviews(): void {\n let patronsOwnReview: Review | undefined;\n const filteredReviews: Review[] = [];\n\n this.reviews.forEach((review) => {\n if (\n !patronsOwnReview &&\n review.reviewer_itemname === this.submitterItemname\n ) {\n patronsOwnReview = review;\n } else filteredReviews.push(review);\n });\n\n this.currentReview = patronsOwnReview;\n this.filteredReviews = this.sortReviews(filteredReviews);\n }\n\n /** Sorts reviews by create date for render */\n private sortReviews(reviews: Review[]): Review[] {\n const sortedReviews = [...reviews].sort((a, b) =>\n a.createdate && b.createdate\n ? new Date(b.createdate).getTime() - new Date(a.createdate).getTime()\n : 0,\n );\n\n return sortedReviews;\n }\n\n /* Renders the given review, using the ia-review component */\n private renderReview(review?: Review): HTMLTemplateResult | typeof nothing {\n if (!review) return nothing;\n\n return html`<ia-review\n .review=${review}\n .identifier=${this.identifier}\n .baseHost=${this.baseHost}\n .reviewService=${this.activeReviewService}\n ?canDelete=${this.canDelete}\n ?bypassTruncation=${this.displayReviewsByDefault}\n ></ia-review>`;\n }\n\n /** Prepare the review form for adding or editing */\n private addEditReview(): void {\n if (!this.bypassRecaptcha) this.recaptchaActivated = true;\n this.displayReviewForm = true;\n }\n\n /** Handles successful review submission */\n private handleReviewUpdate(e: CustomEvent<Review>): void {\n if (!this.currentReview && e.detail) {\n this.dispatchEvent(new CustomEvent<void>('newReviewAdded'));\n this.reviewsCount += 1;\n }\n this.currentReview = e.detail;\n this.displayReviewForm = false;\n }\n\n /** Closes the review form if requested */\n private handleEditCanceled(): void {\n this.displayReviewForm = false;\n\n // Display no-reviews message again if no new review was added\n if (this.reviewsCount === 0) {\n this.displayReviews = false;\n }\n }\n\n static get styles(): CSSResultGroup {\n return [\n css`\n :host {\n font-family: var(\n --ia-font-stack,\n 'Helvetica Neue',\n Helvetica,\n Arial,\n sans-serif\n );\n\n color: var(--ia-text-color, #2c2c2c);\n }\n\n .reviews-list {\n display: flex;\n flex-direction: column;\n gap: 2rem;\n width: 100%;\n }\n\n .own-review-container {\n --error-color: var(--container-error-color, #ea0202);\n --link-color: var(--container-link-color, #4f65f5);\n --container-error-color: #ea0202;\n --container-link-color: #4f65f5;\n --container-bg-color: #fbfbfd;\n --container-border-color: #999999;\n\n border: 2px solid var(--container-border-color, #999999);\n border-radius: 5px;\n background-color: var(--container-bg-color, #fbfbfd);\n padding: 10px;\n }\n\n .message {\n font-weight: 200;\n }\n\n .message ia-button {\n display: inline;\n vertical-align: baseline;\n }\n\n /* The button inside ia-button is a flex box, which would break these\n out of the sentence they sit in. */\n .message ia-button::part(button) {\n display: inline;\n min-height: 0;\n padding: 0;\n font-weight: 600;\n }\n `,\n ];\n }\n}\n"]}
@@ -1,8 +1,23 @@
1
- import type { FetchHandlerInterface } from '@internetarchive/fetch-handler';
1
+ import type { FetchHandlerInterface, FetchOptions } from '@internetarchive/fetch-handler';
2
+ /** A fetch() call the service made */
3
+ export type RecordedFetch = {
4
+ url: string;
5
+ options?: FetchOptions;
6
+ };
2
7
  export declare class MockFetchHandler implements FetchHandlerInterface {
8
+ /** Every fetch() the service made, in order */
9
+ fetches: RecordedFetch[];
10
+ /** What fetch() should hand back. Defaults to a 200 carrying `{ success: true }`. */
11
+ response: () => Response;
3
12
  fetchApiResponse<T>(): Promise<T>;
4
13
  fetchApiPathResponse<T>(): Promise<T>;
5
14
  fetchIAApiResponse<T>(): Promise<T>;
6
- fetch(): Promise<Response>;
15
+ fetch(input: RequestInfo, options?: FetchOptions): Promise<Response>;
16
+ /** The most recent fetch the service made */
17
+ get lastFetch(): RecordedFetch | undefined;
18
+ /** The RequestInit the service handed to the handler */
19
+ get lastRequestInit(): RequestInit;
20
+ /** Body of the last fetch, decoded as url-encoded form params */
21
+ bodyOnLastFetch(): URLSearchParams;
7
22
  }
8
23
  //# sourceMappingURL=mock-fetch-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-fetch-handler.d.ts","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-fetch-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,qBAAa,gBAAiB,YAAW,qBAAqB;IACtD,gBAAgB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAIjC,oBAAoB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAIrC,kBAAkB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAInC,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;CAGjC"}
1
+ {"version":3,"file":"mock-fetch-handler.d.ts","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-fetch-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,YAAY,EACb,MAAM,gCAAgC,CAAC;AAExC,sCAAsC;AACtC,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF,qBAAa,gBAAiB,YAAW,qBAAqB;IAC5D,+CAA+C;IAC/C,OAAO,EAAE,aAAa,EAAE,CAAM;IAE9B,qFAAqF;IACrF,QAAQ,EAAE,MAAM,QAAQ,CAInB;IAEC,gBAAgB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAIjC,oBAAoB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAIrC,kBAAkB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;IAInC,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC;IAK1E,6CAA6C;IAC7C,IAAI,SAAS,IAAI,aAAa,GAAG,SAAS,CAEzC;IAED,wDAAwD;IACxD,IAAI,eAAe,IAAI,WAAW,CAEjC;IAED,iEAAiE;IACjE,eAAe,IAAI,eAAe;CAGnC"}
@@ -1,4 +1,13 @@
1
1
  export class MockFetchHandler {
2
+ constructor() {
3
+ /** Every fetch() the service made, in order */
4
+ this.fetches = [];
5
+ /** What fetch() should hand back. Defaults to a 200 carrying `{ success: true }`. */
6
+ this.response = () => new Response(JSON.stringify({ success: true }), {
7
+ status: 200,
8
+ headers: { 'Content-Type': 'application/json' },
9
+ });
10
+ }
2
11
  async fetchApiResponse() {
3
12
  return { success: true };
4
13
  }
@@ -8,8 +17,21 @@ export class MockFetchHandler {
8
17
  async fetchIAApiResponse() {
9
18
  return {};
10
19
  }
11
- async fetch() {
12
- return new Response();
20
+ async fetch(input, options) {
21
+ this.fetches.push({ url: input, options });
22
+ return this.response();
23
+ }
24
+ /** The most recent fetch the service made */
25
+ get lastFetch() {
26
+ return this.fetches[this.fetches.length - 1];
27
+ }
28
+ /** The RequestInit the service handed to the handler */
29
+ get lastRequestInit() {
30
+ return this.lastFetch?.options?.requestInit ?? {};
31
+ }
32
+ /** Body of the last fetch, decoded as url-encoded form params */
33
+ bodyOnLastFetch() {
34
+ return new URLSearchParams(String(this.lastRequestInit.body ?? ''));
13
35
  }
14
36
  }
15
37
  //# sourceMappingURL=mock-fetch-handler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-fetch-handler.js","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-fetch-handler.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,gBAAgB;IAC3B,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAO,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,OAAO,EAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,EAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,QAAQ,EAAE,CAAC;IACxB,CAAC;CACF","sourcesContent":["import type { FetchHandlerInterface } from '@internetarchive/fetch-handler';\n\nexport class MockFetchHandler implements FetchHandlerInterface {\n async fetchApiResponse<T>(): Promise<T> {\n return { success: true } as T;\n }\n\n async fetchApiPathResponse<T>(): Promise<T> {\n return {} as T;\n }\n\n async fetchIAApiResponse<T>(): Promise<T> {\n return {} as T;\n }\n\n async fetch(): Promise<Response> {\n return new Response();\n }\n}\n"]}
1
+ {"version":3,"file":"mock-fetch-handler.js","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-fetch-handler.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,gBAAgB;IAA7B;QACE,+CAA+C;QAC/C,YAAO,GAAoB,EAAE,CAAC;QAE9B,qFAAqF;QACrF,aAAQ,GAAmB,GAAG,EAAE,CAC9B,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE;YAC9C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;IAiCP,CAAC;IA/BC,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAO,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,OAAO,EAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,EAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAkB,EAAE,OAAsB;QACpD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAe,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED,6CAA6C;IAC7C,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,wDAAwD;IACxD,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,WAAW,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,iEAAiE;IACjE,eAAe;QACb,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;CACF","sourcesContent":["import type {\n FetchHandlerInterface,\n FetchOptions,\n} from '@internetarchive/fetch-handler';\n\n/** A fetch() call the service made */\nexport type RecordedFetch = {\n url: string;\n options?: FetchOptions;\n};\n\nexport class MockFetchHandler implements FetchHandlerInterface {\n /** Every fetch() the service made, in order */\n fetches: RecordedFetch[] = [];\n\n /** What fetch() should hand back. Defaults to a 200 carrying `{ success: true }`. */\n response: () => Response = () =>\n new Response(JSON.stringify({ success: true }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n\n async fetchApiResponse<T>(): Promise<T> {\n return { success: true } as T;\n }\n\n async fetchApiPathResponse<T>(): Promise<T> {\n return {} as T;\n }\n\n async fetchIAApiResponse<T>(): Promise<T> {\n return {} as T;\n }\n\n async fetch(input: RequestInfo, options?: FetchOptions): Promise<Response> {\n this.fetches.push({ url: input as string, options });\n return this.response();\n }\n\n /** The most recent fetch the service made */\n get lastFetch(): RecordedFetch | undefined {\n return this.fetches[this.fetches.length - 1];\n }\n\n /** The RequestInit the service handed to the handler */\n get lastRequestInit(): RequestInit {\n return this.lastFetch?.options?.requestInit ?? {};\n }\n\n /** Body of the last fetch, decoded as url-encoded form params */\n bodyOnLastFetch(): URLSearchParams {\n return new URLSearchParams(String(this.lastRequestInit.body ?? ''));\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { ReviewDeletion, ReviewServiceInterface, ReviewServiceResult, ReviewSubmission } from '../models.js';
2
+ export declare class MockReviewService implements ReviewServiceInterface {
3
+ /** Submissions the form handed over, in order */
4
+ submissions: ReviewSubmission[];
5
+ /** Deletions the review handed over, in order */
6
+ deletions: ReviewDeletion[];
7
+ /** What both operations report back */
8
+ result: ReviewServiceResult;
9
+ submitReview(submission: ReviewSubmission): Promise<ReviewServiceResult>;
10
+ deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult>;
11
+ }
12
+ //# sourceMappingURL=mock-review-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-review-service.d.ts","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-review-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,WAAW,CAAC;AAEnB,qBAAa,iBAAkB,YAAW,sBAAsB;IAC9D,iDAAiD;IACjD,WAAW,EAAE,gBAAgB,EAAE,CAAM;IAErC,iDAAiD;IACjD,SAAS,EAAE,cAAc,EAAE,CAAM;IAEjC,uCAAuC;IACvC,MAAM,EAAE,mBAAmB,CAAqB;IAE1C,YAAY,CAChB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAKzB,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAI3E"}
@@ -0,0 +1,19 @@
1
+ export class MockReviewService {
2
+ constructor() {
3
+ /** Submissions the form handed over, in order */
4
+ this.submissions = [];
5
+ /** Deletions the review handed over, in order */
6
+ this.deletions = [];
7
+ /** What both operations report back */
8
+ this.result = { success: true };
9
+ }
10
+ async submitReview(submission) {
11
+ this.submissions.push(submission);
12
+ return this.result;
13
+ }
14
+ async deleteReview(deletion) {
15
+ this.deletions.push(deletion);
16
+ return this.result;
17
+ }
18
+ }
19
+ //# sourceMappingURL=mock-review-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-review-service.js","sourceRoot":"","sources":["../../../../../src/elements/ia-reviews/mocks/mock-review-service.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,iBAAiB;IAA9B;QACE,iDAAiD;QACjD,gBAAW,GAAuB,EAAE,CAAC;QAErC,iDAAiD;QACjD,cAAS,GAAqB,EAAE,CAAC;QAEjC,uCAAuC;QACvC,WAAM,GAAwB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAalD,CAAC;IAXC,KAAK,CAAC,YAAY,CAChB,UAA4B;QAE5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAwB;QACzC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF","sourcesContent":["import type {\n ReviewDeletion,\n ReviewServiceInterface,\n ReviewServiceResult,\n ReviewSubmission,\n} from '../models';\n\nexport class MockReviewService implements ReviewServiceInterface {\n /** Submissions the form handed over, in order */\n submissions: ReviewSubmission[] = [];\n\n /** Deletions the review handed over, in order */\n deletions: ReviewDeletion[] = [];\n\n /** What both operations report back */\n result: ReviewServiceResult = { success: true };\n\n async submitReview(\n submission: ReviewSubmission,\n ): Promise<ReviewServiceResult> {\n this.submissions.push(submission);\n return this.result;\n }\n\n async deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult> {\n this.deletions.push(deletion);\n return this.result;\n }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ /** A review the patron is submitting, before it's mapped onto wire field names */
2
+ export type ReviewSubmission = {
3
+ /** The IA item being reviewed */
4
+ identifier: string;
5
+ /** Review subject */
6
+ title: string;
7
+ /** Review body */
8
+ body: string;
9
+ /** Star rating, '0' for no rating */
10
+ stars?: string;
11
+ /** ReCaptcha response, when the form ran a challenge */
12
+ recaptchaToken?: string;
13
+ };
14
+ /** The review to remove, identified by its reviewer */
15
+ export type ReviewDeletion = {
16
+ /** The IA item the review belongs to */
17
+ identifier: string;
18
+ /** Reviewer screenname */
19
+ reviewer: string;
20
+ /** Reviewer itemname, used when the reviewer's account is gone */
21
+ reviewerItemname?: string;
22
+ };
23
+ /** The outcome of a review write or delete */
24
+ export type ReviewServiceResult = {
25
+ success: boolean;
26
+ /** A message to show the patron, present when success is false */
27
+ error?: string;
28
+ };
29
+ /**
30
+ * Handles the network side of reviews, so the components only render state.
31
+ */
32
+ export interface ReviewServiceInterface {
33
+ /** Add or update the patron's own review of an item */
34
+ submitReview(submission: ReviewSubmission): Promise<ReviewServiceResult>;
35
+ /** Remove a review, which the backend allows only for review moderators */
36
+ deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult>;
37
+ }
38
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/models.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IAEd,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IAEb,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG;IAC3B,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IAEjB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,8CAA8C;AAC9C,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAC;IAEjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,YAAY,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEzE,2EAA2E;IAC3E,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACtE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/models.ts"],"names":[],"mappings":"","sourcesContent":["/** A review the patron is submitting, before it's mapped onto wire field names */\nexport type ReviewSubmission = {\n /** The IA item being reviewed */\n identifier: string;\n\n /** Review subject */\n title: string;\n\n /** Review body */\n body: string;\n\n /** Star rating, '0' for no rating */\n stars?: string;\n\n /** ReCaptcha response, when the form ran a challenge */\n recaptchaToken?: string;\n};\n\n/** The review to remove, identified by its reviewer */\nexport type ReviewDeletion = {\n /** The IA item the review belongs to */\n identifier: string;\n\n /** Reviewer screenname */\n reviewer: string;\n\n /** Reviewer itemname, used when the reviewer's account is gone */\n reviewerItemname?: string;\n};\n\n/** The outcome of a review write or delete */\nexport type ReviewServiceResult = {\n success: boolean;\n\n /** A message to show the patron, present when success is false */\n error?: string;\n};\n\n/**\n * Handles the network side of reviews, so the components only render state.\n */\nexport interface ReviewServiceInterface {\n /** Add or update the patron's own review of an item */\n submitReview(submission: ReviewSubmission): Promise<ReviewServiceResult>;\n\n /** Remove a review, which the backend allows only for review moderators */\n deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult>;\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import type { FetchHandlerInterface } from '@internetarchive/fetch-handler';
2
+ import type { ReviewDeletion, ReviewServiceInterface, ReviewServiceResult, ReviewSubmission } from './models.js';
3
+ export type ReviewServiceOptions = {
4
+ /**
5
+ * Handles retries and the CSRF token.
6
+ *
7
+ * Writing and deleting need a handler built with a `getCsrfToken` source, since that is what
8
+ * supplies the `X-CSRF-Token` header these endpoints require. The token resolves per request,
9
+ * so it doesn't have to be known when the service is constructed.
10
+ */
11
+ fetchHandler?: FetchHandlerInterface;
12
+ /** Origin for the endpoints. Empty string keeps requests same-origin. */
13
+ baseHost?: string;
14
+ /** Path a review submission POSTs to */
15
+ submitPath?: string;
16
+ /** Path a review deletion goes to */
17
+ deletePath?: string;
18
+ /** Verb the delete endpoint expects */
19
+ deleteMethod?: 'POST' | 'DELETE';
20
+ };
21
+ /**
22
+ * Talks to the archive.org review endpoints.
23
+ *
24
+ * The CSRF token is left to the fetch handler: both operations opt into its automatic
25
+ * `X-CSRF-Token` header, which resolves the token per request.
26
+ */
27
+ export declare class ReviewService implements ReviewServiceInterface {
28
+ private fetchHandler;
29
+ private baseHost;
30
+ private submitPath;
31
+ private deletePath;
32
+ private deleteMethod;
33
+ constructor(options?: ReviewServiceOptions);
34
+ /** @inheritdoc */
35
+ submitReview(submission: ReviewSubmission): Promise<ReviewServiceResult>;
36
+ /** @inheritdoc */
37
+ deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult>;
38
+ /**
39
+ * Sends a credentialed request and reads the `{ success, error }` result out of it.
40
+ */
41
+ private request;
42
+ /**
43
+ * Reads a result out of a response.
44
+ *
45
+ * The status decides the outcome, so a rejected request can't read as a success. fetch
46
+ * resolves on a 4xx or 5xx, so that check can't be left to the catch. A JSON body supplies
47
+ * the patron-facing message; the legacy delete endpoint answers with an HTML page instead,
48
+ * and there the status is all there is to go on.
49
+ */
50
+ private parseResult;
51
+ private get genericError();
52
+ }
53
+ //# sourceMappingURL=review-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-service.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/review-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,UAAU,CAAC;AAElB,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAClC,CAAC;AAMF;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,sBAAsB;IAC1D,OAAO,CAAC,YAAY,CAAwB;IAE5C,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,YAAY,CAAoB;gBAE5B,OAAO,CAAC,EAAE,oBAAoB;IAQ1C,kBAAkB;IACZ,YAAY,CAChB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IAoB/B,kBAAkB;IACZ,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAe1E;;OAEG;YACW,OAAO;IAqBrB;;;;;;;OAOG;YACW,WAAW;IAiBzB,OAAO,KAAK,YAAY,GAEvB;CACF"}
@@ -0,0 +1,97 @@
1
+ import { msg } from '@lit/localize';
2
+ import { IaFetchHandler } from '@internetarchive/fetch-handler';
3
+ /** The endpoints the legacy details page serves */
4
+ const LEGACY_SUBMIT_PATH = '/write-review.php';
5
+ const LEGACY_DELETE_PATH = '/edit-reviews.php';
6
+ /**
7
+ * Talks to the archive.org review endpoints.
8
+ *
9
+ * The CSRF token is left to the fetch handler: both operations opt into its automatic
10
+ * `X-CSRF-Token` header, which resolves the token per request.
11
+ */
12
+ export class ReviewService {
13
+ constructor(options) {
14
+ this.fetchHandler = options?.fetchHandler ?? new IaFetchHandler();
15
+ this.baseHost = options?.baseHost ?? 'https://archive.org';
16
+ this.submitPath = options?.submitPath ?? LEGACY_SUBMIT_PATH;
17
+ this.deletePath = options?.deletePath ?? LEGACY_DELETE_PATH;
18
+ this.deleteMethod = options?.deleteMethod ?? 'POST';
19
+ }
20
+ /** @inheritdoc */
21
+ async submitReview(submission) {
22
+ const body = new URLSearchParams();
23
+ body.append('identifier', submission.identifier);
24
+ body.append('field_reviewtitle', submission.title);
25
+ body.append('field_reviewbody', submission.body);
26
+ body.append('field_stars', submission.stars ?? '0');
27
+ if (submission.recaptchaToken) {
28
+ body.append('g-recaptcha-response', submission.recaptchaToken);
29
+ }
30
+ // tells write-review.php the post came from the form rather than a direct visit
31
+ body.append('submitter', 'review-form');
32
+ return this.request(`${this.baseHost}${this.submitPath}`, {
33
+ method: 'POST',
34
+ body,
35
+ });
36
+ }
37
+ /** @inheritdoc */
38
+ async deleteReview(deletion) {
39
+ // Screennames can contain spaces and ampersands, so the parameters are
40
+ // encoded rather than interpolated straight into the query string.
41
+ const params = new URLSearchParams();
42
+ params.append('identifier', deletion.identifier);
43
+ params.append('deleteReviewer', deletion.reviewer);
44
+ if (deletion.reviewerItemname) {
45
+ params.append('deleteReviewerItemname', deletion.reviewerItemname);
46
+ }
47
+ const url = `${this.baseHost}${this.deletePath}?${params.toString()}`;
48
+ return this.request(url, { method: this.deleteMethod });
49
+ }
50
+ /**
51
+ * Sends a credentialed request and reads the `{ success, error }` result out of it.
52
+ */
53
+ async request(url, init) {
54
+ try {
55
+ const response = await this.fetchHandler.fetch(url, {
56
+ requestInit: {
57
+ method: init.method,
58
+ body: init.body,
59
+ credentials: 'include',
60
+ },
61
+ includeCsrfToken: true,
62
+ });
63
+ return await this.parseResult(response);
64
+ }
65
+ catch (e) {
66
+ console.error('Review request failed', e);
67
+ return { success: false, error: this.genericError };
68
+ }
69
+ }
70
+ /**
71
+ * Reads a result out of a response.
72
+ *
73
+ * The status decides the outcome, so a rejected request can't read as a success. fetch
74
+ * resolves on a 4xx or 5xx, so that check can't be left to the catch. A JSON body supplies
75
+ * the patron-facing message; the legacy delete endpoint answers with an HTML page instead,
76
+ * and there the status is all there is to go on.
77
+ */
78
+ async parseResult(response) {
79
+ let payload;
80
+ try {
81
+ payload = (await response.json());
82
+ }
83
+ catch {
84
+ payload = undefined;
85
+ }
86
+ if (!response.ok) {
87
+ return { success: false, error: payload?.error ?? this.genericError };
88
+ }
89
+ if (!payload || payload.success)
90
+ return { success: true };
91
+ return { success: false, error: payload.error ?? this.genericError };
92
+ }
93
+ get genericError() {
94
+ return msg('Sorry, something went wrong. Please try again later.');
95
+ }
96
+ }
97
+ //# sourceMappingURL=review-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-service.js","sourceRoot":"","sources":["../../../../src/elements/ia-reviews/review-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { msg } from '@lit/localize';\nimport { IaFetchHandler } from '@internetarchive/fetch-handler';\nimport type { FetchHandlerInterface } from '@internetarchive/fetch-handler';\n\nimport type {\n ReviewDeletion,\n ReviewServiceInterface,\n ReviewServiceResult,\n ReviewSubmission,\n} from './models';\n\nexport type ReviewServiceOptions = {\n /**\n * Handles retries and the CSRF token.\n *\n * Writing and deleting need a handler built with a `getCsrfToken` source, since that is what\n * supplies the `X-CSRF-Token` header these endpoints require. The token resolves per request,\n * so it doesn't have to be known when the service is constructed.\n */\n fetchHandler?: FetchHandlerInterface;\n\n /** Origin for the endpoints. Empty string keeps requests same-origin. */\n baseHost?: string;\n\n /** Path a review submission POSTs to */\n submitPath?: string;\n\n /** Path a review deletion goes to */\n deletePath?: string;\n\n /** Verb the delete endpoint expects */\n deleteMethod?: 'POST' | 'DELETE';\n};\n\n/** The endpoints the legacy details page serves */\nconst LEGACY_SUBMIT_PATH = '/write-review.php';\nconst LEGACY_DELETE_PATH = '/edit-reviews.php';\n\n/**\n * Talks to the archive.org review endpoints.\n *\n * The CSRF token is left to the fetch handler: both operations opt into its automatic\n * `X-CSRF-Token` header, which resolves the token per request.\n */\nexport class ReviewService implements ReviewServiceInterface {\n private fetchHandler: FetchHandlerInterface;\n\n private baseHost: string;\n\n private submitPath: string;\n\n private deletePath: string;\n\n private deleteMethod: 'POST' | 'DELETE';\n\n constructor(options?: ReviewServiceOptions) {\n this.fetchHandler = options?.fetchHandler ?? new IaFetchHandler();\n this.baseHost = options?.baseHost ?? 'https://archive.org';\n this.submitPath = options?.submitPath ?? LEGACY_SUBMIT_PATH;\n this.deletePath = options?.deletePath ?? LEGACY_DELETE_PATH;\n this.deleteMethod = options?.deleteMethod ?? 'POST';\n }\n\n /** @inheritdoc */\n async submitReview(\n submission: ReviewSubmission,\n ): Promise<ReviewServiceResult> {\n const body = new URLSearchParams();\n body.append('identifier', submission.identifier);\n body.append('field_reviewtitle', submission.title);\n body.append('field_reviewbody', submission.body);\n body.append('field_stars', submission.stars ?? '0');\n\n if (submission.recaptchaToken) {\n body.append('g-recaptcha-response', submission.recaptchaToken);\n }\n\n // tells write-review.php the post came from the form rather than a direct visit\n body.append('submitter', 'review-form');\n\n return this.request(`${this.baseHost}${this.submitPath}`, {\n method: 'POST',\n body,\n });\n }\n\n /** @inheritdoc */\n async deleteReview(deletion: ReviewDeletion): Promise<ReviewServiceResult> {\n // Screennames can contain spaces and ampersands, so the parameters are\n // encoded rather than interpolated straight into the query string.\n const params = new URLSearchParams();\n params.append('identifier', deletion.identifier);\n params.append('deleteReviewer', deletion.reviewer);\n if (deletion.reviewerItemname) {\n params.append('deleteReviewerItemname', deletion.reviewerItemname);\n }\n\n const url = `${this.baseHost}${this.deletePath}?${params.toString()}`;\n\n return this.request(url, { method: this.deleteMethod });\n }\n\n /**\n * Sends a credentialed request and reads the `{ success, error }` result out of it.\n */\n private async request(\n url: string,\n init: { method: string; body?: BodyInit },\n ): Promise<ReviewServiceResult> {\n try {\n const response = await this.fetchHandler.fetch(url, {\n requestInit: {\n method: init.method,\n body: init.body,\n credentials: 'include',\n },\n includeCsrfToken: true,\n });\n\n return await this.parseResult(response);\n } catch (e) {\n console.error('Review request failed', e);\n return { success: false, error: this.genericError };\n }\n }\n\n /**\n * Reads a result out of a response.\n *\n * The status decides the outcome, so a rejected request can't read as a success. fetch\n * resolves on a 4xx or 5xx, so that check can't be left to the catch. A JSON body supplies\n * the patron-facing message; the legacy delete endpoint answers with an HTML page instead,\n * and there the status is all there is to go on.\n */\n private async parseResult(response: Response): Promise<ReviewServiceResult> {\n let payload: ReviewServiceResult | undefined;\n try {\n payload = (await response.json()) as ReviewServiceResult;\n } catch {\n payload = undefined;\n }\n\n if (!response.ok) {\n return { success: false, error: payload?.error ?? this.genericError };\n }\n\n if (!payload || payload.success) return { success: true };\n\n return { success: false, error: payload.error ?? this.genericError };\n }\n\n private get genericError(): string {\n return msg('Sorry, something went wrong. Please try again later.');\n }\n}\n"]}
@@ -29,6 +29,8 @@ export * from './ia-radio-player/search/transcript-index.js';
29
29
  export * from './ia-reviews/ia-review.js';
30
30
  export * from './ia-reviews/ia-review-form.js';
31
31
  export * from './ia-reviews/ia-reviews.js';
32
+ export * from './ia-reviews/models.js';
33
+ export * from './ia-reviews/review-service.js';
32
34
  export * from './ia-scrubber-bar/ia-scrubber-bar.js';
33
35
  export * from './ia-scrubber-bar/ia-section-marker.js';
34
36
  export * from './ia-scrubber-bar/models.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qDAAqD,CAAC;AACpE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mDAAmD,CAAC;AAClE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0BAA0B,CAAC;AACzC,cAAc,4DAA4D,CAAC;AAC3E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qDAAqD,CAAC;AACpE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mDAAmD,CAAC;AAClE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0BAA0B,CAAC;AACzC,cAAc,4DAA4D,CAAC;AAC3E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC"}
@@ -29,6 +29,8 @@ export * from './ia-radio-player/search/transcript-index.js';
29
29
  export * from './ia-reviews/ia-review.js';
30
30
  export * from './ia-reviews/ia-review-form.js';
31
31
  export * from './ia-reviews/ia-reviews.js';
32
+ export * from './ia-reviews/models.js';
33
+ export * from './ia-reviews/review-service.js';
32
34
  export * from './ia-scrubber-bar/ia-scrubber-bar.js';
33
35
  export * from './ia-scrubber-bar/ia-section-marker.js';
34
36
  export * from './ia-scrubber-bar/models.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qDAAqD,CAAC;AACpE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mDAAmD,CAAC;AAClE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0BAA0B,CAAC;AACzC,cAAc,4DAA4D,CAAC;AAC3E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC","sourcesContent":["export * from './ia-audio-element/ia-audio-element';\nexport * from './ia-audio-element/models';\nexport * from './ia-button/ia-button';\nexport * from './ia-combo-box/ia-combo-box';\nexport * from './ia-dropdown-search-bar/ia-dropdown-search-bar';\nexport * from './ia-expandable-search-bar/ia-expandable-search-bar';\nexport * from './ia-expandable-search-bar/ia-quick-search';\nexport * from './ia-expandable-search-bar/models';\nexport * from './ia-histogram-date-range/ia-histogram-date-range';\nexport * from './ia-histogram-date-range/models';\nexport * from './ia-image-viewer/ia-image-viewer';\nexport * from './ia-item-navigator/ia-item-navigator';\nexport * from './ia-item-navigator/menus/ia-itemnav-viewable-files-panel';\nexport * from './ia-item-navigator/menus/ia-itemnav-sort-files-button';\nexport * from './ia-item-navigator/menus/ia-itemnav-share-panel';\nexport * from './ia-playback-controls/ia-playback-controls';\nexport * from './ia-playback-controls/models';\nexport * from './ia-radio-player/ia-radio-player';\nexport * from './ia-radio-player/ia-search-results-switcher';\nexport * from './ia-radio-player/models';\nexport * from './ia-radio-player/search/backends/full-text-search-backend';\nexport * from './ia-radio-player/search/backends/full-text-search-response';\nexport * from './ia-radio-player/search/backends/local-search-backend';\nexport * from './ia-radio-player/search/backends/search-backend';\nexport * from './ia-radio-player/search/models';\nexport * from './ia-radio-player/search/search-handler';\nexport * from './ia-radio-player/search/search-helper';\nexport * from './ia-radio-player/search/transcript-index';\nexport * from './ia-reviews/ia-review';\nexport * from './ia-reviews/ia-review-form';\nexport * from './ia-reviews/ia-reviews';\nexport * from './ia-scrubber-bar/ia-scrubber-bar';\nexport * from './ia-scrubber-bar/ia-section-marker';\nexport * from './ia-scrubber-bar/models';\nexport * from './ia-transcript-view/duration-formatter';\nexport * from './ia-transcript-view/ia-transcript-entry';\nexport * from './ia-transcript-view/ia-transcript-view';\nexport * from './ia-transcript-view/models';\nexport * from './ia-waveform-progress/ia-waveform-progress';\nexport * from './ia-waveform-progress/models';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iDAAiD,CAAC;AAChE,cAAc,qDAAqD,CAAC;AACpE,cAAc,4CAA4C,CAAC;AAC3D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mDAAmD,CAAC;AAClE,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,0BAA0B,CAAC;AACzC,cAAc,4DAA4D,CAAC;AAC3E,cAAc,6DAA6D,CAAC;AAC5E,cAAc,wDAAwD,CAAC;AACvE,cAAc,kDAAkD,CAAC;AACjE,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yCAAyC,CAAC;AACxD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,+BAA+B,CAAC","sourcesContent":["export * from './ia-audio-element/ia-audio-element';\nexport * from './ia-audio-element/models';\nexport * from './ia-button/ia-button';\nexport * from './ia-combo-box/ia-combo-box';\nexport * from './ia-dropdown-search-bar/ia-dropdown-search-bar';\nexport * from './ia-expandable-search-bar/ia-expandable-search-bar';\nexport * from './ia-expandable-search-bar/ia-quick-search';\nexport * from './ia-expandable-search-bar/models';\nexport * from './ia-histogram-date-range/ia-histogram-date-range';\nexport * from './ia-histogram-date-range/models';\nexport * from './ia-image-viewer/ia-image-viewer';\nexport * from './ia-item-navigator/ia-item-navigator';\nexport * from './ia-item-navigator/menus/ia-itemnav-viewable-files-panel';\nexport * from './ia-item-navigator/menus/ia-itemnav-sort-files-button';\nexport * from './ia-item-navigator/menus/ia-itemnav-share-panel';\nexport * from './ia-playback-controls/ia-playback-controls';\nexport * from './ia-playback-controls/models';\nexport * from './ia-radio-player/ia-radio-player';\nexport * from './ia-radio-player/ia-search-results-switcher';\nexport * from './ia-radio-player/models';\nexport * from './ia-radio-player/search/backends/full-text-search-backend';\nexport * from './ia-radio-player/search/backends/full-text-search-response';\nexport * from './ia-radio-player/search/backends/local-search-backend';\nexport * from './ia-radio-player/search/backends/search-backend';\nexport * from './ia-radio-player/search/models';\nexport * from './ia-radio-player/search/search-handler';\nexport * from './ia-radio-player/search/search-helper';\nexport * from './ia-radio-player/search/transcript-index';\nexport * from './ia-reviews/ia-review';\nexport * from './ia-reviews/ia-review-form';\nexport * from './ia-reviews/ia-reviews';\nexport * from './ia-reviews/models';\nexport * from './ia-reviews/review-service';\nexport * from './ia-scrubber-bar/ia-scrubber-bar';\nexport * from './ia-scrubber-bar/ia-section-marker';\nexport * from './ia-scrubber-bar/models';\nexport * from './ia-transcript-view/duration-formatter';\nexport * from './ia-transcript-view/ia-transcript-entry';\nexport * from './ia-transcript-view/ia-transcript-view';\nexport * from './ia-transcript-view/models';\nexport * from './ia-waveform-progress/ia-waveform-progress';\nexport * from './ia-waveform-progress/models';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/elements",
3
- "version": "1.1.3-webdev-9107.0",
3
+ "version": "1.1.3-webdev-8840.1",
4
4
  "description": "A web component library from the Internet Archive.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "types": "./dist/src/elements/index.d.ts",