@bagelink/vue 0.0.998 → 0.0.1000

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,139 +1,162 @@
1
1
  <script setup lang="ts">
2
- import hljsVuePlugin from '@highlightjs/vue-plugin'
2
+ import type { Language, HilightJS } from './CodeTypes'
3
+
4
+ declare global {
5
+ interface Window {
6
+ hljs: HilightJS
7
+ }
8
+ }
9
+ import { appendStyle, appendScript } from '@bagelink/vue'
3
10
  import { nextTick, onMounted, watch } from 'vue'
4
11
 
5
- const { readonly = false, language, modelValue = '' } = defineProps<{
6
- language?: string
12
+ // Props
13
+ const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals = true } = defineProps<{
14
+ language?: Language
7
15
  readonly?: boolean
8
16
  modelValue?: string
17
+ autodetect?: boolean
18
+ ignoreIllegals?: boolean
9
19
  }>()
10
20
 
21
+ const emit = defineEmits(['update:modelValue'])
22
+
23
+ let hljs = $ref<HilightJS | null>(null)
24
+
25
+ // State and refs
11
26
  let code = $ref('')
27
+ const textarea = $ref<HTMLTextAreaElement>()
28
+ let height = $ref('300px')
29
+ let loaded = $ref(false)
30
+
31
+ // Computed properties
32
+ const cannotDetectLanguage = $computed(() => {
33
+ const lang = language || ''
34
+ return !(autodetect ?? !lang) && !hljs?.getLanguage(lang)
35
+ })
36
+
37
+ const className = $computed(() => {
38
+ if (cannotDetectLanguage) return ''
39
+ return `hljs ${language || ''}`
40
+ })
12
41
 
