Files
risuai/src/ts/plugins/automark.ts
2024-06-19 03:20:54 +09:00

206 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const excludesDat = ['<','>','{','}','[',']','(',')','-',':',';','…','—','','_','*','+','/','\\','|','!','?','.',',',' ']
const symbols = ['<','>','{','}','[',']','(',')','-',':',';','…','—','','_','*','+','/','\\','|','!','?','.',',',' ', '\n', '。', '、', '', '', '', '', '', '', '', '【', '】', '「', '」', '『', '』', '“', '”', '', '', '《', '》', '〈', '〉', '', '', '«', '»', '‟', '„']
const selfClosingTags = [
'br','hr','img','input','meta','link','base','area','col','command','embed','keygen','param','source','track','wbr',
//self closing tags defined by HTML5
'!',
//for doctype <!DOCTYPE html> and comment <!-- -->
'user', 'bot', 'char'
//special tags for user, bot, and char
]
const checkSelfClosingTag = (dat:string) => {
dat = dat.substring(0, 10) //we only need to check the first 10 characters, to avoid checking the whole string
dat = dat.toLowerCase() //we don't care about the case
for(const tag of selfClosingTags){
if(dat.startsWith(tag)){
return true
}
}
return false
}
export function risuFormater(dat:string){
const lines:[string,string][] = [['','']] // [type, content]
let htmlType = 0 // 0: not inside tag, 1: closing tag, 2: opening tag
for(let i=0;i<dat.length;i++){
const getLastLine = () => {
return lines[lines.length-1] ?? [
'not-found', ''
]
}
//html tag handling
if(dat[i] === '<' && getLastLine()[0] !== 'code-block'){
lines.push(['html-tag',''])
if(dat[i+1] === '/'){
htmlType = 1
}
else{
htmlType = 2
}
}
if(dat[i] === '>' && getLastLine()[0] === 'html-tag'){
const pop = lines.pop()
const tagAttr = pop[1].substring(1).trim()
if(htmlType === 1){
const pop2 = lines.pop() //probably html-inner
const chunk = pop2[1] + pop[1] + '>'
if(getLastLine()[0] === ''){
lines.push(['html-chunk',chunk])
lines.push(['',''])
}
else{
getLastLine()[1] += chunk
}
continue
}
else if(checkSelfClosingTag(tagAttr)){
const chunk = pop[1] + '>'
if(getLastLine()[0] === ''){
lines.push(['html-chunk',chunk])
lines.push(['',''])
}
else{
getLastLine()[1] += chunk
}
continue
}
else{
lines.push(['html-inner',pop[1]])
}
htmlType = 0
}
//code block handling
if(dat[i] === '`' && dat[i+1] === '`' && dat[i+2] === '`' && getLastLine()[0] === ''){
if(getLastLine()[0] === 'code-block'){
getLastLine()[1] += '```'
lines.push(['',''])
}
else{
lines.push(['code-block','```'])
}
i += 2
continue
}
if(dat[i] === '\n' && getLastLine()[0] === ''){
lines.push(['newline','\n'])
lines.push(['',''])
}
else if(lines[lines.length-1]){
lines[lines.length-1][1] += dat[i]
}
}
let result = ''
for(let i=0;i<lines.length;i++){
if(lines[i][0] !== ''){
result += lines[i][1]
continue
}
let line = lines[i][1] ??''
let isNumbered = false
let endMarked = false
if(excludesDat.includes(line[0]) || (line[1] === '.' && ['1','2','3','4','5','6','7','8','9'].includes(line[0]))){
isNumbered = true
}
if(line.endsWith('>') || line.endsWith('}') || line.startsWith('<')){
endMarked = true
}
if(isNumbered || endMarked){
result += line
continue
}
let depth = 0
let depthChunk:string[] = ['']
let depthChunkType:string[] = ['']
//spaces for detection
line = ' ' + line + ' '
const isNotCharacter = (t:string) => {
return symbols.includes(t)
}
for(let j=0;j<line.length;j++){
switch(line[j]){
case '"':
case '“':
case '”':{
if(depthChunkType[depth] === '"'){
depthChunkType.pop()
const pop = depthChunk.pop()
depth--
depthChunk[depth] += `<mark risu-mark="quote2">${pop}${line[j]}</mark>`
}
else{
depthChunkType.push('"')
depthChunk.push(line[j])
depth++
}
break
}
case "'":
case '':
case '':{
if(depthChunkType[depth] === "'"){
if(isNotCharacter(line[j-1]) || !isNotCharacter(line[j+1]) || (line[j-2] === 'i' && line[j-1] === 'n')){
//this is not a quote
depthChunk[depth] += line[j]
}
else{
depthChunkType.pop()
const pop = depthChunk.pop()
depth--
depthChunk[depth] += `<mark risu-mark="quote1">${pop}${line[j]}</mark>`
}
}
else{
if(!isNotCharacter(line[j-1]) || isNotCharacter(line[j+1])){
//this is not a quote
depthChunk[depth] += line[j]
}
else{
depthChunkType.push("'")
depthChunk.push(line[j])
depth++
}
}
break
}
default:{
depthChunk[depth] += line[j]
}
}
}
let lineResult = ''
while(depthChunk.length > 0){
lineResult = depthChunk.pop() + lineResult
}
if(lineResult.startsWith(' ')){
lineResult = lineResult.substring(1)
}
if(lineResult.endsWith(' ')){
lineResult = lineResult.substring(0,lineResult.length-1)
}
result += lineResult
}
return result.trim()
}