@bagelink/vue 0.0.996 → 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,133 +1,162 @@
1
1
  <script setup lang="ts">
2
- import hljsVuePlugin from '@highlightjs/vue-plugin'
3
- import { nextTick, watch } from 'vue'
4
- import 'highlight.js/lib/common'
5
- import 'highlight.js/styles/atom-one-dark.css'
2
+ import type { Language, HilightJS } from './CodeTypes'
6
3
 
7
- const { readonly = false, language, modelValue = '' } = defineProps<{
8
- language?: string
4
+ declare global {
5
+ interface Window {
6
+ hljs: HilightJS
7
+ }
8
+ }
9
+ import { appendStyle, appendScript } from '@bagelink/vue'
10
+ import { nextTick, onMounted, watch } from 'vue'
11
+
12
+ // Props
13
+ const { language, readonly = false, modelValue = '', autodetect, ignoreIllegals = true } = defineProps<{
14
+ language?: Language
9
15
  readonly?: boolean
10
16
  modelValue?: string
17
+ autodetect?: boolean
18
+ ignoreIllegals?: boolean
11
19
  }>()
12
20
 
21
+ const emit = defineEmits(['update:modelValue'])
22
+
23
+ let hljs = $ref<HilightJS | null>(null)
24
+
25
+ // State and refs
13
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
+ })
14
36
 
15
- function tabKeyDown(event: KeyboardEvent) {
37
+ const className = $computed(() => {
38
+ if (cannotDetectLanguage) return ''
39
+ return `hljs ${language || ''}`
40
+ })
41
+
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) {
16
69
  const target = event.target as HTMLTextAreaElement
17
70
  const start = target.selectionStart
18
- const end = target.selectionEnd
19
71
  const tab = ' '
20
- code = code.slice(0, start) + tab + code.slice(end)
72
+ code = code.slice(0, start) + tab + code.slice(start)
21
73
  nextTick(() => {
22
- target.selectionStart = target.selectionEnd = start + 2
74
+ target.selectionStart = target.selectionEnd = start + tab.length
23
75
  })
24
76
  }
25
77
 
26
- const textarea = $ref<HTMLInputElement>()
27
- let height = $ref('300px')
28
-
29
- function setHeight() {
30
- if (!textarea) return
31
- const { scrollHeight } = textarea
32
- height = `${scrollHeight}px`
78
+ function adjustHeight() {
79
+ if (textarea?.scrollHeight && textarea.scrollHeight > 300) {
80
+ height = `${textarea.scrollHeight}px`
81
+ }
33
82
  }
34
83
 
35
- const Highlightjs = hljsVuePlugin.component
36
- watch(() => code, setHeight, { immediate: true })
37
- watch(() => modelValue, (value) => {
38
- if (value !== code) code = value
84
+ // Lifecycle
85
+ onMounted(async () => {
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
+ }
97
+ })
98
+
99
+ watch(() => modelValue, (newVal) => {
100
+ adjustHeight()
101
+ if (newVal !== code) {
102
+ code = newVal
103
+ }
39
104
  }, { immediate: true })
40
105
  </script>
41
106
 
42
107
  <template>
43
- <div class="code-editor-wrap ltr radius-1">
108
+ <div v-if="loaded" class="code-editor-wrap grid rounded p-1 overflow hm-300px ">
44
109
  <div class="relative block h-100" :style="{ height }">
45
- <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">
46
- <div
47
- v-for="(_, index) in code.split('\n')"
48
- :key="index"
49
- class="number txt-end"
50
- >
51
- {{ index + 1 }}
52
- </div>
53
- </div>
54
- <Highlightjs
55
- class="highlighted-code absolute inset-0"
56
- :autodetect="!language"
57
- :code="code"
58
- :wrap="true"
59
- :language="language"
60
- />
110
+ <pre class=" overflow-hidden absolute inset-0 p-0 m-0 h-100 codeText">
111
+ <code :class="className" v-html="highlightedCode" />
112
+ </pre>
61
113
  <textarea
62
114
  v-if="!readonly"
63
115
  ref="textarea"
64
116
  v-model="code"
65
- :spellcheck="false"
66
- 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"
67
119
  placeholder="Write your code here"
68
120
  aria-label="Code Editor"
69
121
  data-gramm="false"
70
- @input="$emit('update:modelValue', code)"
71
- @keydown.tab.prevent="tabKeyDown"
122
+ @keydown.tab.prevent="handleTab"
123
+ @input="emit('update:modelValue', code)"
72
124
  />
73
125
  </div>
74
126
  </div>
75
127
  </template>
76
128
 
77
129
  <style>
78
- .code-editor-wrap{
79
- background: #282c34;
80
- overflow: scroll;
81
- display: grid;
82
- max-height: 300px;
130
+ pre code.hljs{
131
+ padding: 0 !important;
132
+ inset: 0 !important;
133
+ position: absolute;
83
134
  }
135
+ </style>
84
136
 
85
- .numbers {
86
- font-family: monospace;
87
- border-inline-end: 1px solid var(--bgl-gray);
88
- }
89
- .number{
90
- scale: 0.9;
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);
91
143
  }
92
- /* Highlight.js styles */
93
- .highlighted-code {
94
- white-space: pre-wrap;
95
- word-wrap: break-word;
96
- overflow: hidden;
97
- margin: 0;
98
- padding: 0;
144
+ .code-editor-wrap {
145
+ background: #282c34;
146
+ height: max-content;
99
147
  }
100
148
 
101
- .highlighted-code code {
102
- padding-left: 70px !important;
103
- overflow: hidden !important;
104
- }
105
-
106
- /* Textarea aligned with Highlight.js */
107
149
  .code-editor {
108
- font-family: monospace;
109
- color: transparent;
110
- resize: none;
111
- white-space: pre-wrap;
112
- word-wrap: break-word;
113
- caret-color: var(--bgl-white);
114
- font-size: 16px;
115
- padding: 1rem;
116
- padding-left: 70px !important;
117
-
118
- }
119
-
120
- .code-editor::-moz-selection {
121
- background: #2466bc30;
122
- color: inherit;
150
+ color: transparent;
151
+ resize: none;
123
152
  }
124
153
 
125
154
  .code-editor::selection {
126
- background: #2466bc30;
127
- color: inherit;
155
+ background: #2466bc30;
156
+ color: inherit;
128
157
  }
129
158
 
130
159
  .code-editor:focus {
131
- outline: none;
160
+ outline: none;
132
161
  }
133
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:\/\//, '')