@playpilot/tpi 5.24.2 → 5.24.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "5.24.2",
3
+ "version": "5.24.4",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,7 +1,7 @@
1
1
  import { mount, unmount } from 'svelte'
2
2
  import TitlePopover from '../routes/components/TitlePopover.svelte'
3
3
  import AfterArticlePlaylinks from '../routes/components/Playlinks/AfterArticlePlaylinks.svelte'
4
- import { cleanPhrase, findNumberOfMatchesInString, findShortestMatchBetweenPhrases, findTextNodeContaining, getIndexOfPhraseInElement, getIndexOfPhraseInBoundary, getNumberOfLeadingAndTrailingSpaces, isNodeInLink, replaceBetween, replaceStartingFrom } from './text'
4
+ import { cleanPhrase, findNumberOfMatchesInString, findShortestMatchBetweenPhrases, findTextNodeContaining, getIndexOfPhraseInElement, getIndexOfPhraseInBoundary, getNumberOfLeadingAndTrailingSpaces, isNodeInLink, replaceBetween, replaceStartingFrom, findSurroundingPhrases } from './text'
5
5
  import type { LinkInjection, LinkInjectionTypes } from './types/injection'
6
6
  import { isHoldingSpecialKey } from './event'
7
7
  import { playFallbackViewTransition } from './viewTransition'
@@ -158,7 +158,6 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
158
158
  }
159
159
 
160
160
  const { injectionElement, linkElement } = createLinkInjectionElement(injection)
161
- const { phrase_before, phrase_after } = injection
162
161
 
163
162
  let replacementIndex = -1
164
163
  let hasBeenReplaced = false
@@ -167,11 +166,15 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
167
166
  // Check if there is only one occurance in the element, in which case the replacement is simple
168
167
  // It's important that we check against the phrase without cleanPhrase(), as we need to check if an
169
168
  // element might contain attributes that contain the phrase.
170
- const numberOfMatches = findNumberOfMatchesInString(element.innerHTML, injection.title)
171
- if (numberOfMatches === 1) replacementIndex = element.innerHTML.indexOf(injection.title)
169
+ const numberOfHtmlMatches = findNumberOfMatchesInString(element.innerHTML, injection.title)
170
+ if (numberOfHtmlMatches === 1) replacementIndex = element.innerHTML.indexOf(injection.title)
172
171
 
173
172
  // !! Option 2 - Replace by phrase_before and phrase_after