13
- function tabKeyDown(event: KeyboardEvent) {
42
+ const highlightedCode = $computed(() => {
43
+ if (cannotDetectLanguage) {
44
+ console.warn(`The language "${language}" you specified could not be found.`)
45
+ return escapeHtml(code)
46
+ }
47
+ const lang = language || ''
48
+ const result = autodetect
49
+ ? hljs?.highlightAuto(code)
50
+ : hljs?.highlight(code, { language: lang, ignoreIllegals })
51
+ return result?.value || ''
52
+ })
53
+
54
+ // Methods
55
+ function escapeHtml(unsafe: string) {
56
+ return unsafe.replace(/[&<>"']/g, (m) => {
57
+ const replacements: { [key: string]: string } = {
58
+ '&': '&amp;',
59
+ '<': '&lt;',
60
+ '>': '&gt;',
61
+ '"': '&quot;',
62
+ '\'': '&#039;'
63
+ }
64
+ return replacements[m] || ''
65
+ })
66
+ }
67
+
68
+ function handleTab(event: KeyboardEvent) {
14
69
  const target = event.target as HTMLTextAreaElement
15
70
  const start = target.selectionStart
16
- const end = target.selectionEnd
17
71
  const tab = ' '
18
- code = code.slice(0, start) + tab + code.slice(end)
72
+ code = code.slice(0, start) + tab + code.slice(start)
19
73
  nextTick(() => {
20
- target.selectionStart = target.selectionEnd = start + 2
74
+ target.selectionStart = target.selectionEnd = start + tab.length
21
75
  })
22
76
  }
23
77
 
24
- const textarea = $ref<HTMLInputElement>()
25
- let height = $ref('300px')
26
-
27
- function setHeight() {
28
- if (!textarea) return
29
- const { scrollHeight } = textarea
30
- height = `${scrollHeight}px`
78
+ function adjustHeight() {
79
+ if (textarea?.scrollHeight && textarea.scrollHeight > 300) {
80
+ height = `${textarea.scrollHeight}px`
81
+ }
31
82
  }
32
83
 
33
- let loaded = $ref(false)
34
-
84
+ // Lifecycle
35
85
  onMounted(async () => {
36
- await import('highlight.js/lib/common')
37
- await import('highlight.js/styles/atom-one-dark.css')
38
- loaded = true
86
+ // Append scripts and styles for Highlight.js
87
+ await appendScript('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js', { id: 'hljs-cdn' })
88
+ await appendStyle('https://cdn.jsdelivr.net/npm/highlight.js/styles/atom-one-dark.min.css')
89
+
90
+ // Initialize hljs
91
+ if (window.hljs) {
92
+ hljs = window.hljs as HilightJS
93
+ loaded = true
94
+ } else {
95
+ console.error('Highlight.js failed to load.')
96
+ }
39
97
  })
40
98
 
41
- const Highlightjs = hljsVuePlugin.component
42
- watch(() => code, setHeight, { immediate: true })
43
- watch(() => modelValue, (value) => {
44
- if (value !== code) code = value
99
+ watch(() => modelValue, (newVal) => {
100
+ adjustHeight()
101
+ if (newVal !== code) {
102
+ code = newVal
103
+ }
45
104
  }, { immediate: true })
46
105
  </script>
47
106
 
48
107
  <template>
49
- <div v-if="loaded" class="code-editor-wrap ltr radius-1">
108
+ <div v-if="loaded" class="code-editor-wrap grid rounded p-1 overflow hm-300px ">
50
109
  <div class="relative block h-100" :style="{ height }">
51
- <div class="numbers absolute inset-0 py-1 px-05 txt16 line-height-15 overflow-hidden z-1 w50px txt-start opacity-3 color-gray">
52
- <div
53
- v-for="(_, index) in code.split('\n')"
54
- :key="index"
55
- class="number txt-end"
56
- >
57
- {{ index + 1 }}
58
- </div>
59
- </div>
60
- <Highlightjs
61
- class="highlighted-code absolute inset-0"
62
- :autodetect="!language"
63
- :code="code"
64
- :wrap="true"
65
- :language="language"
66
- />
110
+ <pre class=" overflow-hidden absolute inset-0 p-0 m-0 h-100 codeText">
111
+ <code :class="className" v-html="highlightedCode" />
112
+ </pre>
67
113
  <textarea
68
114
  v-if="!readonly"
69
115
  ref="textarea"
70
116
  v-model="code"
71
- :spellcheck="false"
72
- class="code-editor absolute inset-0 overflow-hidden bg-transparent line-height-15 border-none m-0"
117
+ class="code-editor absolute inset-0 bg-transparent overflow-hidden h-100 p-0 m-0 codeText border-none"
118
+ spellcheck="false"
73
119
  placeholder="Write your code here"
74
120
  aria-label="Code Editor"
75
121
  data-gramm="false"
76
- @input="$emit('update:modelValue', code)"
77
- @keydown.tab.prevent="tabKeyDown"
122
+ @keydown.tab.prevent="handleTab"
123
+ @input="emit('update:modelValue', code)"
78
124
  />
79
125
  </div>
80
126
  </div>
81
127
  </template>
82
128
 
83
129
  <style>
84
- .code-editor-wrap{
85
- background: #282c34;
86
- overflow: scroll;
87
- display: grid;
88
- max-height: 300px;
130
+ pre code.hljs{
131
+ padding: 0 !important;
132
+ inset: 0 !important;
133
+ position: absolute;
89
134
  }
135
+ </style>
90
136
 
91
- .numbers {
92
- font-family: monospace;
93
- border-inline-end: 1px solid var(--bgl-gray);
137
+ <style scoped>
138
+ .codeText{
139
+ font-family: monospace;
140
+ white-space: pre-wrap;
141
+ word-wrap: break-word;
142
+ caret-color: var(--bgl-white);
94
143
  }
95
- .number{
96
- scale: 0.9;
97
- }
98
- /* Highlight.js styles */
99
- .highlighted-code {
100
- white-space: pre-wrap;
101
- word-wrap: break-word;
102
- overflow: hidden;
103
- margin: 0;
104
- padding: 0;
105
- }
106
-
107
- .highlighted-code code {
108
- padding-left: 70px !important;
109
- overflow: hidden !important;
144
+ .code-editor-wrap {
145
+ background: #282c34;
146
+ height: max-content;
110
147
  }
111
148
 
112
- /* Textarea aligned with Highlight.js */
113
149
  .code-editor {
114
- font-family: monospace;
115
- color: transparent;
116
- resize: none;
117
- white-space: pre-wrap;
118
- word-wrap: break-word;
119
- caret-color: var(--bgl-white);
120
- font-size: 16px;
121
- padding: 1rem;
122
- padding-left: 70px !important;
123
-
124
- }
125
-
126
- .code-editor::-moz-selection {
127
- background: #2466bc30;
128
- color: inherit;
150
+ color: transparent;
151
+ resize: none;
129
152
  }
130
153
 
131
154
  .code-editor::selection {
132
- background: #2466bc30;
133
- color: inherit;
155
+ background: #2466bc30;
156
+ color: inherit;
134
157
  }
135
158
 
136
159
  .code-editor:focus {
137
- outline: none;
160
+ outline: none;
138
161
  }
139
162
  </style>
@@ -111,22 +111,43 @@ export function sleep(ms: number = 100) {
111
111
  return new Promise(resolve => setTimeout(resolve, ms))
112
112
  }
113
113
 
114
- export function appendScript(src: string): Promise<void> {
114
+ export function appendScript(src: string, options?: { id?: string }): Promise<void> {
115
115
  return new Promise((resolve, reject) => {
116
- if (document.querySelector(`script[src="${src}"]`)) {
116
+ if (options?.id) {
117
+ if (document.getElementById(options.id)) {
118
+ resolve()
119
+ return
120
+ }
121
+ } else if (document.querySelector(`script[src="${src}"]`)) {
117
122
  resolve()
118
123
  return
119
124
  }
120
125
  const script = document.createElement('script')
121
126
  script.src = src
122
- script.onload = () => {
123
- resolve()
127
+ if (options?.id) {
128
+ script.id = options.id
124
129
  }
130
+ script.onload = () => { resolve() }
125
131
  script.onerror = reject
126
132
  document.head.append(script)
127
133
  })
128
134
  }
129
135
 
136
+ export function appendStyle(src: string): Promise<void> {
137
+ return new Promise((resolve, reject) => {
138
+ if (document.querySelector(`link[href="${src}"]`)) {
139
+ resolve()
140
+ return
141
+ }
142
+ const link = document.createElement('link')
143
+ link.href = src
144
+ link.rel = 'stylesheet'
145
+ link.onload = () => { resolve() }
146
+ link.onerror = reject
147
+ document.head.append(link)
148
+ })
149
+ }
150
+
130
151
  export function normalizeURL(url: string) {
131
152
  if (url.startsWith('https://')) return url
132
153
  url = url.replace(/http:\/\//, '')