article-content-renderer-vue2 0.1.0
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/LICENSE +22 -0
- package/README.md +366 -0
- package/dist/article-content-renderer-vue2.css +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.js +1215 -0
- package/dist/types/components/AdManagerAd.d.ts +39 -0
- package/dist/types/components/AdSenseAd.d.ts +18 -0
- package/dist/types/components/ArticleContentRenderer.vue.d.ts +23 -0
- package/dist/types/core/url.d.ts +6 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/protocols/registry.d.ts +8 -0
- package/dist/types/protocols/types.d.ts +24 -0
- package/dist/types/protocols/v1/metadata.d.ts +12 -0
- package/dist/types/protocols/v1/renderer.d.ts +3 -0
- package/dist/types/protocols/v1/validator.d.ts +2 -0
- package/dist/types/types.d.ts +153 -0
- package/package.json +77 -0
- package/protocol/article-content-protocol-v1.json +939 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NovaVoyager
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# article-content-renderer-vue2
|
|
2
|
+
|
|
3
|
+
面向 Vue 2.7 的 Article Content Protocol 渲染组件。组件接收协议定义的 ProseMirror JSON,通过 Vue 2 VNode 安全渲染,不使用 `v-html`。
|
|
4
|
+
|
|
5
|
+
该项目与 Vue 3 版本保持相同的协议类型、运行时校验、节点能力、URL 安全策略、articleButton resolver 和样式变量,仅将组件实现与工具链切换为 Vue 2.7。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- 支持 Article Content Protocol v1 的全部节点和 marks。
|
|
10
|
+
- 使用 Vue 2 `CreateElement` 和 `VNodeData`,不是 Vue 3 兼容层。
|
|
11
|
+
- 支持严格模式和非严格容错渲染。
|
|
12
|
+
- 拦截危险链接和图片 URL。
|
|
13
|
+
- articleButton 的完整 href 由使用者回调生成。
|
|
14
|
+
- 支持 Vue 2 SSR。
|
|
15
|
+
- 提供 TypeScript 类型、结构化错误和 CSS Variables。
|
|
16
|
+
- Vue 作为 peer dependency,不会打入组件包。
|
|
17
|
+
|
|
18
|
+
## 环境要求
|
|
19
|
+
|
|
20
|
+
- Vue `2.7.16` 或兼容的 Vue 2.7 版本。
|
|
21
|
+
- Node.js 18 或更高版本用于本地开发和构建。
|
|
22
|
+
|
|
23
|
+
Vue 2 已经停止官方维护;该组件选择 Vue 2.7 是为了兼容仍在使用 Vue 2 的项目,新项目建议优先使用 Vue 3 版本。
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install article-content-renderer-vue2 vue@2.7.16
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
引入组件样式:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import 'article-content-renderer-vue2/style.css'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 基本使用
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<script lang="ts">
|
|
41
|
+
import Vue from 'vue'
|
|
42
|
+
import {
|
|
43
|
+
ArticleContentRenderer,
|
|
44
|
+
type ArticleButtonClickPayload,
|
|
45
|
+
type ArticleDocument,
|
|
46
|
+
type RenderIssue,
|
|
47
|
+
type ResolveArticleButtonLink,
|
|
48
|
+
} from 'article-content-renderer-vue2'
|
|
49
|
+
import 'article-content-renderer-vue2/style.css'
|
|
50
|
+
|
|
51
|
+
export default Vue.extend({
|
|
52
|
+
name: 'ArticlePage',
|
|
53
|
+
components: {
|
|
54
|
+
ArticleContentRenderer,
|
|
55
|
+
},
|
|
56
|
+
data() {
|
|
57
|
+
const article: ArticleDocument = {
|
|
58
|
+
type: 'doc',
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'heading',
|
|
62
|
+
attrs: { level: 1 },
|
|
63
|
+
content: [{ type: 'text', text: 'Article title' }],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'articleButton',
|
|
67
|
+
attrs: {
|
|
68
|
+
id: 'view-more',
|
|
69
|
+
text: 'View more',
|
|
70
|
+
style: 'button',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { article }
|
|
77
|
+
},
|
|
78
|
+
methods: {
|
|
79
|
+
resolveArticleButtonLink: ((attrs) => {
|
|
80
|
+
// 完整 href 由使用者返回,组件不会自动追加参数。
|
|
81
|
+
return `/detail/${encodeURIComponent(attrs.id)}`
|
|
82
|
+
}) as ResolveArticleButtonLink,
|
|
83
|
+
handleArticleButtonClick(payload: ArticleButtonClickPayload): void {
|
|
84
|
+
console.log(payload.attrs, payload.href)
|
|
85
|
+
},
|
|
86
|
+
handleRenderError(issue: RenderIssue): void {
|
|
87
|
+
console.warn(issue.code, issue.path, issue.message)
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<ArticleContentRenderer
|
|
95
|
+
:document="article"
|
|
96
|
+
:resolve-article-button-link="resolveArticleButtonLink"
|
|
97
|
+
@article-button-click="handleArticleButtonClick"
|
|
98
|
+
@render-error="handleRenderError"
|
|
99
|
+
/>
|
|
100
|
+
</template>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
也可以注册为 Vue 2 全局插件:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import Vue from 'vue'
|
|
107
|
+
import { ArticleContentRendererPlugin } from 'article-content-renderer-vue2'
|
|
108
|
+
import 'article-content-renderer-vue2/style.css'
|
|
109
|
+
|
|
110
|
+
Vue.use(ArticleContentRendererPlugin)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 广告与图片地址
|
|
114
|
+
|
|
115
|
+
广告参数与 React 版保持一致:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
interface AdConfig {
|
|
119
|
+
adm?: readonly unknown[]
|
|
120
|
+
ads?: readonly unknown[]
|
|
121
|
+
loc: readonly number[]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface PubId {
|
|
125
|
+
adm: string
|
|
126
|
+
ads: string
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<ArticleContentRenderer
|
|
132
|
+
:document="article"
|
|
133
|
+
:ad-conf="{
|
|
134
|
+
adm: ['banner-1', 'banner-2'],
|
|
135
|
+
ads: ['123', '456'],
|
|
136
|
+
loc: [2, 5],
|
|
137
|
+
}"
|
|
138
|
+
:pubid="{
|
|
139
|
+
adm: '/23054585162/newsflowly/',
|
|
140
|
+
ads: '3887371527059481',
|
|
141
|
+
}"
|
|
142
|
+
ad-title="Advertisement"
|
|
143
|
+
image-base-url="https://cdn.example.com/"
|
|
144
|
+
@render-error="handleRenderError"
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`loc` 使用从 1 开始的 `document.content` 顶层位置,广告会插入在对应元素前面。位置超出文档长度时直接忽略。每个非空 `adm`/`ads` 数组的长度都必须与 `loc` 一致,否则不生成广告占位,并通过 `render-error` 上报 `AD_CONFIG_LENGTH_MISMATCH`。
|
|
149
|
+
|
|
150
|
+
广告选择规则:
|
|
151
|
+
|
|
152
|
+
- 同一位置同时有 `adm` 和 `ads`:先请求 ADM;只有 ADM 返回空广告时才请求对应 AdSense。
|
|
153
|
+
- 只有 `adm`:只请求 ADM,空广告也不回退。
|
|
154
|
+
- 只有 `ads`:直接请求 AdSense。
|
|
155
|
+
|
|
156
|
+
宿主项目需要分别加载一次 Google Publisher Tag 和 Google AdSense SDK。组件通过 `googletag.cmd` 和 `adsbygoogle` 队列请求广告,不会为每个广告位重复插入 `<script>`。
|
|
157
|
+
|
|
158
|
+
ADM 和 AdSense 共用稳定的全局 class,样式由宿主项目覆盖:
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
.article-ad-wrapper {
|
|
162
|
+
max-width: 728px;
|
|
163
|
+
height: 110px;
|
|
164
|
+
margin: 0 auto;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.article-ad-title {
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
line-height: 20px;
|
|
171
|
+
text-align: center;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
外层使用纵向 flex 布局,广告单元自动占满标题之外的剩余高度。上例中外层为 `110px`、标题为 `20px`,广告实际高度为 `90px`。如果宿主使用 `<style scoped>`,请使用 Vue 2 的深度选择器,或将这些广告规则放到非 scoped 样式中。
|
|
176
|
+
|
|
177
|
+
`adTitle` 默认为 `Advertisement`。`imageBaseUrl` 默认为 `https://www.doitme.link/`;传入新地址时,只替换文档图片中该默认前缀,其他图片 URL 保持不变。
|
|
178
|
+
|
|
179
|
+
## articleButton 链接
|
|
180
|
+
|
|
181
|
+
`style: "button"` 和 `style: "text"` 都使用 `<a>` 渲染,只改变视觉样式。resolver 接收当前节点的完整只读属性:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
interface ArticleButtonAttrs {
|
|
185
|
+
id: string
|
|
186
|
+
title?: string
|
|
187
|
+
text: string
|
|
188
|
+
style: 'text' | 'button'
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
resolver 类型:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
type ResolveArticleButtonLink = (
|
|
196
|
+
attrs: Readonly<ArticleButtonAttrs>,
|
|
197
|
+
node: Readonly<ArticleButtonNode>,
|
|
198
|
+
) =>
|
|
199
|
+
| string
|
|
200
|
+
| {
|
|
201
|
+
href: string
|
|
202
|
+
target?: '_self' | '_blank'
|
|
203
|
+
rel?: string
|
|
204
|
+
}
|
|
205
|
+
| null
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
完整 href 由使用者生成:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const resolveArticleButtonLink: ResolveArticleButtonLink = (attrs) => {
|
|
212
|
+
return `/detail/${attrs.id}`
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
当 `attrs.id` 为 `view-more` 时,最终 DOM 为:
|
|
217
|
+
|
|
218
|
+
```html
|
|
219
|
+
<a href="/detail/view-more">...</a>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
组件不会自动添加 `?`,也不会自动把节点属性转换成查询参数。
|
|
223
|
+
|
|
224
|
+
### 使用闭包
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
function createArticleButtonResolver(pathPrefix: string): ResolveArticleButtonLink {
|
|
228
|
+
const prefix = pathPrefix.replace(/\/+$/, '')
|
|
229
|
+
|
|
230
|
+
return (attrs) => `${prefix}/${encodeURIComponent(attrs.id)}`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const resolveArticleButtonLink = createArticleButtonResolver('/detail')
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Vue Router 3
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const resolveArticleButtonLink: ResolveArticleButtonLink = (attrs) => {
|
|
240
|
+
return this.$router.resolve({
|
|
241
|
+
name: 'article-detail',
|
|
242
|
+
params: { id: attrs.id },
|
|
243
|
+
}).href
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleArticleButtonClick(payload: ArticleButtonClickPayload): void {
|
|
247
|
+
if (!payload.href) return
|
|
248
|
+
payload.event.preventDefault()
|
|
249
|
+
void this.$router.push(payload.href)
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Props
|
|
254
|
+
|
|
255
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
256
|
+
| --- | --- | --- | --- |
|
|
257
|
+
| `document` | `unknown` | 必填 | Article Content Protocol 文档 |
|
|
258
|
+
| `protocolVersion` | `number` | `1` | 协议适配器版本 |
|
|
259
|
+
| `strict` | `boolean` | `false` | 校验失败时是否停止整篇正文渲染 |
|
|
260
|
+
| `adConf` | `AdConfig` | `{ adm: [], ads: [], loc: [] }` | 广告配置;非空广告数组长度须与 `loc` 一致 |
|
|
261
|
+
| `pubid` | `PubId` | `{ adm: '', ads: '' }` | ADM/AdSense 发布标识 |
|
|
262
|
+
| `adTitle` | `string` | `"Advertisement"` | ADM 和 AdSense 共用的广告标题 |
|
|
263
|
+
| `imageBaseUrl` | `string` | `"https://www.doitme.link/"` | 替换文档图片的默认地址前缀 |
|
|
264
|
+
| `resolveArticleButtonLink` | `ResolveArticleButtonLink` | `undefined` | 由使用者生成 articleButton 完整链接 |
|
|
265
|
+
|
|
266
|
+
## Events
|
|
267
|
+
|
|
268
|
+
### article-button-click
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
interface ArticleButtonClickPayload {
|
|
272
|
+
attrs: Readonly<ArticleButtonAttrs>
|
|
273
|
+
node: Readonly<ArticleButtonNode>
|
|
274
|
+
href: string | null
|
|
275
|
+
event: MouseEvent
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
事件在原生跳转前同步触发。调用 `payload.event.preventDefault()` 可以由 Vue Router 3 接管跳转。
|
|
280
|
+
|
|
281
|
+
### render-error
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
interface RenderIssue {
|
|
285
|
+
code: RenderIssueCode
|
|
286
|
+
path: string
|
|
287
|
+
message: string
|
|
288
|
+
nodeType?: string
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## 校验和 URL 安全
|
|
293
|
+
|
|
294
|
+
校验同时覆盖 JSON 结构、`contentModel` 和跨节点约束:
|
|
295
|
+
|
|
296
|
+
- `block+`、`listItem+`、`tableRow+` 等内容不能为空。
|
|
297
|
+
- `codeBlock` 最多包含一个 text 节点。
|
|
298
|
+
- 同一张表格的每一行必须具有相同列数。
|
|
299
|
+
- 未知节点、mark、属性和越界值会被报告。
|
|
300
|
+
|
|
301
|
+
普通链接和 articleButton 允许 `http:`、`https:`、`mailto:`、`tel:`、相对路径和页面锚点。图片允许 `http:`、`https:`、`blob:` 和相对路径。危险协议会被拦截。
|
|
302
|
+
|
|
303
|
+
## Vue 2 根节点说明
|
|
304
|
+
|
|
305
|
+
Vue 2 状态组件要求单一根节点,因此该版本会输出:
|
|
306
|
+
|
|
307
|
+
```html
|
|
308
|
+
<div class="acp-document" data-node-type="doc">...</div>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
默认样式为:
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
.acp-document {
|
|
315
|
+
display: contents;
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
它不会增加额外布局盒。Vue 3 版本使用 Fragment,不需要此兼容根节点。
|
|
320
|
+
|
|
321
|
+
## 样式覆盖
|
|
322
|
+
|
|
323
|
+
组件与 Vue 3 版使用相同的 `acp-` 类名和 CSS Variables:
|
|
324
|
+
|
|
325
|
+
```css
|
|
326
|
+
.my-article-theme {
|
|
327
|
+
--acp-color-accent: #7c3aed;
|
|
328
|
+
--acp-color-accent-hover: #6d28d9;
|
|
329
|
+
--acp-radius: 8px;
|
|
330
|
+
--acp-spacing-block: 20px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.my-article-theme .acp-article-button--button {
|
|
334
|
+
min-width: 160px;
|
|
335
|
+
border-radius: 999px;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## 本地 Demo
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm install
|
|
343
|
+
npm run dev
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
默认访问 `http://localhost:5173`。Demo 包含全部主要节点、可编辑 articleButton resolver、严格模式、错误列表和 JSON 查看器。
|
|
347
|
+
|
|
348
|
+
动态 resolver 编辑器只用于本地 Demo;生产项目应在 Vue/TypeScript 源码中定义 resolver。
|
|
349
|
+
|
|
350
|
+
单独检查并构建 Demo:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
npm run demo:typecheck
|
|
354
|
+
npm run demo:build
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## 开发命令
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
npm run typecheck
|
|
361
|
+
npm run test:run
|
|
362
|
+
npm run build
|
|
363
|
+
npm run check
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
原始协议定义位于 [protocol/article-content-protocol-v1.json](./protocol/article-content-protocol-v1.json),完整示例位于 [examples/article-v1.json](./examples/article-v1.json)。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--acp-color-text: inherit;--acp-color-muted: #667085;--acp-color-border: #d0d5dd;--acp-color-surface: #f8fafc;--acp-color-accent: #2563eb;--acp-color-accent-hover: #1d4ed8;--acp-color-on-accent: #ffffff;--acp-radius: .375rem;--acp-spacing-block: 1rem;--acp-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace}.acp-document{display:contents}.acp-paragraph,.acp-heading,.acp-blockquote,.acp-list,.acp-code-block,.acp-horizontal-rule,.acp-image,.acp-article-button,.acp-table-wrapper{box-sizing:border-box}.acp-paragraph,.acp-heading,.acp-blockquote,.acp-list,.acp-code-block,.acp-horizontal-rule,.acp-image,.acp-table-wrapper{margin-block:0 var(--acp-spacing-block)}.acp-paragraph:first-child,.acp-heading:first-child,.acp-blockquote:first-child,.acp-list:first-child,.acp-code-block:first-child,.acp-horizontal-rule:first-child,.acp-image:first-child,.acp-table-wrapper:first-child{margin-block-start:0}.acp-paragraph{color:var(--acp-color-text)}.acp-blockquote{margin-inline:0;padding-inline-start:1rem;border-inline-start:.25rem solid var(--acp-color-border);color:var(--acp-color-muted)}.acp-list{padding-inline-start:1.5rem}.acp-list-item>:last-child{margin-block-end:0}.acp-code-block{overflow-x:auto;padding:1rem;border-radius:var(--acp-radius);background:var(--acp-color-surface);font-family:var(--acp-font-mono);white-space:pre}.acp-mark--code{padding:.1em .25em;border-radius:calc(var(--acp-radius) / 2);background:var(--acp-color-surface);font-family:var(--acp-font-mono)}.acp-horizontal-rule{border:0;border-block-start:1px solid var(--acp-color-border)}.acp-image{display:flex;width:100%}.acp-image--left{justify-content:flex-start}.acp-image--center{justify-content:center}.acp-image--right{justify-content:flex-end}.acp-image__element{display:block;max-width:100%;height:auto}.acp-link,.acp-article-button--text{color:var(--acp-color-accent);text-decoration:underline;text-underline-offset:.15em}.acp-link:hover,.acp-article-button--text:hover{color:var(--acp-color-accent-hover)}.acp-article-button{display:inline-flex;align-items:center;justify-content:center;margin-block:0 var(--acp-spacing-block);cursor:pointer}.acp-article-button--button{min-height:2.5rem;padding:.5rem 1rem;border-radius:var(--acp-radius);background:var(--acp-color-accent);color:var(--acp-color-on-accent);text-decoration:none}.acp-article-button--button:hover{background:var(--acp-color-accent-hover)}.acp-article-button--disabled{cursor:not-allowed;opacity:.55}.acp-table-wrapper{width:100%;overflow-x:auto}.acp-ad-slot{box-sizing:border-box;width:100%}.article-ad-wrapper{display:flex;flex-direction:column;overflow:hidden}.article-ad-title{flex:0 0 auto}.article-ad-unit{flex:1 1 0;min-height:0;width:100%}.acp-table{width:100%;border-collapse:collapse}.acp-table-cell{padding:.625rem .75rem;border:1px solid var(--acp-color-border);vertical-align:top}.acp-table-cell>:last-child{margin-block-end:0}.acp-render-error{padding:.75rem 1rem;border:1px solid #fda29b;border-radius:var(--acp-radius);background:#fffbfa;color:#b42318}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var Y=Object.defineProperty,G=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var v=Object.getOwnPropertySymbols;var W=Object.prototype.hasOwnProperty,Q=Object.prototype.propertyIsEnumerable;var k=(i,e,t)=>e in i?Y(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t,m=(i,e)=>{for(var t in e||(e={}))W.call(e,t)&&k(i,t,e[t]);if(v)for(var t of v(e))Q.call(e,t)&&k(i,t,e[t]);return i},S=(i,e)=>G(i,H(e));var O=(i,e,t)=>k(i,typeof e!="symbol"?e+"":e,t);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const N=require("vue"),X=new Set(["http:","https:","mailto:","tel:"]),J=new Set(["http:","https:","blob:"]),Z="https://article-content-renderer.invalid/",I="https://www.doitme.link/";function tt(i,e){if(typeof i!="string")return i;const t=i.trim();if(!t.startsWith(I))return t;const n=(e.trim()||I).replace(/\/+$/u,""),s=t.slice(I.length).replace(/^\/+/,"");return`${n}/${s}`}function E(i,e){if(typeof i!="string")return null;const t=i.trim();if(!t||/[\u0000-\u001F\u007F]/u.test(t))return null;try{const r=new URL(t,Z);return(e==="link"?X:J).has(r.protocol)?t:null}catch(r){return null}}function D(i,e){const t=new Set(typeof i=="string"?i.trim().split(/\s+/u).filter(Boolean):[]);return e==="_blank"&&(t.add("noopener"),t.add("noreferrer")),t.size>0?[...t].join(" "):void 0}function et(i){const e=i.trim();return e?e.startsWith("ca-pub-")?e:`ca-pub-${e}`:""}const x=N.extend({name:"ArticleAdSenseAd",props:{publisherId:{type:String,required:!0},slotId:{type:[String,Number],required:!0},title:{type:String,default:"Advertisement"}},data(){return{observer:null,requestedKey:null}},computed:{adClient(){return et(this.publisherId)},adSlot(){return String(this.slotId).trim()},requestKey(){return`${this.adClient}:${this.adSlot}`}},watch:{requestKey(){this.requestedKey=null,this.stopObserver(),this.$nextTick(()=>this.startObserver())}},mounted(){this.startObserver()},beforeDestroy(){this.stopObserver()},methods:{requestAd(){var i;if(!(!this.adClient||!this.adSlot||this.requestedKey===this.requestKey))try{const e=window;((i=e.adsbygoogle)!=null?i:e.adsbygoogle=[]).push({}),this.requestedKey=this.requestKey}catch(e){console.error("[ArticleContentRenderer] Failed to request AdSense ad:",e)}},startObserver(){const i=this.$refs.root;if(!(!i||!this.adClient||!this.adSlot)){if(typeof IntersectionObserver=="undefined"){this.requestAd();return}this.observer=new IntersectionObserver(e=>{e.some(t=>t.isIntersecting)&&(this.requestAd(),this.stopObserver())},{rootMargin:"200px 0px"}),this.observer.observe(i)}},stopObserver(){var i;(i=this.observer)==null||i.disconnect(),this.observer=null}},render(i){return!this.adClient||!this.adSlot?i():i("div",{ref:"root",staticClass:"article-ad-wrapper",attrs:{"data-google-ad":"true"}},[i("div",{staticClass:"article-ad-title"},this.title),i("ins",{key:this.requestKey,staticClass:"adsbygoogle article-ad-unit",style:{display:"block",width:"100%"},attrs:{"data-ad-client":this.adClient,"data-ad-slot":this.adSlot}})])}});function C(){var e;const i=window;return(e=i.googletag)!=null||(i.googletag={cmd:[]}),i.googletag}function rt(i,e){const t=i.trim().replace(/\/+$/u,""),r=e.replace(/^\/+|\/+$/gu,"");return t&&r?`${t}/${r}`:""}const it=N.extend({name:"ArticleAdManagerAd",props:{publisherId:{type:String,required:!0},slotId:{type:[String,Number],required:!0},title:{type:String,default:"Advertisement"},fallbackPublisherId:{type:String,default:""},fallbackSlotId:{type:[String,Number],default:void 0}},data(){return{observer:null,definedSlot:null,pubAdsService:null,slotRenderEnded:null,emptyAdmKey:null,cancelled:!1}},computed:{admSlotId(){return String(this.slotId).trim()},adsSlotId(){return this.fallbackSlotId===void 0?"":String(this.fallbackSlotId).trim()},adUnitPath(){return rt(this.publisherId,this.admSlotId)},fallbackKey(){return`${this.adUnitPath}:${this.admSlotId}:${this.fallbackPublisherId}:${this.adsSlotId}`},hasAdSenseFallback(){return!!(this.fallbackPublisherId.trim()&&this.adsSlotId)},shouldUseAdSense(){return this.hasAdSenseFallback&&this.emptyAdmKey===this.fallbackKey}},watch:{fallbackKey(){this.restartAdManager()}},mounted(){this.startObserver()},beforeDestroy(){this.cleanupAdManager()},methods:{restartAdManager(){this.cleanupAdManager(),this.emptyAdmKey=null,this.$nextTick(()=>this.startObserver())},startObserver(){const i=this.$refs.root;if(!(!i||!this.adUnitPath||!this.admSlotId||this.shouldUseAdSense)){if(this.cancelled=!1,typeof IntersectionObserver=="undefined"){this.requestAd();return}this.observer=new IntersectionObserver(e=>{var t;e.some(r=>r.isIntersecting)&&(this.requestAd(),(t=this.observer)==null||t.disconnect(),this.observer=null)},{rootMargin:"200px 0px"}),this.observer.observe(i)}},requestAd(){const i=this.$refs.unit;if(!i)return;const e=C();e.cmd.push(()=>{if(this.cancelled)return;const t=i.clientWidth,r=i.clientHeight,n=t>0&&r>0?["fluid",[t,r]]:"fluid",s=e.defineSlot(this.adUnitPath,n,this.admSlotId);if(!s)return;const a=e.pubads(),o=c=>{if(this.cancelled||c.slot!==s||!c.isEmpty||!this.hasAdSenseFallback)return;const d=this.fallbackKey;this.cleanupAdManager(),this.emptyAdmKey=d};this.definedSlot=s,this.pubAdsService=a,this.slotRenderEnded=o,a.addEventListener("slotRenderEnded",o),s.addService(a),e.enableServices(),e.display(this.admSlotId)})},cleanupAdManager(){var n;this.cancelled=!0,(n=this.observer)==null||n.disconnect(),this.observer=null;const i=this.definedSlot,e=this.pubAdsService,t=this.slotRenderEnded;if(this.definedSlot=null,this.pubAdsService=null,this.slotRenderEnded=null,!i&&!t)return;const r=C();r.cmd.push(()=>{var s,a;e&&t&&((s=e.removeEventListener)==null||s.call(e,"slotRenderEnded",t)),i&&((a=r.destroySlots)==null||a.call(r,[i]))})}},render(i){return this.shouldUseAdSense?i(x,{props:{publisherId:this.fallbackPublisherId,slotId:this.adsSlotId,title:this.title}}):!this.adUnitPath||!this.admSlotId?i():i("div",{ref:"root",staticClass:"article-ad-wrapper article-adm-wrapper",attrs:{"data-google-ad-manager":"true","data-ad-unit-path":this.adUnitPath}},[i("div",{staticClass:"article-ad-title"},this.title),i("div",{ref:"unit",staticClass:"article-ad-unit article-adm-unit",attrs:{id:this.admSlotId}})])}}),nt=new Set(["paragraph","heading","blockquote","bulletList","orderedList","codeBlock","horizontalRule","image","articleButton","table"]);function g(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function R(i){return g(i)?i:{}}function h(i,e,t,r){var c;const n=g(t)?t:{},s=g(t)?r:t,a={},o={};for(const[d,f]of Object.entries(n))if(d==="class")a.class=f;else if(d==="style")a.style=f;else if(d.startsWith("on")&&typeof f=="function"){const F=d.slice(2).toLowerCase();a.on=S(m({},(c=a.on)!=null?c:{}),{[F]:f})}else f!==void 0&&f!==!1&&(o[d]=f);return Object.keys(o).length>0&&(a.attrs=o),i.createElement(e,a,s)}function y(i){return Array.isArray(i)?i:[]}function p(i,...e){return`${i}/${e.map(String).join("/")}`}function q(i){return i==="left"||i==="center"||i==="right"||i==="justify"}function st(i){return i==="left"||i==="center"||i==="right"}function L(i){return i==="_blank"||i==="_self"}function A(i,e){i.reportIssue(e)}function at(i,e,t){if(!g(i)||i.type!=="text"||typeof i.text!="string"||!i.text)return null;let r=i.text;const n=y(i.marks);for(let s=0;s<n.length;s+=1){const a=n[s];if(!g(a)||typeof a.type!="string")continue;const o=p(e,"marks",s);switch(a.type){case"bold":r=h(t,"strong",{class:"acp-mark acp-mark--bold"},[r]);break;case"italic":r=h(t,"em",{class:"acp-mark acp-mark--italic"},[r]);break;case"strike":r=h(t,"s",{class:"acp-mark acp-mark--strike"},[r]);break;case"underline":r=h(t,"u",{class:"acp-mark acp-mark--underline"},[r]);break;case"code":r=h(t,"code",{class:"acp-mark acp-mark--code"},[r]);break;case"link":{const c=R(a.attrs),d=E(c.href,"link");if(!d){A(t,{code:"UNSAFE_URL",path:p(o,"attrs","href"),message:"The link URL is empty, malformed, or uses a disallowed protocol.",nodeType:"link"});break}const f=L(c.target)?c.target:"_blank";r=h(t,"a",{class:"acp-link",href:d,target:f,rel:D(void 0,f)},[r]);break}}}return r}function w(i,e,t){return y(i).map((r,n)=>at(r,p(e,n),t)).filter(r=>r!==null)}function B(i,e,t){return!g(i)||i.type!=="listItem"?null:h(t,"li",{class:"acp-list-item","data-node-type":"listItem"},P(i.content,p(e,"content"),t))}function ot(i,e,t){return!g(i)||i.type!=="tableCell"?null:h(t,"td",{class:"acp-table-cell","data-node-type":"tableCell"},P(i.content,p(e,"content"),t))}function lt(i,e,t){if(!g(i)||i.type!=="tableRow")return null;const r=y(i.content).map((n,s)=>ot(n,p(e,"content",s),t)).filter(n=>n!==null);return h(t,"tr",{class:"acp-table-row","data-node-type":"tableRow"},r)}function dt(i){var n;if(g(i)&&i.target!==void 0&&!L(i.target)||g(i)&&i.rel!==void 0&&typeof i.rel!="string")return null;const e=typeof i=="string"?{href:i}:g(i)&&typeof i.href=="string"?m(m({href:i.href},L(i.target)?{target:i.target}:{}),typeof i.rel=="string"?{rel:i.rel}:{}):null;if(!e)return null;const t=E(e.href,"link");if(!t)return null;const r=(n=e.target)!=null?n:"_self";return{href:t,target:r,rel:D(e.rel,r)}}function ct(i,e,t){var c;const r=R(i.attrs);if(typeof r.id!="string"||!r.id||typeof r.text!="string"||!r.text||r.style!=="text"&&r.style!=="button")return null;const n=Object.freeze(S(m({id:r.id},typeof r.title=="string"?{title:r.title}:{}),{text:r.text,style:r.style})),s=Object.freeze({type:"articleButton",attrs:n});let a=null;if(!t.resolveArticleButtonLink)A(t,{code:"LINK_RESOLUTION_FAILED",path:e,message:"No resolveArticleButtonLink callback was provided for an articleButton node.",nodeType:"articleButton"});else try{const d=t.resolveArticleButtonLink(n,s);a=dt(d),a||A(t,{code:typeof d=="string"||g(d)&&typeof d.href=="string"?"UNSAFE_URL":"LINK_RESOLUTION_FAILED",path:e,message:"The articleButton link resolver returned no usable safe URL.",nodeType:"articleButton"})}catch(d){A(t,{code:"LINK_RESOLUTION_FAILED",path:e,message:`The articleButton link resolver threw an error: ${d instanceof Error?d.message:String(d)}`,nodeType:"articleButton"})}const o=(c=a==null?void 0:a.href)!=null?c:null;return h(t,"a",{class:["acp-article-button",`acp-article-button--${n.style}`,!o&&"acp-article-button--disabled"],href:o!=null?o:void 0,target:a==null?void 0:a.target,rel:a==null?void 0:a.rel,title:n.title,"data-node-type":"articleButton","data-article-button-id":n.id,"data-article-button-style":n.style,"aria-disabled":o?void 0:"true",onClick:d=>{o||d.preventDefault(),t.emitArticleButtonClick({attrs:n,node:s,href:o,event:d})}},n.text)}function M(i,e,t){if(!g(i)||typeof i.type!="string"||!nt.has(i.type))return null;const r=R(i.attrs);switch(i.type){case"paragraph":{const n=q(r.textAlign)?r.textAlign:void 0;return h(t,"p",{class:"acp-paragraph","data-node-type":"paragraph",style:n?{textAlign:n}:void 0},w(i.content,p(e,"content"),t))}case"heading":{const n=Number.isInteger(r.level)&&Number(r.level)>=1&&Number(r.level)<=6?Number(r.level):1,s=q(r.textAlign)?r.textAlign:void 0;return h(t,`h${n}`,{class:["acp-heading",`acp-heading--${n}`],"data-node-type":"heading",style:s?{textAlign:s}:void 0},w(i.content,p(e,"content"),t))}case"blockquote":return h(t,"blockquote",{class:"acp-blockquote","data-node-type":"blockquote"},P(i.content,p(e,"content"),t));case"bulletList":{const n=y(i.content).map((s,a)=>B(s,p(e,"content",a),t)).filter(s=>s!==null);return h(t,"ul",{class:"acp-list acp-list--bullet","data-node-type":"bulletList"},n)}case"orderedList":{const n=Number.isInteger(r.start)&&Number(r.start)>=1?Number(r.start):1,s=y(i.content).map((a,o)=>B(a,p(e,"content",o),t)).filter(a=>a!==null);return h(t,"ol",{class:"acp-list acp-list--ordered","data-node-type":"orderedList",start:n},s)}case"codeBlock":{const n=typeof r.language=="string"&&r.language?r.language:void 0,s=y(i.content).filter(a=>g(a)&&a.type==="text"&&typeof a.text=="string").map(a=>a.text).join("");return h(t,"pre",{class:"acp-code-block","data-node-type":"codeBlock","data-language":n},[h(t,"code",{class:n?`language-${n}`:void 0},s)])}case"horizontalRule":return h(t,"hr",{class:"acp-horizontal-rule","data-node-type":"horizontalRule"});case"image":{const n=E(tt(r.src,t.imageBaseUrl),"image");if(!n)return A(t,{code:"UNSAFE_URL",path:p(e,"attrs","src"),message:"The image URL is empty, malformed, or uses a disallowed protocol.",nodeType:"image"}),null;const s=st(r.imageAlign)?r.imageAlign:"center",a=Number.isInteger(r.width)&&Number(r.width)>=1&&Number(r.width)<=1e4?Number(r.width):void 0,o=Number.isInteger(r.height)&&Number(r.height)>=1&&Number(r.height)<=1e4?Number(r.height):void 0;return h(t,"div",{class:["acp-image",`acp-image--${s}`],"data-node-type":"image","data-image-align":s},[h(t,"img",{class:"acp-image__element",src:n,alt:typeof r.alt=="string"?r.alt:"",title:typeof r.title=="string"?r.title:void 0,width:a,height:o,"data-image-align":s})])}case"articleButton":return ct(i,e,t);case"table":{const n=y(i.content).map((s,a)=>lt(s,p(e,"content",a),t)).filter(s=>s!==null);return h(t,"div",{class:"acp-table-wrapper","data-node-type":"table"},[h(t,"table",{class:"acp-table"},[h(t,"tbody",n)])])}}return null}function P(i,e,t){return y(i).map((r,n)=>M(r,p(e,n),t)).filter(r=>r!==null)}function $(i){return typeof i=="string"||typeof i=="number"?i:void 0}function ut(i,e){const t=$(i.adm),r=$(i.ads),n=t!==void 0&&e.admPublisherId?e.createElement(it,{key:`adm:${i.index}:${String(t)}`,props:{publisherId:e.admPublisherId,slotId:t,title:e.adTitle,fallbackPublisherId:r!==void 0?e.adsPublisherId:void 0,fallbackSlotId:r}}):r!==void 0&&e.adsPublisherId?e.createElement(x,{key:`ads:${i.index}:${String(r)}`,props:{publisherId:e.adsPublisherId,slotId:r,title:e.adTitle}}):null;return e.createElement("div",{key:`/adConf/${i.index}`,staticClass:"acp-ad-slot",attrs:{"data-node-type":"adSlot","data-ad-slot":"true","data-ad-index":i.index,"data-ad-location":i.location,"data-adm":t,"data-ads":r}},n?[n]:[])}function ht(i,e){const t=y(i.content),r=new Map;e.adSlots.forEach(s=>{var o;if(s.location>t.length)return;const a=(o=r.get(s.location))!=null?o:[];a.push(s),r.set(s.location,a)});const n=[];return t.forEach((s,a)=>{var d;const o=a+1;(d=r.get(o))==null||d.forEach(f=>n.push(ut(f,e)));const c=M(s,p("/content",a),e);c&&typeof c!="string"&&n.push(c)}),n}function ft(i,e){return!g(i)||i.type!=="doc"?[]:ht(i,e)}const pt=new Set(["paragraph","heading","blockquote","bulletList","orderedList","codeBlock","horizontalRule","image","articleButton","table"]),gt=new Set(["left","center","right","justify"]),yt=new Set(["left","center","right"]),mt=new Set(["bold","italic","strike","underline","code"]);function b(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function l(i,e){return`${i}/${String(e).replaceAll("~","~0").replaceAll("/","~1")}`}function u(i,...e){let t=i;for(const r of e)t=l(t,r);return t}class bt{constructor(){O(this,"issues",[])}validate(e){if(!b(e))return this.add("INVALID_ROOT","","The document must be an object."),this.result();this.checkProperties(e,"",["type","content"],["type","content"]),e.type!=="doc"&&this.add("INVALID_ROOT","/type",'The root node type must be "doc".');const t=this.requireArray(e.content,"/content");return t==null||t.forEach((r,n)=>this.validateBlock(r,l("/content",n))),this.result()}result(){return{valid:this.issues.length===0,issues:this.issues}}add(e,t,r,n){this.issues.push(m({code:e,path:t,message:r},n?{nodeType:n}:{}))}checkProperties(e,t,r,n=[],s){const a=new Set(r);for(const o of Object.keys(e))a.has(o)||this.add("UNKNOWN_PROPERTY",l(t,o),`Property "${o}" is not allowed.`,s);for(const o of n)o in e||this.add("MISSING_PROPERTY",l(t,o),`Required property "${o}" is missing.`,s)}requireArray(e,t,r){return Array.isArray(e)?e:(this.add("INVALID_TYPE",t,"Expected an array.",r),null)}optionalArray(e,t,r){return e===void 0?[]:this.requireArray(e,t,r)}requireNonEmptyArray(e,t,r){const n=this.requireArray(e,t,r);return n&&n.length===0&&this.add("INVALID_CONTENT",t,`Node "${r}" must contain at least one child.`,r),n}requireRecord(e,t,r){return b(e)?e:(this.add("INVALID_TYPE",t,"Expected an object.",r),null)}optionalRecord(e,t,r){return e===void 0?{}:this.requireRecord(e,t,r)}requireString(e,t,r,n={}){return typeof e!="string"?(this.add("INVALID_TYPE",t,"Expected a string.",r),!1):!n.allowEmpty&&e.length===0?(this.add("INVALID_VALUE",t,"The string must not be empty.",r),!1):n.maxLength!==void 0&&e.length>n.maxLength?(this.add("INVALID_VALUE",t,`The string must contain at most ${n.maxLength} characters.`,r),!1):!0}optionalString(e,t,r,n={}){e!==void 0&&this.requireString(e,t,r,n)}requireInteger(e,t,r,n,s){if(!Number.isInteger(e))return this.add("INVALID_TYPE",t,"Expected an integer.",r),!1;const a=e;return n!==void 0&&a<n?(this.add("INVALID_VALUE",t,`Value must be at least ${n}.`,r),!1):s!==void 0&&a>s?(this.add("INVALID_VALUE",t,`Value must be at most ${s}.`,r),!1):!0}validateBlock(e,t){if(!b(e)){this.add("INVALID_TYPE",t,"A block node must be an object.");return}const r=e.type;if(typeof r!="string"){this.add("MISSING_PROPERTY",l(t,"type"),"A block node requires a type.");return}if(!pt.has(r)){this.add("UNKNOWN_NODE",l(t,"type"),`Unknown block node "${r}".`,r);return}switch(r){case"paragraph":this.validateParagraph(e,t);break;case"heading":this.validateHeading(e,t);break;case"blockquote":this.validateBlockquote(e,t);break;case"bulletList":this.validateBulletList(e,t);break;case"orderedList":this.validateOrderedList(e,t);break;case"codeBlock":this.validateCodeBlock(e,t);break;case"horizontalRule":this.checkProperties(e,t,["type"],["type"],r);break;case"image":this.validateImage(e,t);break;case"articleButton":this.validateArticleButton(e,t);break;case"table":this.validateTable(e,t);break}}validateParagraph(e,t){const r="paragraph";this.checkProperties(e,t,["type","attrs","content"],["type"],r),this.validateTextAlignAttrs(e.attrs,l(t,"attrs"),r,!1),this.validateInlineContent(e.content,l(t,"content"),r)}validateHeading(e,t){const r="heading";this.checkProperties(e,t,["type","attrs","content"],["type","attrs"],r);const n=this.requireRecord(e.attrs,l(t,"attrs"),r);n&&(this.checkProperties(n,l(t,"attrs"),["level","textAlign"],["level"],r),this.requireInteger(n.level,u(t,"attrs","level"),r,1,6),this.validateTextAlign(n.textAlign,u(t,"attrs","textAlign"),r)),this.validateInlineContent(e.content,l(t,"content"),r)}validateTextAlignAttrs(e,t,r,n){const s=n?this.requireRecord(e,t,r):this.optionalRecord(e,t,r);s&&(this.checkProperties(s,t,["textAlign"],[],r),this.validateTextAlign(s.textAlign,l(t,"textAlign"),r))}validateTextAlign(e,t,r){e!==void 0&&(typeof e!="string"?this.add("INVALID_TYPE",t,"Text alignment must be a string.",r):gt.has(e)||this.add("INVALID_VALUE",t,`Unsupported text alignment "${e}".`,r))}validateInlineContent(e,t,r){const n=this.optionalArray(e,t,r);n==null||n.forEach((s,a)=>this.validateText(s,l(t,a),!0))}validateText(e,t,r){if(!b(e)){this.add("INVALID_TYPE",t,"A text node must be an object.","text");return}const n=r?["type","text","marks"]:["type","text"];if(this.checkProperties(e,t,n,["type","text"],"text"),e.type!=="text"&&this.add("INVALID_CONTENT",l(t,"type"),"Expected a text node.","text"),this.requireString(e.text,l(t,"text"),"text"),r&&e.marks!==void 0){const s=this.requireArray(e.marks,l(t,"marks"),"text");s==null||s.forEach((a,o)=>this.validateMark(a,u(t,"marks",o)))}}validateMark(e,t){if(!b(e)){this.add("INVALID_TYPE",t,"A mark must be an object.");return}if(typeof e.type!="string"){this.add("MISSING_PROPERTY",l(t,"type"),"A mark requires a type.");return}if(mt.has(e.type)){this.checkProperties(e,t,["type"],["type"],e.type);return}if(e.type!=="link"){this.add("UNKNOWN_MARK",l(t,"type"),`Unknown mark "${e.type}".`,e.type);return}this.checkProperties(e,t,["type","attrs"],["type","attrs"],"link");const r=this.requireRecord(e.attrs,l(t,"attrs"),"link");r&&(this.checkProperties(r,l(t,"attrs"),["href","target"],["href"],"link"),this.requireString(r.href,u(t,"attrs","href"),"link"),r.target!==void 0&&r.target!=="_blank"&&r.target!=="_self"&&this.add("INVALID_VALUE",u(t,"attrs","target"),'Link target must be "_blank" or "_self".',"link"))}validateBlockquote(e,t){const r="blockquote";this.checkProperties(e,t,["type","content"],["type","content"],r);const n=this.requireNonEmptyArray(e.content,l(t,"content"),r);n==null||n.forEach((s,a)=>this.validateBlock(s,u(t,"content",a)))}validateBulletList(e,t){const r="bulletList";this.checkProperties(e,t,["type","content"],["type","content"],r);const n=this.requireNonEmptyArray(e.content,l(t,"content"),r);n==null||n.forEach((s,a)=>this.validateListItem(s,u(t,"content",a)))}validateOrderedList(e,t){const r="orderedList";this.checkProperties(e,t,["type","attrs","content"],["type","content"],r);const n=this.optionalRecord(e.attrs,l(t,"attrs"),r);n&&(this.checkProperties(n,l(t,"attrs"),["start"],[],r),n.start!==void 0&&this.requireInteger(n.start,u(t,"attrs","start"),r,1));const s=this.requireNonEmptyArray(e.content,l(t,"content"),r);s==null||s.forEach((a,o)=>this.validateListItem(a,u(t,"content",o)))}validateListItem(e,t){const r="listItem",n=this.requireRecord(e,t,r);if(!n)return;this.checkProperties(n,t,["type","content"],["type","content"],r),n.type!==r&&this.add("INVALID_CONTENT",l(t,"type"),"Expected a listItem node.",r);const s=this.requireNonEmptyArray(n.content,l(t,"content"),r);s==null||s.forEach((a,o)=>this.validateBlock(a,u(t,"content",o)))}validateCodeBlock(e,t){const r="codeBlock";this.checkProperties(e,t,["type","attrs","content"],["type"],r);const n=this.optionalRecord(e.attrs,l(t,"attrs"),r);n&&(this.checkProperties(n,l(t,"attrs"),["language"],[],r),this.optionalString(n.language,u(t,"attrs","language"),r,{maxLength:32}));const s=this.optionalArray(e.content,l(t,"content"),r);s&&(s.length>1&&this.add("INVALID_CONTENT",l(t,"content"),"A codeBlock may contain at most one text node.",r),s.forEach((a,o)=>this.validateText(a,u(t,"content",o),!1)))}validateImage(e,t){const r="image";this.checkProperties(e,t,["type","attrs"],["type","attrs"],r);const n=this.requireRecord(e.attrs,l(t,"attrs"),r);n&&(this.checkProperties(n,l(t,"attrs"),["src","alt","title","width","height","imageAlign"],["src"],r),this.requireString(n.src,u(t,"attrs","src"),r),this.optionalString(n.alt,u(t,"attrs","alt"),r,{allowEmpty:!0}),this.optionalString(n.title,u(t,"attrs","title"),r,{allowEmpty:!0}),n.width!==void 0&&this.requireInteger(n.width,u(t,"attrs","width"),r,1,1e4),n.height!==void 0&&this.requireInteger(n.height,u(t,"attrs","height"),r,1,1e4),n.imageAlign!==void 0&&(typeof n.imageAlign!="string"?this.add("INVALID_TYPE",u(t,"attrs","imageAlign"),"Image alignment must be a string.",r):yt.has(n.imageAlign)||this.add("INVALID_VALUE",u(t,"attrs","imageAlign"),`Unsupported image alignment "${n.imageAlign}".`,r)))}validateArticleButton(e,t){const r="articleButton";this.checkProperties(e,t,["type","attrs"],["type","attrs"],r);const n=this.requireRecord(e.attrs,l(t,"attrs"),r);n&&(this.checkProperties(n,l(t,"attrs"),["id","title","text","style"],["id","text","style"],r),this.requireString(n.id,u(t,"attrs","id"),r),this.optionalString(n.title,u(t,"attrs","title"),r),this.requireString(n.text,u(t,"attrs","text"),r),n.style!=="text"&&n.style!=="button"&&this.add("INVALID_VALUE",u(t,"attrs","style"),'Article button style must be "text" or "button".',r))}validateTable(e,t){const r="table";this.checkProperties(e,t,["type","content"],["type","content"],r);const n=this.requireNonEmptyArray(e.content,l(t,"content"),r);if(!n)return;let s=null;n.forEach((a,o)=>{const c=u(t,"content",o),d=this.validateTableRow(a,c);d!==null&&(s===null?s=d:d!==s&&this.add("TABLE_COLUMN_MISMATCH",l(c,"content"),`Expected ${s} table cells but received ${d}.`,"tableRow"))})}validateTableRow(e,t){var a;const r="tableRow",n=this.requireRecord(e,t,r);if(!n)return null;this.checkProperties(n,t,["type","content"],["type","content"],r),n.type!==r&&this.add("INVALID_CONTENT",l(t,"type"),"Expected a tableRow node.",r);const s=this.requireNonEmptyArray(n.content,l(t,"content"),r);return s==null||s.forEach((o,c)=>this.validateTableCell(o,u(t,"content",c))),(a=s==null?void 0:s.length)!=null?a:null}validateTableCell(e,t){const r="tableCell",n=this.requireRecord(e,t,r);if(!n)return;this.checkProperties(n,t,["type","content"],["type","content"],r),n.type!==r&&this.add("INVALID_CONTENT",l(t,"type"),"Expected a tableCell node.",r);const s=this.requireNonEmptyArray(n.content,l(t,"content"),r);s==null||s.forEach((a,o)=>this.validateBlock(a,u(t,"content",o)))}}function At(i){return new bt().validate(i)}const V=Object.freeze({version:1,validate:At,render:ft}),K=new Map([[V.version,V]]),It=Object.freeze([...K.keys()]),T=1;function j(i){return K.get(i)}function z(i,e={}){var s;const t=(s=e.protocolVersion)!=null?s:T,r=j(t);return r?r.validate(i):{valid:!1,issues:[{code:"UNSUPPORTED_PROTOCOL",path:"",message:`Article Content Protocol version ${t} is not supported.`}]}}const kt="Advertisement";function St(){return{adm:[],ads:[],loc:[]}}function Lt(){return{adm:"",ads:""}}function U(i){return`${i.code}:${i.path}:${i.message}`}function Nt(i){var o,c,d,f;const e=(c=(o=i.adm)==null?void 0:o.length)!=null?c:0,t=(f=(d=i.ads)==null?void 0:d.length)!=null?f:0,r=i.loc.length,n=e===0||e===r,s=t===0||t===r,a=e>0||t>0;return r===0&&!a||a&&n&&s?null:{code:"AD_CONFIG_LENGTH_MISMATCH",path:"/adConf",message:`Each non-empty adConf.adm or adConf.ads array must have the same length as adConf.loc, and at least one ad array must contain values when loc is non-empty. Received adm length ${e}, ads length ${t}, and loc length ${r}.`}}function Et(i,e){return e?[]:i.loc.map((t,r)=>{var n,s;return{index:r+1,location:t,adm:(n=i.adm)==null?void 0:n[r],ads:(s=i.ads)==null?void 0:s[r]}}).filter(t=>Number.isInteger(t.location)&&t.location>=1)}const Rt=N.extend({name:"ArticleContentRenderer",props:{document:{type:null,required:!0},protocolVersion:{type:Number,default:T},strict:{type:Boolean,default:!1},adConf:{type:Object,default:St},pubid:{type:Object,default:Lt},adTitle:{type:String,default:kt},imageBaseUrl:{type:String,default:I},resolveArticleButtonLink:{type:Function,default:void 0}},data(){return{reportedRuntimeIssues:new Set,reportedAdConfigIssue:null}},computed:{validation(){return z(this.document,{protocolVersion:this.protocolVersion})},adConfigIssue(){return Nt(this.adConf)},adSlots(){return Et(this.adConf,this.adConfigIssue)}},watch:{validation:{immediate:!0,deep:!0,handler(i){i.issues.forEach(e=>this.$emit("render-error",e))}},document:{deep:!0,handler(){this.reportedRuntimeIssues.clear()}},protocolVersion(){this.reportedRuntimeIssues.clear()},resolveArticleButtonLink(){this.reportedRuntimeIssues.clear()},imageBaseUrl(){this.reportedRuntimeIssues.clear()},adConfigIssue:{immediate:!0,handler(i){if(!i){this.reportedAdConfigIssue=null;return}const e=U(i);this.reportedAdConfigIssue!==e&&(this.reportedAdConfigIssue=e,console.error("[ArticleContentRenderer] Invalid adConf:",i),this.$emit("render-error",i))}}},methods:{reportRuntimeIssue(i){const e=U(i);this.reportedRuntimeIssues.has(e)||(this.reportedRuntimeIssues.add(e),this.$nextTick(()=>this.$emit("render-error",i)))}},render(i){const e=j(this.protocolVersion);if(!e||this.strict&&!this.validation.valid)return i("div",{class:"acp-render-error",attrs:{role:"alert","data-render-error":"true"}},"Invalid article content");const t=e.render(this.document,{createElement:i,adSlots:this.adSlots,admPublisherId:this.pubid.adm,adsPublisherId:this.pubid.ads,adTitle:this.adTitle,imageBaseUrl:this.imageBaseUrl,resolveArticleButtonLink:this.resolveArticleButtonLink,emitArticleButtonClick:r=>this.$emit("article-button-click",r),reportIssue:this.reportRuntimeIssue});return i("div",{class:"acp-document",attrs:{"data-node-type":"doc","data-protocol-version":this.protocolVersion}},t)}});function Pt(i,e,t,r,n,s,a,o){var c=typeof i=="function"?i.options:i;return{exports:i,options:c}}var Tt=Pt(Rt);const _=Tt.exports,_t=Object.freeze({fileFormat:"article-content-protocol",fileFormatVersion:1,mediaType:"application/vnd.article-content-protocol+json",id:"article-content-v1",version:1,name:"Article Content Protocol v1",status:"draft",rootNode:"doc",nodeTypes:Object.freeze(["doc","paragraph","heading","blockquote","bulletList","orderedList","listItem","codeBlock","horizontalRule","image","articleButton","table","tableRow","tableCell","text"]),markTypes:Object.freeze(["bold","italic","strike","underline","code","link"])}),vt={install(i){i.component("ArticleContentRenderer",_)}};exports.ARTICLE_CONTENT_PROTOCOL_V1=_t;exports.ArticleContentRenderer=_;exports.ArticleContentRendererPlugin=vt;exports.CURRENT_PROTOCOL_VERSION=T;exports.SUPPORTED_PROTOCOL_VERSIONS=It;exports.default=_;exports.validateArticleDocument=z;
|