174
- // If multiple or no occurences were found, we use phrase_before and phrase_after to refine the search
173
+ // If multiple or no occurences were found, we use the phrases before and after the injection to find
174
+ // the location of the correct title. This helps with multiple occurrences of the same phrase, but also
175
+ // with text that is broken up by html elements.
176
+ const { phrase_before, phrase_after } = getPhrasesSurroundingInjection(element, injection)
177
+
175
178
  if (replacementIndex === -1 && (phrase_before || phrase_after)) {
176
179
  // The before and after phrase are combined to see if the sentence contains the match exactly.
177
180
  // This is a fairly simple comparison that will fail on special characters, html tags, or inconsistencies
@@ -202,7 +205,8 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
202
205
 
203
206
  // !! Option 3 - Replace by title only, taking previous injections into account
204
207
  // If no occurences were found previously, we check the element from start to finish only checking for the title,
205
- // without considering phrase_before and phrase_after
208
+ // without considering phrase_before and phrase_after. Here we check the index of multiple different types of matches
209
+ // and go with the highest index between them all, assuming that is always going to be the correct phrase.
206
210
  if (replacementIndex === -1 && nodeContainingText?.nodeValue) {
207
211
  // Start searching for injection from either the value, the sentence, or the occurrence. This prevents injecting into
208
212
  // text in an element earlier than the sentence started. A element might contain many sentences, after all.
@@ -305,28 +309,55 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
305
309
  /**
306
310
  * In some coses injections lead to broken HTML. The reason for this varies and is hard to figure out. In any case, we
307
311
  * should never serve broken HTML.
308
- * Before replacing the HTML, we apply the the replacement to a dummy element. From here we check if the number of links
309
- * in the HTML increased by one. If it didn't, something went wrong. What exactly is hard to say, we just know something
310
- * didn't go as intended and we should not inject.
312
+ * Before replacing the HTML, we apply the the replacement to a dummy element. From here we check if the length of the text has
313
+ * increased. If it did, something went wrong. What exactly is hard to say, we just know something didn't go as intended and
314
+ * we should not inject.
311
315
  *
312
- * No tests exists for this function because writing tests would require finding a scenario in which things break. If we
313
- * knew when things break we'd fix it instead.
316
+ * No tests exists for this function because writing tests would require finding a scenario in which things break. If we knew
317
+ * when things break we'd fix it instead.
314
318
  */
315
319
  function replaceIfSafeInjection(originalHtml: string, phrase: string, sentenceElement: HTMLElement, injectionElement: HTMLElement, replacementIndex: number): boolean {
316
320
  const dummyElement = document.createElement('div')
317
321
  dummyElement.innerHTML = originalHtml
318
322
 
319
- const startingNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
323
+ const originalText = dummyElement.innerText
320
324
  dummyElement.innerHTML = replaceStartingFrom(originalHtml, phrase, injectionElement.outerHTML, replacementIndex)
321
- const finalNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
322
325
 
323
- if (finalNumberOfLinks != startingNumberOfLinks + 1) return false
326
+ // If the text has changed at all, something probably went wrong as the new text is supposed to be the same as the old.
327
+ if (Math.abs(dummyElement.innerText.length - originalText.length) > 1) return false
324
328
 
325
329
  sentenceElement.innerHTML = dummyElement.innerHTML
326
330
 
327
331
  return true
328
332
  }
329
333
 
334
+ /**
335
+ * phrase_before and phrase_after are set for manual injections to better get their placement within a sentence.
336
+ * This helps when an injection contains multiple injections of the same word and it also helps when an injection
337
+ * is broken up by html elements. For instance, DigitalSpy sometimes uses multiple styling tags on the same phrase.
338
+ * Something like `<strong>phr</strong><strong>ase</strong.
339
+ *
340
+ * Secondary to manual injections we can also get the phrase_before and phrase_after for ai injections.
341
+ * This only works if the injection occurs only once. In this case it is only used when the phrase is broken up
342
+ * by html elements, like before.
343
+ */
344
+ function getPhrasesSurroundingInjection(element: HTMLElement, injection: LinkInjection): { phrase_before: string | null | undefined, phrase_after: string | null | undefined } {
345
+ const { phrase_before, phrase_after } = injection
346
+
347
+ if (phrase_before || phrase_after) return { phrase_before, phrase_after }
348
+
349
+ // Get the number of occurrences of the same phrase in the element. We can only use the before and after
350
+ // phrases if there is only 1 occurrence, as we can't guarentee we injection into the expected phrase otherwise.
351
+ const numberOfTextMatches = findNumberOfMatchesInString(element.innerText, injection.title)
352
+
353
+ if (numberOfTextMatches !== 1) return { phrase_before: null, phrase_after: null }
354
+
355
+ const startIndex = element.innerText.indexOf(injection.title)
356
+ const surroundingPhrases = findSurroundingPhrases(element, startIndex, startIndex + injection.title.length)
357
+
358
+ return { phrase_before: surroundingPhrases.before, phrase_after: surroundingPhrases.after }
359
+ }
360
+
330
361
  /**
331
362
  * Add all used CSS variables to a data attribute. This data attribute is then used for selectors that for each
332
363
  * individual CSS variable. This is done this way so that CSS variables are only set when they are used.
@@ -59,7 +59,7 @@
59
59
  <time datetime="14:00">1 hour ago</time>
60
60
  </header>
61
61
 
62
- <p>Following the success of John M. Chu's 2018 romantic-comedy Crazy Rich Asians, Quan was inspired to return to acting. He first scored a supporting role in the Netflix movie Finding 'Ohana, before securing a starring role in the absurdist comedy-drama Everything Everywhere all At Once. A critical and commercial success, the film earned $143 million against a budget of $14-25 million, and saw Quan win the Academy Award for Best Supporting Actor. Following his win, Quan struggled to choose projects he was satisfied with, passing on an action-comedy three times, before finally taking his first leading role in it, following advice from Spielberg.</p>
62
+ <p>Following the success of John M. Chu's 2018 romantic-comedy <strong>Crazy</strong> Rich Asians, Quan was inspired to return to acting. He first scored a supporting role in the Netflix movie Finding 'Ohana, before securing a starring role in the absurdist comedy-drama Everything Everywhere all At Once. A critical and commercial success, the film earned $143 million against a budget of $14-25 million, and saw Quan win the Academy Award for Best Supporting Actor. Following his win, Quan struggled to choose projects he was satisfied with, passing on an action-comedy three times, before finally taking his first leading role in it, following advice from Spielberg.</p>
63
63
 
64
64
  <h2>A smaller heading, possibly with an injection in it</h2>
65
65
  <p>In an interview with Epire &amp; Magazine, Quan reveals he quested starring in Love Hurts, which sees him Love Hurts in the leading role of a former assassin turned successful realtor, whose past returns when his brother attempts to hunt him down. The movie is in a similar vein to successful films such as The Long Kiss Goodnight and Nobody, and Quan discussed how he was reluctant to take the part due to his conditioned beliefs about how an action hero should look. But he reveals that he changed his mind following a meeting with Spielberg, who convinced him to do it.</p>
@@ -194,7 +194,7 @@
194
194
  margin: 0 margin(0.5) 0 auto;
195
195
  padding-left: margin(0.5);
196
196
  align-self: center;
197
- color: var(--playpilot-list-item-action-color, var(--playpilot-detail-text-color, var(--playpilot-text-color-alt)));
197
+ color: var(--playpilot-list-item-action-color, var(--playpilot-detail-text-color, rgba(255, 255, 255, 0.75)));
198
198
 
199
199
  &:hover,
200
200
  &:active {
@@ -20,11 +20,11 @@
20
20
  <style lang="scss">
21
21
  .playlink {
22
22
  display: inline-block;
23
- background: var(--playpilot-playlink-label-background, var(--playpilot-genre-background, var(--playpilot-lighter)));
23
+ background: var(--playpilot-playlink-label-background, var(--playpilot-genre-background, var(--playpilot-content)));
24
24
  border-radius: var(--playpilot-playlink-label-border-radius, margin(0.25));
25
25
  padding: margin(0.25) margin(0.5);
26
26
  font-size: var(--playpilot-playlink-label-font-size, 12px);
27
- color: var(--playpilot-playlink-label-text-color, var(--playpilot-detail-text-color, var(--playpilot-text-color-alt))) !important;
27
+ color: var(--playpilot-playlink-label-text-color, var(--playpilot-detail-text-color-alt, rgba(255, 255, 255, 0.75))) !important;
28
28
  text-decoration: none;
29
29
  font-style: normal !important;
30
30
  overflow: hidden;
@@ -852,6 +852,17 @@ describe('linkInjection.js', () => {
852
852
  expect(document.querySelector('a')?.innerText).toBe(injection.title)
853
853
  })
854
854
 
855
+ it('Should properly inject into titles that are split up by multiple elements when neither phrase_before or phrase_after are given but the phrase occurs only once', () => {
856
+ document.body.innerHTML = '<section>Some text with a <strong>phra</strong><strong>se</strong> in it</section>'
857
+
858
+ const elements = getLinkInjectionElements(/** @type {HTMLElement} */ (document.querySelector('section')))
859
+ const injection = generateInjection('Some text with a phrase in it', 'phrase')
860
+
861
+ injectLinksInDocument(elements, { aiInjections: [], manualInjections: [injection] })
862
+
863
+ expect(document.querySelector('a')?.innerText).toBe(injection.title)
864
+ })
865
+
855
866
  it('Should replace existing PlayPilot links', () => {
856
867
  document.body.innerHTML = '<p>This is a sentence with <a href="https://playpilot.com/movie/123">an injection</a>.</p>'
857
868