Skip to content

Commit 51410f7

Browse files
authored
feat: introduce blink parser and replace insertion type (DAP-4712) (#10)
1 parent 9639a7c commit 51410f7

File tree

9 files changed

+93
-2
lines changed

9 files changed

+93
-2
lines changed

libs/core/src/adapters/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export enum InsertionType {
55
After = 'after',
66
Begin = 'begin',
77
End = 'end',
8+
Replace = 'replace',
89
}
910

1011
export interface IAdapter {

libs/core/src/core.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DynamicHtmlAdapter } from './adapters/dynamic-html-adapter'
44
import { JsonParser } from './parsers/json-parser'
55
import { BosParser } from './parsers/bos-parser'
66
import { MutableWebParser } from './parsers/mweb-parser'
7+
import { LinkParser } from './parsers/link-parser'
78
import { AdapterType, ParserConfig } from './types'
89

910
export class Core {
@@ -93,6 +94,9 @@ export class Core {
9394
new MutableWebParser()
9495
)
9596

97+
case AdapterType.Link:
98+
return new DynamicHtmlAdapter(document.body, this._treeBuilder, config.id, new LinkParser())
99+
96100
default:
97101
throw new Error('Incompatible adapter type')
98102
}

libs/core/src/parsers/bos-parser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export class BosParser implements IParser {
6161

6262
const result: { element: HTMLElement; contextName: string }[] = []
6363

64+
// ToDo: maybe querySelectorAll('.foo:not(.foo .foo)') is faster?
6465
for (const childContextName of contextConfig.children ?? []) {
6566
const childConfig = this.config.contexts[childContextName]
6667
if (!childConfig.component) continue
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { IParser, InsertionPoint } from './interface'
2+
3+
function tryParseUrl(text: string): URL | null {
4+
try {
5+
return new URL(text)
6+
} catch (_) {
7+
return null
8+
}
9+
}
10+
11+
export class LinkParser implements IParser {
12+
parseContext({ href, innerText }: HTMLAnchorElement) {
13+
// ToDo: twitter-specific logic. Twitter adds '…' to the end of the url
14+
const cleanUrl = innerText.replace(/$/g, '')
15+
16+
return {
17+
id: href,
18+
href: href,
19+
innerHref: tryParseUrl(cleanUrl)?.href,
20+
innerText: innerText,
21+
}
22+
}
23+
24+
findChildElements(element: HTMLElement) {
25+
// Using <a> inside of <a> is not valid, so only one level of nesting is possible
26+
if (element.tagName === 'A') return []
27+
28+
return Array.from(element.querySelectorAll('a')).map((element) => ({
29+
element,
30+
contextName: 'link',
31+
}))
32+
}
33+
34+
findInsertionPoint(): HTMLElement | null {
35+
// No insertions points in blinks
36+
return null
37+
}
38+
39+
getInsertionPoints(): InsertionPoint[] {
40+
// No insertions points in blinks
41+
return []
42+
}
43+
}

libs/core/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ export enum AdapterType {
66
Microdata = 'microdata',
77
Json = 'json',
88
MWeb = 'mweb',
9+
Link = 'link',
910
}
1011

1112
export type ParserConfig =
1213
| ({ parserType: AdapterType.Json; id: string } & JsonParserConfig)
1314
| ({ parserType: AdapterType.Bos; id: string } & BosParserConfig)
1415
| { parserType: AdapterType.MWeb; id: string }
16+
| { parserType: AdapterType.Link; id: string }

libs/engine/src/app/contexts/mutable-web-context/mutable-web-provider.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,26 @@ const MWebParserConfig: ParserConfig = {
4040
],
4141
}
4242

43+
const LinkParserConfig: ParserConfig = {
44+
parserType: AdapterType.Link,
45+
id: 'engine', // ToDo: id used as namespace
46+
targets: [
47+
{
48+
namespace: 'engine',
49+
contextType: 'website',
50+
if: { id: { not: null } },
51+
},
52+
],
53+
}
54+
4355
const MutableWebProvider: FC<Props> = ({ config, defaultMutationId, modalApi, children }) => {
4456
const { tree, attachParserConfig, detachParserConfig, updateRootContext } = useCore()
4557
const engineRef = useRef<Engine | null>(null)
4658

4759
if (!engineRef.current) {
4860
engineRef.current = new Engine(config)
4961
attachParserConfig(MWebParserConfig) // ToDo: move
62+
attachParserConfig(LinkParserConfig)
5063

5164
console.log('[MutableWeb] Engine initialized', engineRef.current)
5265
}

libs/engine/src/app/services/parser-config/parser-config.entity.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ export enum AdapterType {
88
Microdata = 'microdata',
99
Json = 'json',
1010
MWeb = 'mweb',
11+
Link = 'link',
1112
}
1213

1314
export type ParserConfig =
1415
| ({ parserType: AdapterType.Json; id: ParserConfigId; targets: Target[] } & JsonParserConfig)
1516
| ({ parserType: AdapterType.Bos; id: ParserConfigId; targets: Target[] } & BosParserConfig)
1617
| { parserType: AdapterType.MWeb; id: ParserConfigId; targets: Target[] }
18+
| { parserType: AdapterType.Link; id: ParserConfigId; targets: Target[] }

libs/react/src/components/context-portal.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, ReactElement, useEffect, useRef, useState } from 'react'
1+
import React, { FC, ReactElement, useEffect, useState } from 'react'
22
import { createPortal } from 'react-dom'
33
import { IContextNode } from '@mweb/core'
44
import { InsertionType } from '@mweb/core'
@@ -13,7 +13,14 @@ export const ContextPortal: FC<{
1313
// ToDo: replace insPoints.find with Map
1414
const target = injectTo
1515
? context.insPoints.find((ip) => ip.name === injectTo)
16-
: { element: context.element, insertionType: DefaultInsertionType }
16+
: {
17+
element: context.element,
18+
insertionType:
19+
// ToDo: de-hardcode when context modificators will be implemented (using contexts instead of insertion points)
20+
context.namespace === 'engine' && context.contextType === 'link'
21+
? InsertionType.Replace
22+
: DefaultInsertionType,
23+
}
1724

1825
if (!target?.element) return null
1926
if (!target?.insertionType) return null
@@ -49,13 +56,29 @@ const InsPointPortal: FC<{
4956
case InsertionType.End:
5057
element.appendChild(_container)
5158
break
59+
case InsertionType.Replace:
60+
// Only one layout manager will be injected
61+
// so this code will be executed only once for multiple widgets
62+
63+
// We hide the element instead of removing it
64+
// because it must be shown again when the layout manager is unmounted.
65+
// Also it prevents unexpected behaviour of an original website.
66+
element.style.display = 'none'
67+
68+
element.after(_container)
69+
break
5270
default:
5371
break
5472
}
5573

5674
setContainer(_container)
5775

5876
return () => {
77+
if (insertionType === InsertionType.Replace) {
78+
// ToDo: hidden contexts will be shown
79+
element.style.display = ''
80+
}
81+
5982
_container.remove()
6083
setContainer(null)
6184
}

libs/react/src/contexts/core-context/core-provider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const CoreProvider: FC<Props> = ({ children }) => {
1111

1212
if (!coreRef.current) {
1313
coreRef.current = new Core()
14+
15+
console.log('[@mweb/react] Initialized core', coreRef.current)
1416
}
1517

1618
const core = coreRef.current

0 commit comments

Comments
 (0)