diff --git a/index.js b/index.js deleted file mode 100644 index 6538b9c..0000000 --- a/index.js +++ /dev/null @@ -1,110 +0,0 @@ -const fetch = require('isomorphic-fetch') - -class Telegraph { - constructor (token, opts) { - this.options = Object.assign({ - token: token, - apiRoot: 'https://api.telegra.ph' - }, opts) - } - - set token (token) { - this.options.token = token - } - - callService (method, payload) { - return fetch(`${this.options.apiRoot}/${method}`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(payload) - }) - .then(unwrap) - } - - createAccount (shortName, name, url) { - return this.callService('createAccount', { - short_name: shortName, - author_name: name, - author_url: url - }) - } - - createPage (title, content, authorName, authorUrl, returnContent) { - return this.callService('createPage', { - access_token: this.options.token, - title: title, - author_name: authorName, - author_url: authorUrl, - content: content, - return_content: returnContent - }) - } - - editAccountInfo (shortName, name, url) { - return this.callService('editAccountInfo', { - access_token: this.options.token, - short_name: shortName, - author_name: name, - author_url: url - }) - } - - editPage (path, title, content, authorName, authorUrl, returnContent) { - return this.callService(`editPage/${path}`, { - access_token: this.options.token, - title: title, - author_name: authorName, - author_url: authorUrl, - content: content, - return_content: returnContent - }) - } - - getPage (path, returnContent) { - return this.callService(`getPage/${path}`, { - access_token: this.options.token, - return_content: returnContent - }) - } - - getViews (path, year, month, day, hour) { - return this.callService(`getViews/${path}`, { - access_token: this.options.token, - year: year, - month: month, - day: day, - hour: hour - }) - } - - getPageList (offset, limit) { - return this.callService('getPageList', { - access_token: this.options.token, - offset: offset, - limit: limit - }) - } - - revokeAccessToken () { - return this.callService('revokeAccessToken', { - access_token: this.options.token - }) - } -} - -function unwrap (res) { - if (!res.ok) { - const err = new Error(res.statusText || 'Error calling telegra.ph') - err.statusCode = res.status - throw err - } - return res.json() - .then((json) => { - if (('ok' in json && !json.ok)) { - throw new Error(json.error || 'Error calling telegra.ph') - } - return json.result - }) -} - -module.exports = Telegraph diff --git a/package.json b/package.json index 31ef5ef..03de1c1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "telegra.ph", "version": "1.0.1", "description": "Tiny API helper for Telegra.ph", - "main": "index.js", + "main": "dist/index.js", "scripts": { "test": "eslint . && ava test" }, @@ -25,8 +25,9 @@ }, "homepage": "https://github.com/telegraf/telegra.ph#readme", "files": [ - "index.js", - "typings/*.ts" + "dist", + "LICENSE", + "readme.md" ], "devDependencies": { "ava": "^1.2.0", @@ -37,8 +38,5 @@ "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0" - }, - "dependencies": { - "isomorphic-fetch": "^2.2.1" } } diff --git a/readme.md b/readme.md index 85fa85c..1b46c5e 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ # 📝 telegra.ph + [![NPM Version](https://img.shields.io/npm/v/telegra.ph.svg?style=flat-square)](https://www.npmjs.com/package/telegra.ph) [![Build Status](https://img.shields.io/travis/telegraf/telegra.ph.svg?branch=master&style=flat-square)](https://travis-ci.org/telegraf/telegra.ph) @@ -10,55 +11,79 @@ Tiny API helper for Telegra.ph. ## Installation -```js +```bash $ npm install telegra.ph --save ``` ## Example +### Create an account + ```js -const Telegraph = require('telegra.ph') +import Telegraph from "telegra.ph" const client = new Telegraph() -client.createAccount().then((account) => { - client.token = account.access_token - return client.getPageList() -}).then((pages) => console.log(pages)) +const account = client.createAccount() +client.token = account.access_token +const pages = await client.getPageList() +console.log(pages) ``` +### Use existing account + ```js -const Telegraph = require('telegra.ph') +import Telegraph from "telegra.ph" const client = new Telegraph(process.env.TOKEN) -client.getPageList().then((pages) => console.log(pages)) +const pages = await client.getPageList() +console.log(pages) ``` -## API documentation +### Use arbitrary HTTP Client +```js +import Telegraph from "telegra.ph" +import { Fetch } from "telegra.ph/clients" + +const client = new Telegraph(process.env.TOKEN, { client: Fetch }) + +const pages = await client.getPageList() +console.log(pages) +``` + +## API documentation #### [`createAccount`](http://telegra.ph/api#createAccount) + `.createAccount(shortName, name, url)` #### [`createPage`](http://telegra.ph/api#createPage) + `.createPage(title, content, authorName, authorUrl, returnContent)` #### [`editAccountInfo`](http://telegra.ph/api#editAccountInfo) + `.editAccountInfo(shortName, name, url)` #### [`editPage`](http://telegra.ph/api#editPage) + `.editPage(path, title, content, authorName, authorUrl, returnContent)` #### [`getPage`](http://telegra.ph/api#getPage) + `.getPage(path, returnContent)` #### [`getViews`](http://telegra.ph/api#getViews) + `.getViews(path, year, month, day, hour)` #### [`getPageList`](http://telegra.ph/api#getPageList) + `.getPageList(path, offset, limit)` #### [`revokeAccessToken`](http://telegra.ph/api#revokeAccessToken) + `.revokeAccessToken()` diff --git a/src/clients/fetch.ts b/src/clients/fetch.ts new file mode 100644 index 0000000..258b0f4 --- /dev/null +++ b/src/clients/fetch.ts @@ -0,0 +1,33 @@ +import { Client } from "./index" + +export class Fetch implements Client { + constructor(public baseURL: string) {} + + async get(path: string): Promise { + const response = await fetch(`${this.baseURL}${path}`) + const json = await response.json() + if (json.ok === false) { + if (json.error) throw new Error(json.error) + else throw new Error(json) + } + if (json.result) return json.result + else return json + } + + async post(path: string, body: unknown): Promise { + const response = await fetch(`${this.baseURL}${path}`, { + method: "POST", + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + }, + }) + const json = await response.json() + if (json.ok === false) { + if (json.error) throw new Error(json.error) + else throw new Error(json) + } + if (json.result) return json.result + else return json + } +} diff --git a/src/clients/https.ts b/src/clients/https.ts new file mode 100644 index 0000000..c462628 --- /dev/null +++ b/src/clients/https.ts @@ -0,0 +1,50 @@ +import type { Client } from "./" +import https from "https" + +export class HTTPS implements Client { + constructor(public baseURL: string) {} + + protected request(method: string, path: string, data: unknown): Promise { + return new Promise((resolve, reject) => { + const req = https.request( + `${this.baseURL}${path}`, + { + method, + headers: { + "Content-Type": "application/json", + }, + }, + (res) => { + let body = "" + res.on("data", (chunk) => { + body += chunk + }) + res.on("end", () => { + try { + const obj = JSON.parse(body) + if (obj.ok === false) { + if (obj.error) reject(new Error(obj.error)) + else reject(new Error(obj)) + } + if (obj.result) resolve(obj.result) + else resolve(obj) + } catch (err) { + reject(err) + } + }) + } + ) + req.on("error", reject) + req.write(JSON.stringify(data)) + req.end() + }) + } + + post(path: string, data: unknown): Promise { + return this.request("POST", path, data) + } + + get(path: string, data: unknown): Promise { + return this.request("GET", path, data) + } +} diff --git a/src/clients/index.ts b/src/clients/index.ts new file mode 100644 index 0000000..76e50e1 --- /dev/null +++ b/src/clients/index.ts @@ -0,0 +1,7 @@ +export interface Client { + post: (path: string, data: unknown) => Promise + get: (path: string, data: unknown) => Promise +} + +export { Fetch } from "./fetch" +export { HTTPS } from "./https" diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..08861b1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,212 @@ +import type { Account, Node, Page, PageList, PageViews } from "./typings" +import { type Client, HTTPS } from "./clients" + +export type Options = { + token?: string + apiRoot?: string + client?: new (baseURL: string) => Client +} + +/** + * Initialize new Telegraph app. + * @param {string} token Access token + * @param {Options} opts options + * @example + * new Telegraph(token, opts) + */ +export default class Telegraph { + private options: Options + protected client: Client + + constructor(token: string, opts?: Options) { + if (opts?.apiRoot) { + opts.apiRoot = opts.apiRoot.endsWith("/") ? opts.apiRoot : opts.apiRoot + "/" + } + this.options = { + apiRoot: "https://api.telegra.ph/", + client: HTTPS, + ...opts, + token, + } + + if (!this.options.apiRoot) throw new Error("apiRoot is required") + if (!this.options.client) throw new Error("client is required") + + this.client = new this.options.client(this.options.apiRoot) + } + + public set token(token: string) { + this.options.token = token + } + + /** + * Use this method to create a new Telegraph account. Most users only need one account, but this can be useful for channel administrators who would like to keep individual author names and profile links for each of their channels. + * @param {string} shortName **1-32 characters.** **Required.** Account name, helps users with several accounts remember which they are currently using. Displayed to the user above the "Edit/Publish" button on Telegra.ph, other users don't see this name. + * @param {string} name **0-128 characters.** Default author name used when creating new articles. + * @param {string | undefined} url **0-512 characters.** Default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with the regular fields and an additional `access_token` field. + * @example + * const telegraph = new Telegraph(token) + * const account = await telegraph.createAccount('Sandbox', 'Anonymous') + */ + public createAccount(shortName: string, name: string, url?: string): Promise { + return this.client.post("createAccount", { + short_name: shortName, + author_name: name, + author_url: url, + }) + } + + /** + * Use this method to create a new Telegraph page. + * @see https://telegra.ph/api#createPage + * @param {string} title **1-256 characters.** **Required.** Page title. + * @param {Array} content Content of the page. + * @param {string} authorName **0-128 characters.** Author name, displayed below the article's title. + * @param {string | undefined} authorUrl **0-512 characters.** Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @param {boolean | undefined} returnContent If `true`, a `content` field will be returned in the Page object (see: [Content format](https://telegra.ph/api#Content-format)). + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * const content = [ { tag: 'p', children: [ 'Hello, world!' ] } ] + * const page = await telegraph.createPage('Sample Page', content, 'Anonymous') + */ + public createPage(title: string, content: Array, authorName: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.client.post("createPage", { + access_token: this.options.token, + title: title, + author_name: authorName, + author_url: authorUrl, + content: content, + return_content: returnContent, + }) + } + + /** + * Use this method to update information about a Telegraph account. Pass only the parameters that you want to edit. + * @see https://telegra.ph/api#editAccountInfo + * @param {string | undefined} shortName New account name. + * @param {string | undefined} name New default author name used when creating new articles. + * @param {string | undefined} url New default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with the default fields. + * @example + * const telegraph = new Telegraph(token) + * const account = await telegraph.editAccountInfo(, 'Anonymous', 'https://telegra.ph/api') + */ + public editAccountInfo( + shortName: string | undefined = undefined, + name: string | undefined = undefined, + url: string | undefined = undefined + ): Promise { + return this.client.post("editAccountInfo", { + access_token: this.options.token, + short_name: shortName, + author_name: name, + author_url: url, + }) + } + + /** + * Use this method to edit an existing Telegraph page. + * @see https://telegra.ph/api#editPage + * @param {string} path **Required.** Path to the page. + * @param {string} title **1-256 characters.** **Required.** Page title. + * @param {Array} content **up to 64 KB.** **Required.** Content of the page. + * @param {string | undefined} authorName **0-128 characters.** Author name, displayed below the article's title. + * @param {string | undefined} authorUrl **0-512 characters.** Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @param {boolean | undefined} returnContent If `true`, a `content` field will be returned in the Page object (see: [Content format](https://telegra.ph/api#Content-format)). + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * + * const content = [ { tag: 'p', children: [ 'Hello, world!' ] } ] + * const page = await telegraph.createPage('Sample Page', content, 'Anonymous') + * + * const newContent = [ { tag: 'b', children: [ 'Hello, world!' ] } ] + * const editedPage = await telegraph.editPage(page.path, 'Sample Page', newContent, 'Anonymous') + */ + public editPage(path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.client.post(`editPage/${path}`, { + access_token: this.options.token, + title: title, + author_name: authorName, + author_url: authorUrl, + content: content, + return_content: returnContent, + }) + } + + /** + * Use this method to get a Telegraph page. + * @see https://telegra.ph/api#getPage + * @param {string} path **Required.** Path to the Telegraph page (in the format `Title-12-31`, i.e. everything that comes after `http://telegra.ph/`). + * @param {boolean | undefined} returnContent If `true`, content field will be returned in Page object. + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * const page = await telegraph.getPage('Sample-Page-12-15', true) + */ + public getPage(path: string, returnContent?: boolean): Promise { + return this.client.post(`getPage/${path}`, { + access_token: this.options.token, + return_content: returnContent, + }) + } + + /** + * Use this method to get the number of views for a Telegraph article. By default, the total number of page views will be returned + * @see https://telegra.ph/api#getViews + * @param {string} path **Required.** Path to the Telegraph page (in the format `Title-12-31`, where `12` is the month and `31` the day the article was first published). + * @param {number | undefined} year Required if `month` is passed. If passed, the number of page views for the requested year will be returned. + * @param {number | undefined} month Required if `day` is passed. If passed, the number of page views for the requested month will be returned. + * @param {number | undefined} day Required if `hour` is passed. If passed, the number of page views for the requested day will be returned. + * @param {number | undefined} hour If passed, the number of page views for the requested hour will be returned. + * @returns {Promise} On success, returns a [`PageViews`](https://telegra.ph/api#PageViews) object. + * @example + * const telegraph = new Telegraph(token) + * const views = await telegraph.getViews('Sample-Page-12-15', 2016, 12) + */ + getViews(path: string): Promise + getViews(path: string, year: number): Promise + getViews(path: string, year: number, month: number): Promise + getViews(path: string, year: number, month: number, day: number): Promise + getViews(path: string, year: number, month: number, day: number, hour: number): Promise + public getViews(path: string, year?: number, month?: number, day?: number, hour?: number): Promise { + return this.client.post(`getViews/${path}`, { + access_token: this.options.token, + year: year, + month: month, + day: day, + hour: hour, + }) + } + + /** + * Use this method to get a list of pages belonging to a Telegraph account. + * @see https://telegra.ph/api#getPageList + * @param {number | undefined} offset Sequential number of the first page to be returned. + * @param {number | undefined} limit Limits the number of pages to be retrieved. + * @returns {Promise} On success, returns a [`PageList`](https://telegra.ph/api#PageList) object. + * @example + * const telegraph = new Telegraph(token) + * const pageList = await telegraph.getPageList(undefined, 3) + */ + public getPageList(offset?: number, limit?: number): Promise { + return this.client.post("getPageList", { + access_token: this.options.token, + offset: offset, + limit: limit, + }) + } + + /** + * Use this method to revoke access_token and generate a new one, for example, if the user would like to reset all connected sessions, or you have reasons to believe the token was compromised. + * @see https://telegra.ph/api#revokeAccessToken + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with new `access_token` and `auth_url` fields. + */ + public revokeAccessToken(): Promise { + return this.client.post("revokeAccessToken", { + access_token: this.options.token, + }) + } +} diff --git a/src/typings/account.ts b/src/typings/account.ts new file mode 100644 index 0000000..7555485 --- /dev/null +++ b/src/typings/account.ts @@ -0,0 +1,30 @@ +/** + * This object represents a Telegraph account. + * @see https://telegra.ph/api#Account + */ +export interface Account { + /** + * Account name, helps users with several accounts remember which they are currently using. Displayed to the user above the "Edit/Publish" button on Telegra.ph, other users don't see this name. + */ + short_name: string + /** + * Default author name used when creating new articles. + */ + author_name: string + /** + * Default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + */ + author_url?: string + /** + * Optional. Only returned by the `createAccount` and `revokeAccessToken` method. Access token of the Telegraph account. + */ + access_token?: string + /** + * Optional. URL to authorize a browser on telegra.ph and connect it to a Telegraph account. This URL is valid for only one use and for 5 minutes only. + */ + auth_url?: string + /** + * Optional. Number of pages belonging to the Telegraph account. + */ + page_count?: number +} diff --git a/src/typings/index.ts b/src/typings/index.ts new file mode 100644 index 0000000..d4ff9e0 --- /dev/null +++ b/src/typings/index.ts @@ -0,0 +1,6 @@ +export { Account } from "./account" +export { Node } from "./node" +export { NodeElement } from "./nodeElement" +export { Page } from "./page" +export { PageList } from "./pageList" +export { PageViews } from "./pageViews" diff --git a/src/typings/node.ts b/src/typings/node.ts new file mode 100644 index 0000000..8f51f11 --- /dev/null +++ b/src/typings/node.ts @@ -0,0 +1,6 @@ +import { NodeElement } from "./nodeElement" +/** + * This abstract object represents a DOM Node. It can be a String which represents a DOM text node or a NodeElement object. + * @see https://telegra.ph/api#Node + */ +export type Node = string | NodeElement diff --git a/src/typings/nodeElement.ts b/src/typings/nodeElement.ts new file mode 100644 index 0000000..5f80103 --- /dev/null +++ b/src/typings/nodeElement.ts @@ -0,0 +1,46 @@ +import { Node } from "./node" +/** + * This object represents a DOM element node. + * @see https://telegra.ph/api#NodeElement + */ +export interface NodeElement { + /** + * Name of the DOM element. Available tags: `a`, `aside`, `b`, `blockquote`, `br`, `code`, `em`, `figcaption`, `figure`, `h3`, `h4`, `hr`, `i`, `iframe`, `img`, `li`, `ol`, `p`, `pre`, `s`, `strong`, `u`, `ul`, `video`. + */ + tag: + | "a" + | "aside" + | "b" + | "blockquote" + | "br" + | "code" + | "em" + | "figcaption" + | "figure" + | "h3" + | "h4" + | "hr" + | "i" + | "iframe" + | "img" + | "li" + | "ol" + | "p" + | "pre" + | "s" + | "strong" + | "u" + | "ul" + | "video" + /** + * Optional. Attributes of the DOM element. Key of object represents name of attribute, value represents value of attribute. Available attributes: `href`, `src`. + */ + attrs?: { + href?: string + src?: string + } + /** + * Optional. List of child nodes for the DOM element. + */ + children?: Array | string +} diff --git a/src/typings/page.ts b/src/typings/page.ts new file mode 100644 index 0000000..8bc4f1a --- /dev/null +++ b/src/typings/page.ts @@ -0,0 +1,46 @@ +/** + * This object represents a page on Telegraph. + * @see https://telegra.ph/api#Page + */ +export interface Page { + /** + * Path to the page. + */ + path: string + /** + * URL of the page. + */ + url: string + /** + * Title of the page. + */ + title: string + /** + * Description of the page. + */ + description: string + /** + * Optional. Name of the author, displayed below the title. + */ + author_name?: string + /** + * Optional. Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + */ + author_url?: string + /** + * Optional. Image URL of the page. + */ + image_url?: string + /** + * Optional. Content of the page. + */ + content?: Array + /** + * Number of page views for the page. + */ + views: number + /** + * Optional. Only returned if access_token passed. True, if the target Telegraph account can edit the page. + */ + can_edit?: boolean +} diff --git a/src/typings/pageList.ts b/src/typings/pageList.ts new file mode 100644 index 0000000..50bae84 --- /dev/null +++ b/src/typings/pageList.ts @@ -0,0 +1,15 @@ +import type { Page } from "./page" +/** + * This object represents a list of Telegraph articles belonging to an account. Most recently created articles first. + * @see https://telegra.ph/api#PageList + */ +export interface PageList { + /** + * Total number of pages belonging to the target Telegraph account. + */ + total_count: number + /** + * Requested pages of the target Telegraph account. + */ + pages: Array +} diff --git a/src/typings/pageViews.ts b/src/typings/pageViews.ts new file mode 100644 index 0000000..5a995c2 --- /dev/null +++ b/src/typings/pageViews.ts @@ -0,0 +1,10 @@ +/** + * This object represents the number of page views for a Telegraph article. + * @see https://telegra.ph/api#PageViews + */ +export interface PageViews { + /** + * Number of page views for the target page. + */ + views: number +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6e8f0c3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "declaration": true, + "esModuleInterop": true, + "noImplicitAny": false, + "outDir": "./dist", + "importsNotUsedAsValues": "remove" + }, + "include": ["src/**/*"] +} diff --git a/typings/index.d.ts b/typings/index.d.ts deleted file mode 100644 index 555be0f..0000000 --- a/typings/index.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as tph from './telegraph.d' - -export interface Telegraph { - - token: string - - callService (method: string, payload: object): Promise - - createAccount (shortName: string, name?: string, url?: string): Promise - - createPage (title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise - - editAccountInfo (shortName?: string, name?: string, url?: string): Promise - - editPage (path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise - - getPage (path: string, returnContent?: boolean): Promise - - getViews (path: string, year: number, month: number, day: number, hour?: number): Promise - - getPageList (offset?: number, limit?: number): Promise - - revokeAccessToken (): Promise -} - - -export interface TelegraphConstructor { - /** - * Initialize new Telegraph app. - * @param token Access token - * @param opts options - * @example - * new Telegraph(token, opts) - */ - new (token: string, options?: TelegraphOptions): Telegraph -} - -export interface TelegraphOptions { - - token?: string - - apiRoot?: string - -} - -export const Telegraph: TelegraphConstructor - -export default Telegraph \ No newline at end of file diff --git a/typings/telegraph.d.ts b/typings/telegraph.d.ts deleted file mode 100644 index 299c6b2..0000000 --- a/typings/telegraph.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface Account { - short_name: string - author_name: string - author_url?: string - access_token?: string - auth_url?: string - page_count?: number -} - -export interface PageList { - total_count: number - pages: Array -} - -export interface Page { - path: string - url: string - title: string - description: string - author_name?: string - author_url?: string - image_url?: string - content?: Array - views: number - can_edit?: boolean -} - -export interface PageViews { - views: number -} - -export type Node = string | NodeElement - -export interface NodeElement { - tag: 'a' | 'aside' | 'b' | 'blockquote' | 'br' | 'code' | 'em' | 'figcaption' | 'figure' | 'h3' | 'h4' | 'hr' | 'i' | 'iframe' | 'img' | 'li' | 'ol' | 'p' | 'pre' | 's' | 'strong' | 'u' | 'ul' | 'video' - attrs?: { - href?: string - src?: string - } - children?: Array | string -} \ No newline at end of file