Skip to content

Commit

Permalink
done
Browse files Browse the repository at this point in the history
  • Loading branch information
rikzun committed Jan 31, 2024
1 parent 58f7261 commit 8291157
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 19 deletions.
14 changes: 7 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './App.style.scss'
import { Packer, Document } from "docx"
import { ReadonlyPre } from './components/ReadonlyPre/ReadonlyPre'
import { saveBlob } from './utils'
import { ReadonlyPre } from './components/ReadonlyPre'
import { saveBlob } from './utils/utils'

// function downloadPDF() {
// const file = new Document({
Expand All @@ -21,8 +21,8 @@ export function App() {
address: 'Russia, Novorossiysk',
languages: {
russian: 'native',
english: 'b1'
russian: 'native',
english: 'b1'
},
technicalSkills: [
Expand Down Expand Up @@ -56,9 +56,9 @@ export function App() {
],
discord: 'rikzun',
mail: 'rik.zunqq@gmail.com',
github: 'https://github.com/rikzun'
mail: '[rik.zunqq@gmail.com](mailto:rik.zunqq@gmail.com)',
github: '[https://github.com/rikzun](https://github.com/rikzun)'
}`

return <ReadonlyPre value={text} />
}
}
14 changes: 14 additions & 0 deletions src/components/CodeHighlight/CodeHighlight.styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.highlight {
.string { color: #CE9178 }
.numeric { color: #B5CEA8 }
.reserved { color: #569CD6 }
.variable { color: #4FC1FF }
.object-key { color: #9CDCFE }

.string__link {
cursor: pointer;
text-underline-offset: 2px;
}

caret-color: white;
}
41 changes: 41 additions & 0 deletions src/components/CodeHighlight/CodeHighlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import "./CodeHighlight.styles.scss"
import { Fragment, memo } from "react"
import { trimStart } from "src/utils/utils"
import { type CodeHighlightProps, Token } from "./CodeHighlight.types"
import { Range } from "src/utils/range"
import { CodeHighlightService } from "./CodeHightlight.service"

export const CodeHighlight = memo((props: CodeHighlightProps) => (
<div className="highlight">
{props.value.split('\n').map((rawLine, i) => {
const line = trimStart(rawLine, 4)
const tokens: Map<Range, Token> = new Map()

CodeHighlightService.matchAllRanges(line, CodeHighlightService.regex.string)
.forEach(([range, string]) => tokens.set(range, new Token('string', CodeHighlightService.getStringSubtype(string))))

CodeHighlightService.matchAllNewRanges(line, CodeHighlightService.regex.number, [...tokens.keys()])
.forEach(([range, _]) => tokens.set(range, new Token('numeric')))

CodeHighlightService.matchAllNewRanges(line, CodeHighlightService.regex.objectKey, [...tokens.keys()])
.forEach(([range, _]) => tokens.set(range, new Token('object-key')))

CodeHighlightService.matchAllNewRanges(line, CodeHighlightService.regex.reserved, [...tokens.keys()])
.forEach(([range, string]) => {
tokens.set(range, new Token('reserved'))

if (string === 'const') {
const variableRange = CodeHighlightService.matchVariable(line, range)
if (variableRange) tokens.set(variableRange, new Token('variable'))
}
})

return (
<Fragment key={rawLine + i}>
{CodeHighlightService.buildNodes(line, tokens)}
<br />
</Fragment>
)
})}
</div>
))
25 changes: 25 additions & 0 deletions src/components/CodeHighlight/CodeHighlight.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export interface CodeHighlightProps {
value: string
}

export type TokenType = 'string' | 'numeric' | 'reserved' | 'variable' | 'object-key'
export type TokenSubtype = 'link'
export class Token {
type: TokenType
subtype: TokenSubtype | null
rest: string | null

constructor(type: TokenType, subtype: TokenSubtype | null = null, rest: string | null = null) {
this.type = type
this.subtype = subtype
this.rest = rest
}

toClassName() {
return [
this.type,
this.rest ? (this.type + '__' + this.rest) : null,
this.subtype ? (this.type + '__' + this.subtype) : null
].filter(Boolean).join(' ')
}
}
84 changes: 84 additions & 0 deletions src/components/CodeHighlight/CodeHightlight.service.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Range } from "src/utils/range"
import { Token } from "./CodeHighlight.types"
import type { ReactNode } from "react"

export const CodeHighlightService = new class {
regex = {
string: /('(?:[^'\\]|\\.)*')/g,
number: /(\d+)/g,
reserved: /(const)/g,
objectKey: /(\w+\s*:)/g,

stringLink: /\[(.*)\]\((.*)\)/
}

matchAllRanges(string: string, regexp: RegExp): [Range, string][] {
return [...string.matchAll(regexp)].map(({1: string, index}) => (
[new Range(index!, index! + string.length), string]
))
}

matchAllNewRanges(string: string, regexp: RegExp, ranges: Range[]): [Range, string][] {
return this.matchAllRanges(string, regexp).filter(([range, _]) => {
return !ranges.some((v) => v.contains(range))
})
}

matchVariable(string: string, range: Range) {
const match = (' '.repeat(range.end) + string.substring(range.end)).match(/\w+/)
return match ? new Range(match.index!, match.index! + match[0].length) : null
}

getStringSubtype(string: string) {
if (this.regex.stringLink.test(string)) return 'link'
}

matchStringFormatting(string: string): [string, string] | null {
const matchTitle = string.match(this.regex.stringLink)
if (!matchTitle) return null

return [matchTitle[1], matchTitle[2]]
}

buildNodes(line: string, tokens: Map<Range, Token>) {
const nodeList: ReactNode[] = [...line.split('')]

Array.from(tokens.entries())
.sort((a, b) => a[0].start < b[0].start ? 1 : -1)
.forEach(([range, token]) => {
let element: ReactNode
const string = nodeList.slice(range.start, range.end).join('')

switch(token.subtype) {
default: {
element = (
<span className={token.toClassName()}>
{string}
</span>
)
break
}

case 'link': {
const [title, value] = this.matchStringFormatting(string)!

element = (
<a
className={token.toClassName()}
href={value}
target={value.startsWith('mailto:') ? undefined : '_blank'}
contentEditable={false}
>
{title}
</a>
)
break
}
}

nodeList.splice(range.start, string.length, element)
})

return nodeList
}
}()
1 change: 1 addition & 0 deletions src/components/CodeHighlight/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './CodeHighlight'
23 changes: 11 additions & 12 deletions src/components/ReadonlyPre/ReadonlyPre.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { array, trimStart } from 'src/utils'
import { array } from 'src/utils/utils'
import './ReadonlyPre.styles.scss'
import type { SyntheticEvent, KeyboardEvent, FormEvent } from 'react'
import { useState } from 'react'
import { CodeHighlight } from '../CodeHighlight'

interface ReadonlyPreProps { value: string }

Expand All @@ -11,6 +12,8 @@ export function ReadonlyPre(props: ReadonlyPreProps) {

const acceptableKeyCodes = [
'Tab',
'ShiftLeft',
'ShiftRight',
'Home',
'End',
'PageUp',
Expand All @@ -22,26 +25,26 @@ export function ReadonlyPre(props: ReadonlyPreProps) {
...array(12).map((v) => `F${v + 1}`)
]

const acceptableCtrlKeyCodes = [
'KeyA', 'KeyC'
]

const eventCancel = (e: SyntheticEvent) => {
e.preventDefault()
e.stopPropagation()
return false
}

const onKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && acceptableCtrlKeyCodes.includes(e.code)) return
if (!acceptableKeyCodes.includes(e.code)) return eventCancel(e)
}

const onInput = (e: FormEvent<HTMLPreElement>) => {
if (e.cancelable) return eventCancel(e)
updateKey()
updateKey() // for fucking android and firefox
}

/* {`{
kekw
}\n`.split('\n').map((v) => trimStart(v, 12)).join('\n')}
<span onClick={downloadPDF}>█████</span> */

return (
<pre
className="container"
Expand All @@ -55,11 +58,7 @@ export function ReadonlyPre(props: ReadonlyPreProps) {
contentEditable
suppressContentEditableWarning
>
{props.value.split('\n').map((rawLine) => {
const line = trimStart(rawLine, 4).split(' ')
// console.log(line)
return <span>{line}</span>
})}
<CodeHighlight value={props.value} />
</pre>
)
}
1 change: 1 addition & 0 deletions src/components/ReadonlyPre/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ReadonlyPre'
13 changes: 13 additions & 0 deletions src/utils/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class Range {
start: number
end: number

constructor(start: number, end: number) {
this.start = start
this.end = end
}

contains(other: Range) {
return this.start <= other.start && this.end >= other.end
}
}
File renamed without changes.

0 comments on commit 8291157

Please sign in to comment.