Merge remote-tracking branch 'origin/develop' into vue3-again

* origin/develop: (475 commits)
  Apply 1 suggestion(s) to 1 file(s)
  Update dependency @ungap/event-target to v0.2.3
  Update package.json
  fix broken icons after FA upgrade
  Update Font Awesome
  Update dependency webpack-dev-middleware to v3.7.3
  Update dependency vuelidate to v0.7.7
  Pin dependency @kazvmoe-infra/pinch-zoom-element to 1.2.0
  lint
  Make media modal buttons larger
  Add English translation for hide tooltip
  Add hide button to media modal
  Lint
  Prevent hiding media viewer if swiped over SwipeClick
  Fix webkit image blurs
  Fix video in media modal not displaying properly
  Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403
  Remove image box-shadow in media modal
  Clean up debug code for image pinch zoom
  Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm
  ...
This commit is contained in:
Henry Jameson 2022-03-16 21:00:20 +02:00
commit cd4ad2df11
184 changed files with 11589 additions and 3790 deletions

View file

@ -44,6 +44,7 @@ export const parseUser = (data) => {
const mastoShort = masto && !data.hasOwnProperty('avatar')
output.id = String(data.id)
output._original = data // used for server-side settings
if (masto) {
output.screen_name = data.acct
@ -54,17 +55,20 @@ export const parseUser = (data) => {
return output
}
output.name = data.display_name
output.name_html = addEmojis(escape(data.display_name), data.emojis)
output.emoji = data.emojis
output.name = escape(data.display_name)
output.name_html = output.name
output.name_unescaped = data.display_name
output.description = data.note
output.description_html = addEmojis(data.note, data.emojis)
// TODO cleanup this shit, output.description is overriden with source data
output.description_html = data.note
output.fields = data.fields
output.fields_html = data.fields.map(field => {
return {
name: addEmojis(escape(field.name), data.emojis),
value: addEmojis(field.value, data.emojis)
name: escape(field.name),
value: field.value
}
})
output.fields_text = data.fields.map(field => {
@ -205,7 +209,7 @@ export const parseUser = (data) => {
// Convert punycode to unicode for UI
output.screen_name_ui = output.screen_name
if (output.screen_name.includes('@')) {
if (output.screen_name && output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
let unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {
@ -239,16 +243,6 @@ export const parseAttachment = (data) => {
return output
}
export const addEmojis = (string, emojis) => {
const matchOperatorsRegex = /[|\\{}()[\]^$+*?.-]/g
return emojis.reduce((acc, emoji) => {
const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&')
return acc.replace(
new RegExp(`:${regexSafeShortCode}:`, 'g'),
`<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />`
)
}, string)
}
export const parseStatus = (data) => {
const output = {}
@ -266,7 +260,8 @@ export const parseStatus = (data) => {
output.type = data.reblog ? 'retweet' : 'status'
output.nsfw = data.sensitive
output.statusnet_html = addEmojis(data.content, data.emojis)
output.raw_html = data.content
output.emojis = data.emojis
output.tags = data.tags
@ -293,13 +288,13 @@ export const parseStatus = (data) => {
output.retweeted_status = parseStatus(data.reblog)
}
output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
output.summary_raw_html = escape(data.spoiler_text)
output.external_url = data.url
output.poll = data.poll
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
title_html: addEmojis(escape(field.title), data.emojis)
title_html: escape(field.title)
}))
}
output.pinned = data.pinned
@ -325,7 +320,7 @@ export const parseStatus = (data) => {
output.nsfw = data.nsfw
}
output.statusnet_html = data.statusnet_html
output.raw_html = data.statusnet_html
output.text = data.text
output.in_reply_to_status_id = data.in_reply_to_status_id
@ -444,11 +439,8 @@ export const parseChatMessage = (message) => {
output.id = message.id
output.created_at = new Date(message.created_at)
output.chat_id = message.chat_id
if (message.content) {
output.content = addEmojis(message.content, message.emojis)
} else {
output.content = ''
}
output.emojis = message.emojis
output.content = message.content
if (message.attachment) {
output.attachments = [parseAttachment(message.attachment)]
} else {

View file

@ -1,52 +1,58 @@
import { find } from 'lodash'
const createFaviconService = () => {
let favimg, favcanvas, favcontext, favicon
const favicons = []
const faviconWidth = 128
const faviconHeight = 128
const badgeRadius = 32
const initFaviconService = () => {
const nodes = document.getElementsByTagName('link')
favicon = find(nodes, node => node.rel === 'icon')
if (favicon) {
favcanvas = document.createElement('canvas')
favcanvas.width = faviconWidth
favcanvas.height = faviconHeight
favimg = new Image()
favimg.src = favicon.href
favcontext = favcanvas.getContext('2d')
}
const nodes = document.querySelectorAll('link[rel="icon"]')
nodes.forEach(favicon => {
if (favicon) {
const favcanvas = document.createElement('canvas')
favcanvas.width = faviconWidth
favcanvas.height = faviconHeight
const favimg = new Image()
favimg.crossOrigin = 'anonymous'
favimg.src = favicon.href
const favcontext = favcanvas.getContext('2d')
favicons.push({ favcanvas, favimg, favcontext, favicon })
}
})
}
const isImageLoaded = (img) => img.complete && img.naturalHeight !== 0
const clearFaviconBadge = () => {
if (!favimg || !favcontext || !favicon) return
if (favicons.length === 0) return
favicons.forEach(({ favimg, favcanvas, favcontext, favicon }) => {
if (!favimg || !favcontext || !favicon) return
favcontext.clearRect(0, 0, faviconWidth, faviconHeight)
if (isImageLoaded(favimg)) {
favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
}
favicon.href = favcanvas.toDataURL('image/png')
favcontext.clearRect(0, 0, faviconWidth, faviconHeight)
if (isImageLoaded(favimg)) {
favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
}
favicon.href = favcanvas.toDataURL('image/png')
})
}
const drawFaviconBadge = () => {
if (!favimg || !favcontext || !favcontext) return
if (favicons.length === 0) return
clearFaviconBadge()
favicons.forEach(({ favimg, favcanvas, favcontext, favicon }) => {
if (!favimg || !favcontext || !favcontext) return
const style = getComputedStyle(document.body)
const badgeColor = `${style.getPropertyValue('--badgeNotification') || 'rgb(240, 100, 100)'}`
const style = getComputedStyle(document.body)
const badgeColor = `${style.getPropertyValue('--badgeNotification') || 'rgb(240, 100, 100)'}`
if (isImageLoaded(favimg)) {
favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
}
favcontext.fillStyle = badgeColor
favcontext.beginPath()
favcontext.arc(faviconWidth - badgeRadius, badgeRadius, badgeRadius, 0, 2 * Math.PI, false)
favcontext.fill()
favicon.href = favcanvas.toDataURL('image/png')
if (isImageLoaded(favimg)) {
favcontext.drawImage(favimg, 0, 0, favimg.width, favimg.height, 0, 0, faviconWidth, faviconHeight)
}
favcontext.fillStyle = badgeColor
favcontext.beginPath()
favcontext.arc(faviconWidth - badgeRadius, badgeRadius, badgeRadius, 0, 2 * Math.PI, false)
favcontext.fill()
favicon.href = favcanvas.toDataURL('image/png')
})
}
return {

View file

@ -2,6 +2,10 @@
// or the entire service could be just mimetype service that only operates
// on mimetypes and not files. Currently the naming is confusing.
const fileType = mimetype => {
if (mimetype.match(/flash/)) {
return 'flash'
}
if (mimetype.match(/text\/html/)) {
return 'html'
}

View file

@ -4,9 +4,15 @@ const DIRECTION_RIGHT = [1, 0]
const DIRECTION_UP = [0, -1]
const DIRECTION_DOWN = [0, 1]
const BUTTON_LEFT = 0
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
const touchCoord = touch => [touch.screenX, touch.screenY]
const touchEventCoord = e => touchCoord(e.touches[0])
const pointerEventCoord = e => [e.clientX, e.clientY]
const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
@ -61,6 +67,132 @@ const updateSwipe = (event, gesture) => {
gesture._swiping = false
}
class SwipeAndClickGesture {
// swipePreviewCallback(offsets: Array[Number])
// offsets: the offset vector which the underlying component should move, from the starting position
// swipeEndCallback(sign: 0|-1|1)
// sign: if the swipe does not meet the threshold, 0
// if the swipe meets the threshold in the positive direction, 1
// if the swipe meets the threshold in the negative direction, -1
constructor ({
direction,
// swipeStartCallback
swipePreviewCallback,
swipeEndCallback,
swipeCancelCallback,
swipelessClickCallback,
threshold = 30,
perpendicularTolerance = 1.0,
disableClickThreshold = 1
}) {
const nop = () => {}
this.direction = direction
this.swipePreviewCallback = swipePreviewCallback || nop
this.swipeEndCallback = swipeEndCallback || nop
this.swipeCancelCallback = swipeCancelCallback || nop
this.swipelessClickCallback = swipelessClickCallback || nop
this.threshold = typeof threshold === 'function' ? threshold : () => threshold
this.disableClickThreshold = typeof disableClickThreshold === 'function' ? disableClickThreshold : () => disableClickThreshold
this.perpendicularTolerance = perpendicularTolerance
this._reset()
}
_reset () {
this._startPos = [0, 0]
this._pointerId = -1
this._swiping = false
this._swiped = false
this._preventNextClick = false
}
start (event) {
// Only handle left click
if (event.button !== BUTTON_LEFT) {
return
}
this._startPos = pointerEventCoord(event)
this._pointerId = event.pointerId
this._swiping = true
this._swiped = false
}
move (event) {
if (this._swiping && this._pointerId === event.pointerId) {
this._swiped = true
const coord = pointerEventCoord(event)
const delta = deltaCoord(this._startPos, coord)
this.swipePreviewCallback(delta)
}
}
cancel (event) {
if (!this._swiping || this._pointerId !== event.pointerId) {
return
}
this.swipeCancelCallback()
}
end (event) {
if (!this._swiping) {
return
}
if (this._pointerId !== event.pointerId) {
return
}
this._swiping = false
// movement too small
const coord = pointerEventCoord(event)
const delta = deltaCoord(this._startPos, coord)
const sign = (() => {
if (vectorLength(delta) < this.threshold()) {
return 0
}
// movement is opposite from direction
const isPositive = dotProduct(delta, this.direction) > 0
// movement perpendicular to direction is too much
const towardsDir = project(delta, this.direction)
const perpendicularDir = perpendicular(this.direction)
const towardsPerpendicular = project(delta, perpendicularDir)
if (
vectorLength(towardsDir) * this.perpendicularTolerance <
vectorLength(towardsPerpendicular)
) {
return 0
}
return isPositive ? 1 : -1
})()
if (this._swiped) {
this.swipeEndCallback(sign)
}
this._reset()
// Only a mouse will fire click event when
// the end point is far from the starting point
// so for other kinds of pointers do not check
// whether we have swiped
if (vectorLength(delta) >= this.disableClickThreshold() && event.pointerType === 'mouse') {
this._preventNextClick = true
}
}
click (event) {
if (!this._preventNextClick) {
this.swipelessClickCallback()
}
this._reset()
}
}
const GestureService = {
DIRECTION_LEFT,
DIRECTION_RIGHT,
@ -68,7 +200,8 @@ const GestureService = {
DIRECTION_DOWN,
swipeGesture,
beginSwipe,
updateSwipe
updateSwipe,
SwipeAndClickGesture
}
export default GestureService

View file

@ -0,0 +1,136 @@
import { getTagName } from './utility.service.js'
/**
* This is a tiny purpose-built HTML parser/processor. This basically detects
* any type of visual newline and converts entire HTML into a array structure.
*
* Text nodes are represented as object with single property - text - containing
* the visual line. Intended usage is to process the array with .map() in which
* map function returns a string and resulting array can be converted back to html
* with a .join('').
*
* Generally this isn't very useful except for when you really need to either
* modify visual lines (greentext i.e. simple quoting) or do something with
* first/last line.
*
* known issue: doesn't handle CDATA so nested CDATA might not work well
*
* @param {Object} input - input data
* @return {(string|{ text: string })[]} processed html in form of a list.
*/
export const convertHtmlToLines = (html = '') => {
// Elements that are implicitly self-closing
// https://developer.mozilla.org/en-US/docs/Glossary/empty_element
const emptyElements = new Set([
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
])
// Block-level element (they make a visual line)
// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements
const blockElements = new Set([
'address', 'article', 'aside', 'blockquote', 'details', 'dialog', 'dd',
'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li', 'main',
'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul'
])
// br is very weird in a way that it's technically not block-level, it's
// essentially converted to a \n (or \r\n). There's also wbr but it doesn't
// guarantee linebreak, only suggest it.
const linebreakElements = new Set(['br'])
const visualLineElements = new Set([
...blockElements.values(),
...linebreakElements.values()
])
// All block-level elements that aren't empty elements, i.e. not <hr>
const nonEmptyElements = new Set(visualLineElements)
// Difference
for (let elem of emptyElements) {
nonEmptyElements.delete(elem)
}
// All elements that we are recognizing
const allElements = new Set([
...nonEmptyElements.values(),
...emptyElements.values()
])
let buffer = [] // Current output buffer
const level = [] // How deep we are in tags and which tags were there
let textBuffer = '' // Current line content
let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
if (textBuffer.trim().length > 0) {
buffer.push({ level: [...level], text: textBuffer })
} else {
buffer.push(textBuffer)
}
textBuffer = ''
}
const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing
flush()
buffer.push(tag)
}
const handleOpen = (tag) => { // handles opening tags
flush()
buffer.push(tag)
level.unshift(getTagName(tag))
}
const handleClose = (tag) => { // handles closing tags
if (level[0] === getTagName(tag)) {
flush()
buffer.push(tag)
level.shift()
} else { // Broken case
textBuffer += tag
}
}
for (let i = 0; i < html.length; i++) {
const char = html[i]
if (char === '<' && tagBuffer === null) {
tagBuffer = char
} else if (char !== '>' && tagBuffer !== null) {
tagBuffer += char
} else if (char === '>' && tagBuffer !== null) {
tagBuffer += char
const tagFull = tagBuffer
tagBuffer = null
const tagName = getTagName(tagFull)
if (allElements.has(tagName)) {
if (linebreakElements.has(tagName)) {
handleBr(tagFull)
} else if (nonEmptyElements.has(tagName)) {
if (tagFull[1] === '/') {
handleClose(tagFull)
} else if (tagFull[tagFull.length - 2] === '/') {
// self-closing
handleBr(tagFull)
} else {
handleOpen(tagFull)
}
} else {
textBuffer += tagFull
}
} else {
textBuffer += tagFull
}
} else if (char === '\n') {
handleBr(char)
} else {
textBuffer += char
}
}
if (tagBuffer) {
textBuffer += tagBuffer
}
flush()
return buffer
}

View file

@ -0,0 +1,98 @@
import { getTagName } from './utility.service.js'
import { unescape } from 'lodash'
/**
* This is a not-so-tiny purpose-built HTML parser/processor. This parses html
* and converts it into a tree structure representing tag openers/closers and
* children.
*
* Structure follows this pattern: [opener, [...children], closer] except root
* node which is just [...children]. Text nodes can only be within children and
* are represented as strings.
*
* Intended use is to convert HTML structure and then recursively iterate over it
* most likely using a map. Very useful for dynamically rendering html replacing
* tags with JSX elements in a render function.
*
* known issue: doesn't handle CDATA so CDATA might not work well
* known issue: doesn't handle HTML comments
*
* @param {Object} input - input data
* @return {string} processed html
*/
export const convertHtmlToTree = (html = '') => {
// Elements that are implicitly self-closing
// https://developer.mozilla.org/en-US/docs/Glossary/empty_element
const emptyElements = new Set([
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
])
// TODO For future - also parse HTML5 multi-source components?
const buffer = [] // Current output buffer
const levels = [['', buffer]] // How deep we are in tags and which tags were there
let textBuffer = '' // Current line content
let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
const getCurrentBuffer = () => {
return levels[levels.length - 1][1]
}
const flushText = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
if (textBuffer === '') return
getCurrentBuffer().push(textBuffer)
textBuffer = ''
}
const handleSelfClosing = (tag) => {
getCurrentBuffer().push([tag])
}
const handleOpen = (tag) => {
const curBuf = getCurrentBuffer()
const newLevel = [unescape(tag), []]
levels.push(newLevel)
curBuf.push(newLevel)
}
const handleClose = (tag) => {
const currentTag = levels[levels.length - 1]
if (getTagName(levels[levels.length - 1][0]) === getTagName(tag)) {
currentTag.push(tag)
levels.pop()
} else {
getCurrentBuffer().push(tag)
}
}
for (let i = 0; i < html.length; i++) {
const char = html[i]
if (char === '<' && tagBuffer === null) {
flushText()
tagBuffer = char
} else if (char !== '>' && tagBuffer !== null) {
tagBuffer += char
} else if (char === '>' && tagBuffer !== null) {
tagBuffer += char
const tagFull = tagBuffer
tagBuffer = null
const tagName = getTagName(tagFull)
if (tagFull[1] === '/') {
handleClose(tagFull)
} else if (emptyElements.has(tagName) || tagFull[tagFull.length - 2] === '/') {
// self-closing
handleSelfClosing(tagFull)
} else {
handleOpen(tagFull)
}
} else {
textBuffer += char
}
}
if (tagBuffer) {
textBuffer += tagBuffer
}
flushText()
return buffer
}

View file

@ -0,0 +1,73 @@
/**
* Extract tag name from tag opener/closer.
*
* @param {String} tag - tag string, i.e. '<a href="...">'
* @return {String} - tagname, i.e. "div"
*/
export const getTagName = (tag) => {
const result = /(?:<\/(\w+)>|<(\w+)\s?.*?\/?>)/gi.exec(tag)
return result && (result[1] || result[2])
}
/**
* Extract attributes from tag opener.
*
* @param {String} tag - tag string, i.e. '<a href="...">'
* @return {Object} - map of attributes key = attribute name, value = attribute value
* attributes without values represented as boolean true
*/
export const getAttrs = tag => {
const innertag = tag
.substring(1, tag.length - 1)
.replace(new RegExp('^' + getTagName(tag)), '')
.replace(/\/?$/, '')
.trim()
const attrs = Array.from(innertag.matchAll(/([a-z0-9-]+)(?:=("[^"]+?"|'[^']+?'))?/gi))
.map(([trash, key, value]) => [key, value])
.map(([k, v]) => {
if (!v) return [k, true]
return [k, v.substring(1, v.length - 1)]
})
return Object.fromEntries(attrs)
}
/**
* Finds shortcodes in text
*
* @param {String} text - original text to find emojis in
* @param {{ url: String, shortcode: Sring }[]} emoji - list of shortcodes to find
* @param {Function} processor - function to call on each encountered emoji,
* function is passed single object containing matching emoji ({ url, shortcode })
* return value will be inserted into resulting array instead of :shortcode:
* @return {Array} resulting array with non-emoji parts of text and whatever {processor}
* returned for emoji
*/
export const processTextForEmoji = (text, emojis, processor) => {
const buffer = []
let textBuffer = ''
for (let i = 0; i < text.length; i++) {
const char = text[i]
if (char === ':') {
const next = text.slice(i + 1)
let found = false
for (let emoji of emojis) {
if (next.slice(0, emoji.shortcode.length + 1) === (emoji.shortcode + ':')) {
found = emoji
break
}
}
if (found) {
buffer.push(textBuffer)
textBuffer = ''
buffer.push(processor(found))
i += found.shortcode.length + 1
} else {
textBuffer += char
}
} else {
textBuffer += char
}
}
if (textBuffer) buffer.push(textBuffer)
return buffer
}

View file

@ -0,0 +1,40 @@
const createRuffleService = () => {
let ruffleInstance = null
const getRuffle = () => new Promise((resolve, reject) => {
if (ruffleInstance) {
resolve(ruffleInstance)
return
}
// Ruffle needs these to be set before it's loaded
// https://github.com/ruffle-rs/ruffle/issues/3952
window.RufflePlayer = {}
window.RufflePlayer.config = {
polyfills: false,
publicPath: '/static/ruffle'
}
// Currently it's seems like a better way of loading ruffle
// because it needs the wasm publically accessible, but it needs path to it
// and filename of wasm seems to be pseudo-randomly generated (is it a hash?)
const script = document.createElement('script')
// see webpack config, using CopyPlugin to copy it from node_modules
// provided via ruffle-mirror
script.src = '/static/ruffle/ruffle.js'
script.type = 'text/javascript'
script.onerror = (e) => { reject(e) }
script.onabort = (e) => { reject(e) }
script.oncancel = (e) => { reject(e) }
script.onload = () => {
ruffleInstance = window.RufflePlayer
resolve(ruffleInstance)
}
document.body.appendChild(script)
})
return { getRuffle }
}
const RuffleService = createRuffleService()
export default RuffleService

View file

@ -369,6 +369,12 @@ export const SLOT_INHERITANCE = {
textColor: 'preserve'
},
postCyantext: {
depends: ['cBlue'],
layer: 'bg',
textColor: 'preserve'
},
border: {
depends: ['fg'],
opacity: 'border',

View file

@ -1,94 +0,0 @@
/**
* This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and
* allows it to be processed, useful for greentexting, mostly
*
* known issue: doesn't handle CDATA so nested CDATA might not work well
*
* @param {Object} input - input data
* @param {(string) => string} processor - function that will be called on every line
* @return {string} processed html
*/
export const processHtml = (html, processor) => {
const handledTags = new Set(['p', 'br', 'div'])
const openCloseTags = new Set(['p', 'div'])
let buffer = '' // Current output buffer
const level = [] // How deep we are in tags and which tags were there
let textBuffer = '' // Current line content
let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
// Extracts tag name from tag, i.e. <span a="b"> => span
const getTagName = (tag) => {
const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag)
return result && (result[1] || result[2])
}
const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
if (textBuffer.trim().length > 0) {
buffer += processor(textBuffer)
} else {
buffer += textBuffer
}
textBuffer = ''
}
const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing
flush()
buffer += tag
}
const handleOpen = (tag) => { // handles opening tags
flush()
buffer += tag
level.push(tag)
}
const handleClose = (tag) => { // handles closing tags
flush()
buffer += tag
if (level[level.length - 1] === tag) {
level.pop()
}
}
for (let i = 0; i < html.length; i++) {
const char = html[i]
if (char === '<' && tagBuffer === null) {
tagBuffer = char
} else if (char !== '>' && tagBuffer !== null) {
tagBuffer += char
} else if (char === '>' && tagBuffer !== null) {
tagBuffer += char
const tagFull = tagBuffer
tagBuffer = null
const tagName = getTagName(tagFull)
if (handledTags.has(tagName)) {
if (tagName === 'br') {
handleBr(tagFull)
} else if (openCloseTags.has(tagName)) {
if (tagFull[1] === '/') {
handleClose(tagFull)
} else if (tagFull[tagFull.length - 2] === '/') {
// self-closing
handleBr(tagFull)
} else {
handleOpen(tagFull)
}
}
} else {
textBuffer += tagFull
}
} else if (char === '\n') {
handleBr(char)
} else {
textBuffer += char
}
}
if (tagBuffer) {
textBuffer += tagBuffer
}
flush()
return buffer
}

View file

@ -8,6 +8,11 @@ const highlightStyle = (prefs) => {
const solidColor = `rgb(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)})`
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .1)`
const tintColor2 = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .2)`
const customProps = {
'--____highlight-solidColor': solidColor,
'--____highlight-tintColor': tintColor,
'--____highlight-tintColor2': tintColor2
}
if (type === 'striped') {
return {
backgroundImage: [
@ -17,11 +22,13 @@ const highlightStyle = (prefs) => {
`${tintColor2} 20px,`,
`${tintColor2} 40px`
].join(' '),
backgroundPosition: '0 0'
backgroundPosition: '0 0',
...customProps
}
} else if (type === 'solid') {
return {
backgroundColor: tintColor2
backgroundColor: tintColor2,
...customProps
}
} else if (type === 'side') {
return {
@ -31,7 +38,8 @@ const highlightStyle = (prefs) => {
`${solidColor} 2px,`,
`transparent 6px`
].join(' '),
backgroundPosition: '0 0'
backgroundPosition: '0 0',
...customProps
}
}
}