mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
feat: improve editor + add exporting
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
const METHOD = {
|
||||
GET: 'get',
|
||||
DELETE: 'delete',
|
||||
POST: 'post',
|
||||
PUT: 'put',
|
||||
} as const
|
||||
|
||||
type ObjectValues<T> = T[keyof T]
|
||||
type Method = ObjectValues<typeof METHOD>
|
||||
|
||||
export default Method
|
||||
@@ -1,329 +0,0 @@
|
||||
import { ResponseFn } from './sbFetch'
|
||||
|
||||
export interface ISbStoriesParams
|
||||
extends Partial<ISbStoryData>,
|
||||
ISbMultipleStoriesData {
|
||||
resolve_level?: number
|
||||
_stopResolving?: boolean
|
||||
by_slugs?: string
|
||||
by_uuids?: string
|
||||
by_uuids_ordered?: string
|
||||
component?: string
|
||||
content_type?: string
|
||||
cv?: number
|
||||
datasource?: string
|
||||
dimension?: string
|
||||
excluding_fields?: string
|
||||
excluding_ids?: string
|
||||
excluding_slugs?: string
|
||||
fallback_lang?: string
|
||||
filename?: string
|
||||
filter_query?: any
|
||||
first_published_at_gt?: string
|
||||
first_published_at_lt?: string
|
||||
from_release?: string
|
||||
is_startpage?: boolean
|
||||
language?: string
|
||||
level?: number
|
||||
page?: number
|
||||
per_page?: number
|
||||
published_at_gt?: string
|
||||
published_at_lt?: string
|
||||
resolve_assets?: number
|
||||
resolve_links?: 'link' | 'url' | 'story' | '0' | '1' | 'link'
|
||||
resolve_relations?: string | string[]
|
||||
search_term?: string
|
||||
size?: string
|
||||
sort_by?: string
|
||||
starts_with?: string
|
||||
token?: string
|
||||
version?: 'draft' | 'published'
|
||||
with_tag?: string
|
||||
}
|
||||
|
||||
export interface ISbStoryParams {
|
||||
resolve_level?: number
|
||||
token?: string
|
||||
find_by?: 'uuid'
|
||||
version?: 'draft' | 'published'
|
||||
resolve_links?: 'link' | 'url' | 'story' | '0' | '1'
|
||||
resolve_relations?: string | string[]
|
||||
cv?: number
|
||||
from_release?: string
|
||||
language?: string
|
||||
fallback_lang?: string
|
||||
}
|
||||
|
||||
export interface ISbComponentType<T extends string> {
|
||||
_uid?: string
|
||||
component?: T
|
||||
_editable?: string
|
||||
}
|
||||
|
||||
export interface ISbStoryData<
|
||||
Content = ISbComponentType<string> & { [index: string]: any },
|
||||
> extends ISbMultipleStoriesData {
|
||||
alternates: ISbAlternateObject[]
|
||||
breadcrumbs?: ISbLinkURLObject[]
|
||||
content: Content
|
||||
created_at: string
|
||||
default_full_slug?: string
|
||||
default_root?: string
|
||||
disble_fe_editor?: boolean
|
||||
first_published_at?: string
|
||||
full_slug: string
|
||||
group_id: string
|
||||
id: number
|
||||
imported_at?: string
|
||||
is_folder?: boolean
|
||||
is_startpage?: boolean
|
||||
lang: string
|
||||
last_author?: {
|
||||
id: number
|
||||
userid: string
|
||||
}
|
||||
meta_data: any
|
||||
name: string
|
||||
parent?: ISbStoryData
|
||||
parent_id: number
|
||||
path?: string
|
||||
pinned?: '1' | boolean
|
||||
position: number
|
||||
published?: boolean
|
||||
published_at: string | null
|
||||
release_id?: number
|
||||
slug: string
|
||||
sort_by_date: string | null
|
||||
tag_list: string[]
|
||||
translated_slugs?: {
|
||||
path: string
|
||||
name: string | null
|
||||
lang: ISbStoryData['lang']
|
||||
}[]
|
||||
unpublished_changes?: boolean
|
||||
updated_at?: string
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export interface ISbMultipleStoriesData {
|
||||
by_ids?: string
|
||||
by_uuids?: string
|
||||
contain_component?: string
|
||||
excluding_ids?: string
|
||||
filter_query?: any
|
||||
folder_only?: boolean
|
||||
full_slug?: string
|
||||
in_release?: string
|
||||
in_trash?: boolean
|
||||
is_published?: boolean
|
||||
in_workflow_stages?: string
|
||||
page?: number
|
||||
pinned?: '1' | boolean
|
||||
search?: string
|
||||
sort_by?: string
|
||||
starts_with?: string
|
||||
story_only?: boolean
|
||||
text_search?: string
|
||||
with_parent?: number
|
||||
with_slug?: string
|
||||
with_tag?: string
|
||||
}
|
||||
|
||||
export interface ISbAlternateObject {
|
||||
id: number
|
||||
name: string
|
||||
slug: string
|
||||
published: boolean
|
||||
full_slug: string
|
||||
is_folder: boolean
|
||||
parent_id: number
|
||||
}
|
||||
|
||||
export interface ISbLinkURLObject {
|
||||
id: number
|
||||
name: string
|
||||
slug: string
|
||||
full_slug: string
|
||||
url: string
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export interface ISbStories {
|
||||
data: {
|
||||
cv: number
|
||||
links: (ISbStoryData | ISbLinkURLObject)[]
|
||||
rels: ISbStoryData[]
|
||||
stories: ISbStoryData[]
|
||||
}
|
||||
perPage: number
|
||||
total: number
|
||||
headers: any
|
||||
}
|
||||
|
||||
export interface ISbStory {
|
||||
data: {
|
||||
cv: number
|
||||
links: (ISbStoryData | ISbLinkURLObject)[]
|
||||
rels: ISbStoryData[]
|
||||
story: ISbStoryData
|
||||
}
|
||||
headers: any
|
||||
}
|
||||
|
||||
export interface IMemoryType extends ISbResult {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ICacheProvider {
|
||||
get: (key: string) => Promise<IMemoryType | void>
|
||||
set: (key: string, content: ISbResult) => Promise<void>
|
||||
getAll: () => Promise<IMemoryType | void>
|
||||
flush: () => Promise<void>
|
||||
}
|
||||
|
||||
export interface ISbCache {
|
||||
type?: 'none' | 'memory' | 'custom'
|
||||
clear?: 'auto' | 'manual'
|
||||
custom?: ICacheProvider
|
||||
}
|
||||
|
||||
export interface ISbConfig {
|
||||
accessToken?: string
|
||||
oauthToken?: string
|
||||
resolveNestedRelations?: boolean
|
||||
cache?: ISbCache
|
||||
responseInterceptor?: ResponseFn
|
||||
fetch?: typeof fetch
|
||||
timeout?: number
|
||||
headers?: any
|
||||
region?: string
|
||||
maxRetries?: number
|
||||
https?: boolean
|
||||
rateLimit?: number
|
||||
componentResolver?: (component: string, data: any) => void
|
||||
richTextSchema?: ISbSchema
|
||||
endpoint?: string
|
||||
}
|
||||
|
||||
export interface ISbResult {
|
||||
data: any
|
||||
perPage: number
|
||||
total: number
|
||||
headers: Headers
|
||||
}
|
||||
|
||||
export interface ISbResponse {
|
||||
data: any
|
||||
status: number
|
||||
statusText: string
|
||||
}
|
||||
|
||||
export interface ISbError {
|
||||
message?: string
|
||||
status?: number
|
||||
response?: ISbResponse
|
||||
}
|
||||
|
||||
export interface ISbNode extends Element {
|
||||
content: object[]
|
||||
attrs: {
|
||||
anchor?: string
|
||||
body?: Array<ISbComponentType<any>>
|
||||
href?: string
|
||||
level?: number
|
||||
linktype?: string
|
||||
custom?: LinkCustomAttributes
|
||||
[key: string]: any | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export type NodeSchema = {
|
||||
(node: ISbNode): object
|
||||
}
|
||||
|
||||
export type MarkSchema = {
|
||||
(node: ISbNode): object
|
||||
}
|
||||
|
||||
export interface ISbContentMangmntAPI<
|
||||
Content = ISbComponentType<string> & { [index: string]: any },
|
||||
> {
|
||||
story: {
|
||||
name: string
|
||||
slug: string
|
||||
content?: Content
|
||||
default_root?: boolean
|
||||
is_folder?: boolean
|
||||
parent_id?: string
|
||||
disble_fe_editor?: boolean
|
||||
path?: string
|
||||
is_startpage?: boolean
|
||||
position?: number
|
||||
first_published_at?: string
|
||||
translated_slugs_attributes?: {
|
||||
path: string
|
||||
name: string | null
|
||||
lang: ISbContentMangmntAPI['lang']
|
||||
}[]
|
||||
}
|
||||
force_update?: '1' | unknown
|
||||
release_id?: number
|
||||
publish?: '1' | unknown
|
||||
lang?: string
|
||||
}
|
||||
|
||||
export interface ISbManagmentApiResult {
|
||||
data: any
|
||||
headers: any
|
||||
}
|
||||
|
||||
export interface ISbSchema {
|
||||
nodes: any
|
||||
marks: any
|
||||
}
|
||||
|
||||
export interface ISbRichtext {
|
||||
content?: ISbRichtext[]
|
||||
marks?: ISbRichtext[]
|
||||
attrs?: any
|
||||
text?: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface LinkCustomAttributes {
|
||||
rel?: string
|
||||
title?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ISbLink {
|
||||
id?: number
|
||||
slug?: string
|
||||
name?: string
|
||||
is_folder?: boolean
|
||||
parent_id?: number
|
||||
published?: boolean
|
||||
position?: number
|
||||
uuid?: string
|
||||
is_startpage?: boolean
|
||||
}
|
||||
|
||||
export interface ISbLinks {
|
||||
links?: {
|
||||
[key: string]: ISbLink
|
||||
}
|
||||
}
|
||||
|
||||
export type ThrottleFn = {
|
||||
(...args: any): any
|
||||
}
|
||||
|
||||
export type AsyncFn = (...args: any) => [] | Promise<ISbResult>
|
||||
|
||||
export type ArrayFn = (...args: any) => void
|
||||
|
||||
export type HtmlEscapes = {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface ISbCustomFetch extends Omit<RequestInit, 'method'> {}
|
||||
@@ -1,396 +0,0 @@
|
||||
import defaultHtmlSerializer from './schema'
|
||||
import { ISbSchema, ISbRichtext } from './interfaces'
|
||||
|
||||
type HtmlEscapes = {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
type OptimizeImagesOptions =
|
||||
| boolean
|
||||
| {
|
||||
class?: string
|
||||
filters?: {
|
||||
blur?: number
|
||||
brightness?: number
|
||||
fill?: string
|
||||
format?: 'webp' | 'jpeg' | 'png'
|
||||
grayscale?: boolean
|
||||
quality?: number
|
||||
rotate?: 90 | 180 | 270
|
||||
}
|
||||
height?: number
|
||||
loading?: 'lazy' | 'eager'
|
||||
sizes?: string[]
|
||||
srcset?: (number | [number, number])[]
|
||||
width?: number
|
||||
}
|
||||
|
||||
type RenderOptions = {
|
||||
optimizeImages?: OptimizeImagesOptions
|
||||
}
|
||||
|
||||
const escapeHTML = function (string: string) {
|
||||
const htmlEscapes = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
} as HtmlEscapes
|
||||
|
||||
const reUnescapedHtml = /[&<>"']/g
|
||||
const reHasUnescapedHtml = RegExp(reUnescapedHtml.source)
|
||||
|
||||
return string && reHasUnescapedHtml.test(string)
|
||||
? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr])
|
||||
: string
|
||||
}
|
||||
|
||||
interface ISbTag extends Element {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface ISbNode {
|
||||
[key: string]: ISbSchema | ((arg: ISbRichtext) => any)
|
||||
}
|
||||
|
||||
interface ISbFunction<T extends any[], R> {
|
||||
(...args: T): R
|
||||
}
|
||||
|
||||
class RichTextResolver {
|
||||
private marks: ISbNode
|
||||
private nodes: ISbNode
|
||||
|
||||
public constructor(schema?: ISbSchema) {
|
||||
if (!schema) {
|
||||
schema = defaultHtmlSerializer as ISbSchema
|
||||
}
|
||||
|
||||
this.marks = schema.marks || []
|
||||
this.nodes = schema.nodes || []
|
||||
}
|
||||
|
||||
public addNode(key: string, schema: ISbSchema | ISbFunction<any, any>) {
|
||||
this.nodes[key] = schema
|
||||
}
|
||||
|
||||
public addMark(key: string, schema: ISbSchema) {
|
||||
this.marks[key] = schema
|
||||
}
|
||||
|
||||
public render(
|
||||
data?: ISbRichtext,
|
||||
options: RenderOptions = { optimizeImages: false }
|
||||
) {
|
||||
if (data && data.content && Array.isArray(data.content)) {
|
||||
let html = ''
|
||||
|
||||
data.content.forEach((node: any) => {
|
||||
html += this.renderNode(node)
|
||||
})
|
||||
|
||||
if (options.optimizeImages) {
|
||||
return this.optimizeImages(html, options.optimizeImages)
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`The render method must receive an Object with a "content" field.
|
||||
The "content" field must be an array of nodes as the type ISbRichtext.
|
||||
ISbRichtext:
|
||||
content?: ISbRichtext[]
|
||||
marks?: ISbRichtext[]
|
||||
attrs?: any
|
||||
text?: string
|
||||
type: string
|
||||
|
||||
Example:
|
||||
{
|
||||
content: [
|
||||
{
|
||||
content: [
|
||||
{
|
||||
text: 'Hello World',
|
||||
type: 'text'
|
||||
}
|
||||
],
|
||||
type: 'paragraph'
|
||||
}
|
||||
],
|
||||
type: 'doc'
|
||||
}`
|
||||
)
|
||||
return ''
|
||||
}
|
||||
|
||||
private optimizeImages(html: string, options: OptimizeImagesOptions): string {
|
||||
let w = 0
|
||||
let h = 0
|
||||
let imageAttributes = ''
|
||||
let filters = ''
|
||||
|
||||
if (typeof options !== 'boolean') {
|
||||
if (typeof options.width === 'number' && options.width > 0) {
|
||||
imageAttributes += `width="${options.width}" `
|
||||
w = options.width
|
||||
}
|
||||
|
||||
if (typeof options.height === 'number' && options.height > 0) {
|
||||
imageAttributes += `height="${options.height}" `
|
||||
h = options.height
|
||||
}
|
||||
|
||||
if (options.loading === 'lazy' || options.loading === 'eager') {
|
||||
imageAttributes += `loading="${options.loading}" `
|
||||
}
|
||||
|
||||
if (typeof options.class === 'string' && options.class.length > 0) {
|
||||
imageAttributes += `class="${options.class}" `
|
||||
}
|
||||
|
||||
if (options.filters) {
|
||||
if (
|
||||
typeof options.filters.blur === 'number' &&
|
||||
options.filters.blur >= 0 &&
|
||||
options.filters.blur <= 100
|
||||
) {
|
||||
filters += `:blur(${options.filters.blur})`
|
||||
}
|
||||
|
||||
if (
|
||||
typeof options.filters.brightness === 'number' &&
|
||||
options.filters.brightness >= -100 &&
|
||||
options.filters.brightness <= 100
|
||||
) {
|
||||
filters += `:brightness(${options.filters.brightness})`
|
||||
}
|
||||
|
||||
if (
|
||||
options.filters.fill &&
|
||||
(options.filters.fill.match(/[0-9A-Fa-f]{6}/g) ||
|
||||
options.filters.fill === 'transparent')
|
||||
) {
|
||||
filters += `:fill(${options.filters.fill})`
|
||||
}
|
||||
|
||||
if (
|
||||
options.filters.format &&
|
||||
['webp', 'png', 'jpeg'].includes(options.filters.format)
|
||||
) {
|
||||
filters += `:format(${options.filters.format})`
|
||||
}
|
||||
|
||||
if (
|
||||
typeof options.filters.grayscale === 'boolean' &&
|
||||
options.filters.grayscale
|
||||
) {
|
||||
filters += ':grayscale()'
|
||||
}
|
||||
|
||||
if (
|
||||
typeof options.filters.quality === 'number' &&
|
||||
options.filters.quality >= 0 &&
|
||||
options.filters.quality <= 100
|
||||
) {
|
||||
filters += `:quality(${options.filters.quality})`
|
||||
}
|
||||
|
||||
if (
|
||||
options.filters.rotate &&
|
||||
[90, 180, 270].includes(options.filters.rotate)
|
||||
) {
|
||||
filters += `:rotate(${options.filters.rotate})`
|
||||
}
|
||||
|
||||
if (filters.length > 0) filters = '/filters' + filters
|
||||
}
|
||||
}
|
||||
|
||||
if (imageAttributes.length > 0) {
|
||||
html = html.replace(/<img/g, `<img ${imageAttributes.trim()}`)
|
||||
}
|
||||
|
||||
if (typeof options !== 'boolean' && (options.sizes || options.srcset)) {
|
||||
html = html.replace(/<img.*?src=["|'](.*?)["|']/g, (value: string) => {
|
||||
const url = value.match(
|
||||
/a.storyblok.com\/f\/(\d+)\/([^.]+)\.(gif|jpg|jpeg|png|tif|tiff|bmp)/g
|
||||
)
|
||||
|
||||
if (url && url.length > 0) {
|
||||
const imageAttributes = {
|
||||
srcset: options.srcset
|
||||
?.map((value) => {
|
||||
if (typeof value === 'number') {
|
||||
return `//${url}/m/${value}x0${filters} ${value}w`
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value.length === 2) {
|
||||
let w = 0
|
||||
let h = 0
|
||||
if (typeof value[0] === 'number') w = value[0]
|
||||
if (typeof value[1] === 'number') h = value[1]
|
||||
return `//${url}/m/${w}x${h}${filters} ${w}w`
|
||||
}
|
||||
})
|
||||
.join(', '),
|
||||
sizes: options.sizes?.map((size) => size).join(', '),
|
||||
}
|
||||
|
||||
let renderImageAttributes = ''
|
||||
if (imageAttributes.srcset) {
|
||||
renderImageAttributes += `srcset="${imageAttributes.srcset}" `
|
||||
}
|
||||
if (imageAttributes.sizes) {
|
||||
renderImageAttributes += `sizes="${imageAttributes.sizes}" `
|
||||
}
|
||||
|
||||
return value.replace(/<img/g, `<img ${renderImageAttributes.trim()}`)
|
||||
}
|
||||
|
||||
return value
|
||||
})
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
private renderNode(item: ISbRichtext) {
|
||||
const html = []
|
||||
|
||||
if (item.marks) {
|
||||
item.marks.forEach((m: any) => {
|
||||
const mark = this.getMatchingMark(m)
|
||||
|
||||
if (mark && mark.tag !== '') {
|
||||
html.push(this.renderOpeningTag(mark.tag))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const node = this.getMatchingNode(item)
|
||||
|
||||
if (node && node.tag) {
|
||||
html.push(this.renderOpeningTag(node.tag))
|
||||
}
|
||||
|
||||
if (item.content) {
|
||||
item.content.forEach((content: any) => {
|
||||
html.push(this.renderNode(content))
|
||||
})
|
||||
} else if (item.text) {
|
||||
html.push(escapeHTML(item.text))
|
||||
} else if (node && node.singleTag) {
|
||||
html.push(this.renderTag(node.singleTag, ' /'))
|
||||
} else if (node && node.html) {
|
||||
html.push(node.html)
|
||||
} else if (item.type === 'emoji') {
|
||||
html.push(this.renderEmoji(item))
|
||||
}
|
||||
|
||||
if (node && node.tag) {
|
||||
html.push(this.renderClosingTag(node.tag))
|
||||
}
|
||||
|
||||
if (item.marks) {
|
||||
item.marks
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.forEach((m) => {
|
||||
const mark = this.getMatchingMark(m)
|
||||
|
||||
if (mark && mark.tag !== '') {
|
||||
html.push(this.renderClosingTag(mark.tag))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return html.join('')
|
||||
}
|
||||
|
||||
private renderTag(tags: ISbTag[], ending: string) {
|
||||
if (tags.constructor === String) {
|
||||
return `<${tags}${ending}>`
|
||||
}
|
||||
|
||||
const all = tags.map((tag) => {
|
||||
if (tag.constructor === String) {
|
||||
return `<${tag}${ending}>`
|
||||
} else {
|
||||
let h = `<${tag.tag}`
|
||||
if (tag.attrs) {
|
||||
for (const key in tag.attrs) {
|
||||
const value = tag.attrs[key]
|
||||
if (value !== null) {
|
||||
h += ` ${key}="${value}"`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `${h}${ending}>`
|
||||
}
|
||||
})
|
||||
return all.join('')
|
||||
}
|
||||
|
||||
private renderOpeningTag(tags: ISbTag[]) {
|
||||
return this.renderTag(tags, '')
|
||||
}
|
||||
|
||||
private renderClosingTag(tags: ISbTag[]) {
|
||||
if (tags.constructor === String) {
|
||||
return `</${tags}>`
|
||||
}
|
||||
|
||||
const all = tags
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map((tag) => {
|
||||
if (tag.constructor === String) {
|
||||
return `</${tag}>`
|
||||
} else {
|
||||
return `</${tag.tag}>`
|
||||
}
|
||||
})
|
||||
|
||||
return all.join('')
|
||||
}
|
||||
|
||||
private getMatchingNode(item: ISbRichtext) {
|
||||
const node = this.nodes[item.type]
|
||||
if (typeof node === 'function') {
|
||||
return node(item)
|
||||
}
|
||||
}
|
||||
|
||||
private getMatchingMark(item: ISbRichtext) {
|
||||
const node = this.marks[item.type]
|
||||
if (typeof node === 'function') {
|
||||
return node(item)
|
||||
}
|
||||
}
|
||||
|
||||
private renderEmoji(item: ISbRichtext) {
|
||||
if (item.attrs.emoji) {
|
||||
return item.attrs.emoji
|
||||
}
|
||||
|
||||
const emojiImageContainer = [
|
||||
{
|
||||
tag: 'img',
|
||||
attrs: {
|
||||
src: item.attrs.fallbackImage,
|
||||
draggable: 'false',
|
||||
loading: 'lazy',
|
||||
align: 'absmiddle',
|
||||
},
|
||||
},
|
||||
] as unknown as ISbTag[]
|
||||
|
||||
return this.renderTag(emojiImageContainer, ' /')
|
||||
}
|
||||
}
|
||||
|
||||
export default RichTextResolver
|
||||
@@ -1,192 +0,0 @@
|
||||
import { SbHelpers } from './sbHelpers'
|
||||
|
||||
import {
|
||||
ISbResponse,
|
||||
ISbError,
|
||||
ISbStoriesParams,
|
||||
ISbCustomFetch,
|
||||
} from './interfaces'
|
||||
import Method from './constants'
|
||||
|
||||
export type ResponseFn = {
|
||||
(arg?: ISbResponse | any): any
|
||||
}
|
||||
|
||||
interface ISbFetch {
|
||||
baseURL: string
|
||||
timeout?: number
|
||||
headers: Headers
|
||||
responseInterceptor?: ResponseFn
|
||||
fetch?: typeof fetch
|
||||
}
|
||||
|
||||
class SbFetch {
|
||||
private baseURL: string
|
||||
private timeout?: number
|
||||
private headers: Headers
|
||||
private responseInterceptor?: ResponseFn
|
||||
private fetch: typeof fetch
|
||||
private ejectInterceptor?: boolean
|
||||
private url: string
|
||||
private parameters: ISbStoriesParams
|
||||
private fetchOptions: ISbCustomFetch
|
||||
|
||||
public constructor($c: ISbFetch) {
|
||||
this.baseURL = $c.baseURL
|
||||
this.headers = $c.headers || new Headers()
|
||||
this.timeout = $c?.timeout ? $c.timeout * 1000 : 0
|
||||
this.responseInterceptor = $c.responseInterceptor
|
||||
this.fetch = (...args: [any]) =>
|
||||
$c.fetch ? $c.fetch(...args) : fetch(...args)
|
||||
this.ejectInterceptor = false
|
||||
this.url = ''
|
||||
this.parameters = {} as ISbStoriesParams
|
||||
this.fetchOptions = {}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url string
|
||||
* @param params ISbStoriesParams
|
||||
* @returns Promise<ISbResponse | Error>
|
||||
*/
|
||||
public get(url: string, params: ISbStoriesParams) {
|
||||
this.url = url
|
||||
this.parameters = params
|
||||
return this._methodHandler('get')
|
||||
}
|
||||
|
||||
public post(url: string, params: ISbStoriesParams) {
|
||||
this.url = url
|
||||
this.parameters = params
|
||||
return this._methodHandler('post')
|
||||
}
|
||||
|
||||
public put(url: string, params: ISbStoriesParams) {
|
||||
this.url = url
|
||||
this.parameters = params
|
||||
return this._methodHandler('put')
|
||||
}
|
||||
|
||||
public delete(url: string, params: ISbStoriesParams) {
|
||||
this.url = url
|
||||
this.parameters = params
|
||||
return this._methodHandler('delete')
|
||||
}
|
||||
|
||||
private async _responseHandler(res: Response) {
|
||||
const headers: string[] = []
|
||||
const response = {
|
||||
data: {},
|
||||
headers: {},
|
||||
status: 0,
|
||||
statusText: '',
|
||||
}
|
||||
|
||||
if (res.status !== 204) {
|
||||
await res.json().then(($r) => {
|
||||
response.data = $r
|
||||
})
|
||||
}
|
||||
|
||||
for (const pair of res.headers.entries()) {
|
||||
headers[pair[0] as any] = pair[1]
|
||||
}
|
||||
|
||||
response.headers = { ...headers }
|
||||
response.status = res.status
|
||||
response.statusText = res.statusText
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private async _methodHandler(
|
||||
method: Method
|
||||
): Promise<ISbResponse | ISbError> {
|
||||
let urlString = `${this.baseURL}${this.url}`
|
||||
|
||||
let body = null
|
||||
|
||||
if (method === 'get') {
|
||||
const helper = new SbHelpers()
|
||||
urlString = `${this.baseURL}${this.url}?${helper.stringify(
|
||||
this.parameters
|
||||
)}`
|
||||
} else {
|
||||
body = JSON.stringify(this.parameters)
|
||||
}
|
||||
|
||||
const url = new URL(urlString)
|
||||
|
||||
const controller = new AbortController()
|
||||
const { signal } = controller
|
||||
|
||||
let timeout
|
||||
|
||||
if (this.timeout) {
|
||||
timeout = setTimeout(() => controller.abort(), this.timeout)
|
||||
}
|
||||
|
||||
try {
|
||||
const fetchResponse = await this.fetch(`${url}`, {
|
||||
method,
|
||||
headers: this.headers,
|
||||
body,
|
||||
signal,
|
||||
...this.fetchOptions,
|
||||
})
|
||||
|
||||
if (this.timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
const response = (await this._responseHandler(
|
||||
fetchResponse
|
||||
)) as ISbResponse
|
||||
|
||||
if (this.responseInterceptor && !this.ejectInterceptor) {
|
||||
return this._statusHandler(this.responseInterceptor(response))
|
||||
} else {
|
||||
return this._statusHandler(response)
|
||||
}
|
||||
} catch (err: any) {
|
||||
const error: ISbError = {
|
||||
message: err,
|
||||
}
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
public setFetchOptions(fetchOptions: ISbCustomFetch = {}) {
|
||||
if (Object.keys(fetchOptions).length > 0 && 'method' in fetchOptions) {
|
||||
delete fetchOptions.method
|
||||
}
|
||||
this.fetchOptions = { ...fetchOptions }
|
||||
}
|
||||
|
||||
public eject() {
|
||||
this.ejectInterceptor = true
|
||||
}
|
||||
|
||||
private _statusHandler(res: ISbResponse): Promise<ISbResponse | ISbError> {
|
||||
const statusOk = /20[0-6]/g
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (statusOk.test(`${res.status}`)) {
|
||||
return resolve(res)
|
||||
}
|
||||
|
||||
const error: ISbError = {
|
||||
message: res.statusText,
|
||||
status: res.status,
|
||||
response: Array.isArray(res.data)
|
||||
? res.data[0]
|
||||
: res.data.error || res.data.slug,
|
||||
}
|
||||
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default SbFetch
|
||||
@@ -1,101 +0,0 @@
|
||||
import { ISbStoriesParams, ISbResult, AsyncFn, HtmlEscapes } from './interfaces'
|
||||
interface ISbParams extends ISbStoriesParams {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type ArrayFn = (...args: any) => void
|
||||
|
||||
type FlatMapFn = (...args: any) => [] | any
|
||||
|
||||
type RangeFn = (...args: any) => []
|
||||
|
||||
export class SbHelpers {
|
||||
public isCDNUrl = (url = '') => url.indexOf('/cdn/') > -1
|
||||
|
||||
public getOptionsPage = (
|
||||
options: ISbStoriesParams,
|
||||
perPage = 25,
|
||||
page = 1
|
||||
) => {
|
||||
return {
|
||||
...options,
|
||||
per_page: perPage,
|
||||
page,
|
||||
}
|
||||
}
|
||||
|
||||
public delay = (ms: number) => new Promise((res) => setTimeout(res, ms))
|
||||
|
||||
public arrayFrom = (length = 0, func: ArrayFn) => [...Array(length)].map(func)
|
||||
|
||||
public range = (start = 0, end = start): Array<any> => {
|
||||
const length = Math.abs(end - start) || 0
|
||||
const step = start < end ? 1 : -1
|
||||
return this.arrayFrom(length, (_, i: number) => i * step + start)
|
||||
}
|
||||
|
||||
public asyncMap = async (arr: RangeFn[], func: AsyncFn) =>
|
||||
Promise.all(arr.map(func))
|
||||
|
||||
public flatMap = (arr: ISbResult[] = [], func: FlatMapFn) =>
|
||||
arr.map(func).reduce((xs, ys) => [...xs, ...ys], [])
|
||||
|
||||
/**
|
||||
* @method stringify
|
||||
* @param {Object} params
|
||||
* @param {String} prefix
|
||||
* @param {Boolean} isArray
|
||||
* @return {String} Stringified object
|
||||
*/
|
||||
public stringify(
|
||||
params: ISbParams,
|
||||
prefix?: string,
|
||||
isArray?: boolean
|
||||
): string {
|
||||
const pairs = []
|
||||
for (const key in params) {
|
||||
if (!Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
continue
|
||||
}
|
||||
const value = params[key]
|
||||
const enkey = isArray ? '' : encodeURIComponent(key)
|
||||
let pair
|
||||
if (typeof value === 'object') {
|
||||
pair = this.stringify(
|
||||
value,
|
||||
prefix ? prefix + encodeURIComponent('[' + enkey + ']') : enkey,
|
||||
Array.isArray(value)
|
||||
)
|
||||
} else {
|
||||
pair =
|
||||
(prefix ? prefix + encodeURIComponent('[' + enkey + ']') : enkey) +
|
||||
'=' +
|
||||
encodeURIComponent(value)
|
||||
}
|
||||
pairs.push(pair)
|
||||
}
|
||||
return pairs.join('&')
|
||||
}
|
||||
|
||||
/**
|
||||
* @method escapeHTML
|
||||
* @param {String} string text to be parsed
|
||||
* @return {String} Text parsed
|
||||
*/
|
||||
public escapeHTML = function (string: string) {
|
||||
const htmlEscapes = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
} as HtmlEscapes
|
||||
|
||||
const reUnescapedHtml = /[&<>"']/g
|
||||
const reHasUnescapedHtml = RegExp(reUnescapedHtml.source)
|
||||
|
||||
return string && reHasUnescapedHtml.test(string)
|
||||
? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr])
|
||||
: string
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
import { ISbNode, NodeSchema, MarkSchema, ISbComponentType } from './interfaces'
|
||||
import { SbHelpers } from './sbHelpers'
|
||||
|
||||
const pick = function (attrs: Attrs, allowed: string[]) {
|
||||
const h = {} as Attrs
|
||||
|
||||
for (const key in attrs) {
|
||||
const value = attrs[key]
|
||||
if (allowed.indexOf(key) > -1 && value !== null) {
|
||||
h[key] = value
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
const isEmailLinkType = (type: string) => type === 'email'
|
||||
|
||||
type Attrs = {
|
||||
[key: string]: string | number | Array<ISbComponentType<any>>
|
||||
}
|
||||
|
||||
// nodes
|
||||
const horizontal_rule: NodeSchema = () => {
|
||||
return {
|
||||
singleTag: 'hr',
|
||||
}
|
||||
}
|
||||
const blockquote: NodeSchema = () => {
|
||||
return {
|
||||
tag: 'blockquote',
|
||||
}
|
||||
}
|
||||
const bullet_list: NodeSchema = () => {
|
||||
return {
|
||||
tag: 'ul',
|
||||
}
|
||||
}
|
||||
const code_block: NodeSchema = (node: ISbNode) => {
|
||||
return {
|
||||
tag: [
|
||||
'pre',
|
||||
{
|
||||
tag: 'code',
|
||||
attrs: node.attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
const hard_break: NodeSchema = () => {
|
||||
return {
|
||||
singleTag: 'br',
|
||||
}
|
||||
}
|
||||
const heading: NodeSchema = (node: ISbNode) => {
|
||||
return {
|
||||
tag: `h${node.attrs.level}`,
|
||||
}
|
||||
}
|
||||
|
||||
const image: NodeSchema = (node: ISbNode) => {
|
||||
return {
|
||||
singleTag: [
|
||||
{
|
||||
tag: 'img',
|
||||
attrs: pick(node.attrs, ['src', 'alt', 'title']),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
const list_item: NodeSchema = () => {
|
||||
return {
|
||||
tag: 'li',
|
||||
}
|
||||
}
|
||||
const ordered_list: NodeSchema = () => {
|
||||
return {
|
||||
tag: 'ol',
|
||||
}
|
||||
}
|
||||
const paragraph: NodeSchema = () => {
|
||||
return {
|
||||
tag: 'p',
|
||||
}
|
||||
}
|
||||
|
||||
const emoji: NodeSchema = (node: ISbNode) => {
|
||||
const attrs = {
|
||||
['data-type']: 'emoji',
|
||||
['data-name']: node.attrs.name,
|
||||
emoji: node.attrs.emoji
|
||||
}
|
||||
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// marks
|
||||
const bold: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'b',
|
||||
}
|
||||
}
|
||||
const strike: MarkSchema = () => {
|
||||
return {
|
||||
tag: 's',
|
||||
}
|
||||
}
|
||||
const underline: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'u',
|
||||
}
|
||||
}
|
||||
const strong: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'strong',
|
||||
}
|
||||
}
|
||||
const code: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'code',
|
||||
}
|
||||
}
|
||||
const italic: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'i',
|
||||
}
|
||||
}
|
||||
const link: MarkSchema = (node: ISbNode) => {
|
||||
if (!node.attrs) {
|
||||
return {
|
||||
tag: '',
|
||||
}
|
||||
}
|
||||
const escapeHTML = new SbHelpers().escapeHTML
|
||||
const attrs = { ...node.attrs }
|
||||
const { linktype = 'url' } = node.attrs
|
||||
delete attrs.linktype
|
||||
|
||||
if (attrs.href) {
|
||||
attrs.href = escapeHTML(node.attrs.href || '')
|
||||
}
|
||||
|
||||
if (isEmailLinkType(linktype)) {
|
||||
attrs.href = `mailto:${attrs.href}`
|
||||
}
|
||||
|
||||
if (attrs.anchor) {
|
||||
attrs.href = `${attrs.href}#${attrs.anchor}`
|
||||
delete attrs.anchor
|
||||
}
|
||||
|
||||
if (attrs.custom) {
|
||||
for (const key in attrs.custom) {
|
||||
attrs[key] = attrs.custom[key]
|
||||
}
|
||||
delete attrs.custom
|
||||
}
|
||||
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'a',
|
||||
attrs: attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const styled: MarkSchema = (node: ISbNode) => {
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: node.attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const subscript: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'sub',
|
||||
}
|
||||
}
|
||||
|
||||
const superscript: MarkSchema = () => {
|
||||
return {
|
||||
tag: 'sup'
|
||||
}
|
||||
}
|
||||
|
||||
const anchor: MarkSchema = (node: ISbNode) => {
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs: node.attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const highlight: MarkSchema = (node: ISbNode) => {
|
||||
if (!node.attrs?.color) return {
|
||||
tag: '',
|
||||
}
|
||||
|
||||
const attrs = {
|
||||
['style']: `background-color:${node.attrs.color};`,
|
||||
}
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const textStyle: MarkSchema = (node: ISbNode) => {
|
||||
if (!node.attrs?.color) return {
|
||||
tag: '',
|
||||
}
|
||||
|
||||
const attrs = {
|
||||
['style']: `color:${node.attrs.color}`,
|
||||
}
|
||||
return {
|
||||
tag: [
|
||||
{
|
||||
tag: 'span',
|
||||
attrs,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
nodes: {
|
||||
horizontal_rule,
|
||||
blockquote,
|
||||
bullet_list,
|
||||
code_block,
|
||||
hard_break,
|
||||
heading,
|
||||
image,
|
||||
list_item,
|
||||
ordered_list,
|
||||
paragraph,
|
||||
emoji
|
||||
},
|
||||
marks: {
|
||||
bold,
|
||||
strike,
|
||||
underline,
|
||||
strong,
|
||||
code,
|
||||
italic,
|
||||
link,
|
||||
styled,
|
||||
subscript,
|
||||
superscript,
|
||||
anchor,
|
||||
highlight,
|
||||
textStyle,
|
||||
},
|
||||
}
|
||||
@@ -18,6 +18,7 @@ browser.storage.local.get().then(({ DarkMode }) => {
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.setAttribute("type", "text/css");
|
||||
style.classList.add('iconFamily')
|
||||
style.innerHTML = `
|
||||
@font-face {
|
||||
font-family: 'IconFamily';
|
||||
|
||||
@@ -9,3 +9,7 @@ body {
|
||||
.bn-container > [contenteditable="true"] {
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
.ProseMirror {
|
||||
background: transparent !important;
|
||||
}
|
||||
@@ -2,18 +2,16 @@ import "@blocknote/core/fonts/inter.css";
|
||||
import { useCreateBlockNote } from "@blocknote/react";
|
||||
import { BlockNoteView } from "@blocknote/mantine";
|
||||
import "@blocknote/mantine/style.css";
|
||||
//import { generateHTML } from '@tiptap/html'
|
||||
import './Editor.css'
|
||||
|
||||
|
||||
export default function Editor() {
|
||||
const editor = useCreateBlockNote({});
|
||||
|
||||
/* debounce on change to export to html */
|
||||
editor._tiptapEditor.on('update', () => {
|
||||
window.parent.postMessage({
|
||||
type: 'message-html',
|
||||
data: editor._tiptapEditor.getHTML()
|
||||
data: editor._tiptapEditor.getHTML(),
|
||||
}, '*')
|
||||
})
|
||||
|
||||
|
||||
@@ -6,41 +6,66 @@ export default async function handleComposeMessage(): Promise<void> {
|
||||
console.log('COMPOSE MESSAGE!');
|
||||
|
||||
const container: HTMLElement | null = document.querySelector('.pane .footer .pillbox');
|
||||
const firstButton: HTMLButtonElement | null = document.querySelector('.pane .footer .pillbox button.first') as HTMLButtonElement;
|
||||
const simpleEditorButton: HTMLButtonElement | null = document.querySelector('.pane .footer .pillbox button.first') as HTMLButtonElement;
|
||||
|
||||
if (container && firstButton) {
|
||||
if (container && simpleEditorButton) {
|
||||
const buttonHTML = /* html */ `
|
||||
<button class="button">
|
||||
<button class="button" id="betterEditorButton">
|
||||
Better Editor
|
||||
</button>
|
||||
`;
|
||||
const button: HTMLElement = stringToHTML(buttonHTML);
|
||||
|
||||
// Append the new button as the second child of options
|
||||
firstButton.parentNode?.insertBefore(button.firstChild as Node, firstButton.nextSibling);
|
||||
// Check if the button already exists
|
||||
if (!container.querySelector('#betterEditorButton')) {
|
||||
// Insert the new button after the Simple editor button
|
||||
simpleEditorButton.insertAdjacentElement('afterend', button.firstElementChild as HTMLElement);
|
||||
}
|
||||
|
||||
// Add click event listeners to both buttons
|
||||
container.addEventListener('click', (event: Event) => handleButtonClick(event, container, firstButton));
|
||||
// Add click event listeners to the container (event delegation)
|
||||
container.addEventListener('click', handleButtonClick);
|
||||
}
|
||||
}
|
||||
|
||||
function handleButtonClick(event: Event, container: HTMLElement, firstButton: HTMLButtonElement): void {
|
||||
function handleButtonClick(event: MouseEvent): void {
|
||||
console.log('handleButtonClick', event);
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
if (target && target.classList.contains('button')) {
|
||||
const isBetterEditorButton = target.textContent?.trim() === 'Better Editor';
|
||||
if (target.tagName !== 'BUTTON') return;
|
||||
|
||||
if (isBetterEditorButton) {
|
||||
activateBetterEditor(container, firstButton);
|
||||
} else {
|
||||
deactivateBetterEditor(container, firstButton);
|
||||
}
|
||||
const container = target.closest('.pillbox') as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
const simpleEditorButton = container.querySelector('button.first') as HTMLButtonElement;
|
||||
const betterEditorButton = container.querySelector('#betterEditorButton') as HTMLButtonElement;
|
||||
|
||||
if (!simpleEditorButton || !betterEditorButton) {
|
||||
console.error('Could not find Simple Editor or Better Editor buttons');
|
||||
return;
|
||||
}
|
||||
|
||||
const isBetterEditorButton = target === betterEditorButton;
|
||||
const isSimpleEditorButton = target === simpleEditorButton;
|
||||
|
||||
if (isBetterEditorButton) {
|
||||
activateBetterEditor(simpleEditorButton, betterEditorButton);
|
||||
} else if (isSimpleEditorButton) {
|
||||
activateSimpleEditor(simpleEditorButton, betterEditorButton);
|
||||
} else {
|
||||
deactivateBetterEditor(simpleEditorButton, betterEditorButton);
|
||||
}
|
||||
|
||||
container.querySelectorAll('button').forEach(btn => btn.classList.remove('depressed'));
|
||||
target.classList.add('depressed');
|
||||
}
|
||||
|
||||
function activateBetterEditor(container: HTMLElement, firstButton: HTMLButtonElement): void {
|
||||
firstButton.classList.remove('depressed');
|
||||
container.children[1]?.classList.add('depressed');
|
||||
function activateBetterEditor(simpleEditorButton: HTMLButtonElement, betterEditorButton: HTMLButtonElement): void {
|
||||
// Programmatically click the Simple Editor button first
|
||||
simpleEditorButton.click();
|
||||
|
||||
// Then proceed with Better Editor activation
|
||||
simpleEditorButton.classList.remove('depressed');
|
||||
betterEditorButton.classList.add('depressed');
|
||||
|
||||
const ckeInner = document.querySelector('.pane .cke_inner') as HTMLElement;
|
||||
if (ckeInner) ckeInner.style.display = 'none';
|
||||
@@ -63,23 +88,33 @@ function activateBetterEditor(container: HTMLElement, firstButton: HTMLButtonEle
|
||||
window.addEventListener('message', (event) => handleEditorMessage(event, ckeEditor), { once: true });
|
||||
}
|
||||
|
||||
function deactivateBetterEditor(container: HTMLElement, firstButton: HTMLButtonElement): void {
|
||||
function activateSimpleEditor(simpleEditorButton: HTMLButtonElement, betterEditorButton: HTMLButtonElement): void {
|
||||
simpleEditorButton.classList.add('depressed');
|
||||
betterEditorButton.classList.remove('depressed');
|
||||
|
||||
const ckeInner = document.querySelector('.pane .cke_inner') as HTMLElement;
|
||||
const extensionEditor = document.querySelector('.extension-editor') as HTMLIFrameElement;
|
||||
|
||||
if (ckeInner && extensionEditor) {
|
||||
ckeInner.style.display = 'block';
|
||||
firstButton.classList.add('depressed');
|
||||
container.children[1]?.classList.remove('depressed');
|
||||
extensionEditor.style.display = 'none';
|
||||
}
|
||||
if (ckeInner) ckeInner.style.removeProperty('display')
|
||||
if (extensionEditor) extensionEditor.style.display = 'none'
|
||||
}
|
||||
|
||||
function deactivateBetterEditor(simpleEditorButton: HTMLButtonElement, betterEditorButton: HTMLButtonElement): void {
|
||||
const ckeInner = document.querySelector('.pane .cke_inner') as HTMLElement;
|
||||
const ckeContents = document.querySelector('.pane .cke_contents') as HTMLElement;
|
||||
const extensionEditor = document.querySelector('.extension-editor') as HTMLIFrameElement;
|
||||
|
||||
if (ckeInner) ckeInner.style.removeProperty('display');
|
||||
if (ckeContents) ckeContents.style.removeProperty('display');
|
||||
if (extensionEditor) extensionEditor.style.removeProperty('display');
|
||||
|
||||
simpleEditorButton.classList.remove('depressed');
|
||||
betterEditorButton.classList.remove('depressed');
|
||||
}
|
||||
|
||||
function handleEditorMessage(event: MessageEvent, ckeEditor: HTMLIFrameElement): void {
|
||||
if (!event.origin.includes(browser.runtime.id) || event.data.type !== "message-html") return;
|
||||
|
||||
console.log('Message from extension editor', event.data.data);
|
||||
|
||||
if (ckeEditor.contentDocument) {
|
||||
ckeEditor.contentDocument.body.innerHTML = event.data.data + `<style>${styles}</style>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user