mirror of
https://github.com/OliveTin/OliveTin
synced 2026-09-26 03:35:44 +00:00
chore: migrate frontend linting from standard to neostandard
Replace the ESLint 8-based standard package with neostandard and a flat config so frontend installs no longer pull deprecated ESLint 8 transitives. Keep vue recommended rules and auto-fix indent drift. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/recommended",
|
||||
"standard"
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"parser": {
|
||||
"js": "espree"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/no-v-html": "warn",
|
||||
"no-tabs": "off",
|
||||
"no-mixed-spaces-and-tabs": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import neostandard from 'neostandard'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'node_modules/**',
|
||||
'resources/scripts/gen/**'
|
||||
]
|
||||
},
|
||||
...neostandard({
|
||||
env: ['browser'],
|
||||
noJsx: true
|
||||
}),
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.{js,mjs,vue}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
'@stylistic/no-tabs': 'off',
|
||||
'@stylistic/no-mixed-spaces-and-tabs': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/no-v-html': 'warn'
|
||||
}
|
||||
}
|
||||
]
|
||||
Generated
+1310
-971
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,9 @@
|
||||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"source": "index.html",
|
||||
"devDependencies": {
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-plugin-vue": "^10.11.0",
|
||||
"neostandard": "^0.13.0",
|
||||
"process": "^0.11.10",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
@@ -35,7 +37,6 @@
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"iconify-icon": "^3.0.2",
|
||||
"picocrank": "^1.29.0",
|
||||
"standard": "^17.1.2",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^8.2.2",
|
||||
"vue": "^3.5.42",
|
||||
|
||||
@@ -273,9 +273,9 @@ function constructFromJson (json) {
|
||||
if (bindingId.value) {
|
||||
rateLimits[bindingId.value] = rateLimitExpires.value
|
||||
setBindingExecutionState(
|
||||
bindingId.value,
|
||||
!!json.hasRunningInstance,
|
||||
!!json.hasQueuedInstance
|
||||
bindingId.value,
|
||||
!!json.hasRunningInstance,
|
||||
!!json.hasQueuedInstance
|
||||
)
|
||||
}
|
||||
updateRateLimitStatus()
|
||||
@@ -310,8 +310,8 @@ function updateRateLimitStatus () {
|
||||
isRateLimited.value = false
|
||||
rateLimitMessage.value = ''
|
||||
if (rateLimitInterval.value) {
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -325,8 +325,8 @@ function updateRateLimitStatus () {
|
||||
rateLimitMessage.value = ''
|
||||
rateLimitExpires.value = 0
|
||||
if (rateLimitInterval.value) {
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
clearInterval(rateLimitInterval.value)
|
||||
rateLimitInterval.value = null
|
||||
}
|
||||
} else {
|
||||
// Still rate limited
|
||||
@@ -336,9 +336,9 @@ function updateRateLimitStatus () {
|
||||
|
||||
// Set up interval to update every second
|
||||
if (!rateLimitInterval.value) {
|
||||
rateLimitInterval.value = setInterval(() => {
|
||||
rateLimitInterval.value = setInterval(() => {
|
||||
updateRateLimitStatus()
|
||||
}, 1000)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,12 +360,12 @@ async function handleClick () {
|
||||
const bindingId = props.actionData.bindingId
|
||||
const prefilled = props.prefilledArguments || {}
|
||||
if (Object.keys(prefilled).length > 0) {
|
||||
router.push({
|
||||
router.push({
|
||||
path: `/actionBinding/${bindingId}/argumentForm`,
|
||||
state: { prefilledArguments: prefilled }
|
||||
})
|
||||
})
|
||||
} else {
|
||||
router.push(`/actionBinding/${bindingId}/argumentForm`)
|
||||
router.push(`/actionBinding/${bindingId}/argumentForm`)
|
||||
}
|
||||
} else {
|
||||
await startAction()
|
||||
@@ -439,7 +439,7 @@ async function startAction (actionArgs) {
|
||||
stopButtonResultWatch = watch(
|
||||
() => buttonResults[startActionArgs.uniqueTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
onLogEntryChanged(newResult)
|
||||
onLogEntryChanged(newResult)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -450,11 +450,11 @@ async function startAction (actionArgs) {
|
||||
const trackingId = response.executionTrackingId || startActionArgs.uniqueTrackingId
|
||||
|
||||
if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
|
||||
router.push(`/logs/${trackingId}`)
|
||||
router.push(`/logs/${trackingId}`)
|
||||
}
|
||||
|
||||
if (!connectionState.connected) {
|
||||
await pollExecutionUntilDone(trackingId)
|
||||
await pollExecutionUntilDone(trackingId)
|
||||
}
|
||||
} catch (err) {
|
||||
stopWatchingButtonResult()
|
||||
@@ -554,14 +554,14 @@ onMounted(() => {
|
||||
watch(
|
||||
rateLimits,
|
||||
() => {
|
||||
const id = bindingId.value
|
||||
if (id && rateLimits[id] !== undefined) {
|
||||
const id = bindingId.value
|
||||
if (id && rateLimits[id] !== undefined) {
|
||||
const newExpires = rateLimits[id]
|
||||
if (newExpires !== rateLimitExpires.value) {
|
||||
rateLimitExpires.value = newExpires
|
||||
updateRateLimitStatus()
|
||||
rateLimitExpires.value = newExpires
|
||||
updateRateLimitStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
@@ -570,9 +570,9 @@ onMounted(() => {
|
||||
watch(
|
||||
() => pendingBindingFlash[bindingId.value],
|
||||
(pending) => {
|
||||
if (pending) {
|
||||
if (pending) {
|
||||
consumeAndFlashPendingResult()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
@@ -593,7 +593,7 @@ watch(
|
||||
(newData) => {
|
||||
updateFromJson(newData)
|
||||
if (newData?.icon !== undefined) {
|
||||
glyph.value = newData.icon ?? ''
|
||||
glyph.value = newData.icon ?? ''
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
const fallbackNamedHtmlEntities = {
|
||||
amp: '&',
|
||||
apos: "'",
|
||||
darr: '\u2193',
|
||||
gt: '>',
|
||||
laquo: '\u00ab',
|
||||
larr: '\u2190',
|
||||
nbsp: '\u00a0',
|
||||
quot: '"',
|
||||
raquo: '\u00bb',
|
||||
rarr: '\u2192',
|
||||
uarr: '\u2191',
|
||||
amp: '&',
|
||||
apos: "'",
|
||||
darr: '\u2193',
|
||||
gt: '>',
|
||||
laquo: '\u00ab',
|
||||
larr: '\u2190',
|
||||
nbsp: '\u00a0',
|
||||
quot: '"',
|
||||
raquo: '\u00bb',
|
||||
rarr: '\u2192',
|
||||
uarr: '\u2191',
|
||||
}
|
||||
|
||||
export function decodeHtmlEntities(text) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.innerHTML = text
|
||||
export function decodeHtmlEntities (text) {
|
||||
if (typeof document !== 'undefined') {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.innerHTML = text
|
||||
|
||||
return textarea.value
|
||||
}
|
||||
return textarea.value
|
||||
}
|
||||
|
||||
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||
const codePoint = Number.parseInt(hex, 16)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||
const codePoint = Number.parseInt(decimal, 10)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&([a-zA-Z][a-zA-Z0-9]+);?/g, (entity, name) => {
|
||||
return fallbackNamedHtmlEntities[name] ?? entity
|
||||
})
|
||||
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||
const codePoint = Number.parseInt(hex, 16)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||
const codePoint = Number.parseInt(decimal, 10)
|
||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||
}).replace(/&([a-zA-Z][a-zA-Z0-9]+);?/g, (entity, name) => {
|
||||
return fallbackNamedHtmlEntities[name] ?? entity
|
||||
})
|
||||
}
|
||||
|
||||
export function glyphLooksLikeHtml(text) {
|
||||
const trimmedText = text.trim()
|
||||
export function glyphLooksLikeHtml (text) {
|
||||
const trimmedText = text.trim()
|
||||
|
||||
return trimmedText.startsWith('<') || /<img\b/i.test(text) || /\/custom-webui\//i.test(text)
|
||||
return trimmedText.startsWith('<') || /<img\b/i.test(text) || /\/custom-webui\//i.test(text)
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import assert from 'node:assert/strict'
|
||||
import { decodeHtmlEntities, glyphLooksLikeHtml } from './actionIconGlyphHelpers.mjs'
|
||||
|
||||
test('decodeHtmlEntities decodes named entity icons as plain glyph text', () => {
|
||||
assert.equal(decodeHtmlEntities('«'), '\u00ab')
|
||||
assert.equal(decodeHtmlEntities('→'), '\u2192')
|
||||
assert.equal(decodeHtmlEntities('« next →'), '\u00ab next \u2192')
|
||||
assert.equal(decodeHtmlEntities('«'), '\u00ab')
|
||||
assert.equal(decodeHtmlEntities('→'), '\u2192')
|
||||
assert.equal(decodeHtmlEntities('« next →'), '\u00ab next \u2192')
|
||||
})
|
||||
|
||||
test('decoded named entity icons are not treated as HTML markup', () => {
|
||||
const decodedGlyph = decodeHtmlEntities('→')
|
||||
const decodedGlyph = decodeHtmlEntities('→')
|
||||
|
||||
assert.equal(glyphLooksLikeHtml(decodedGlyph), false)
|
||||
assert.equal(glyphLooksLikeHtml(decodedGlyph), false)
|
||||
})
|
||||
|
||||
test('decodeHtmlEntities keeps existing numeric entity icon support', () => {
|
||||
assert.equal(decodeHtmlEntities('💩'), '\ud83d\udca9')
|
||||
assert.equal(decodeHtmlEntities('💾'), '\ud83d\udcbe')
|
||||
assert.equal(decodeHtmlEntities('💩'), '\ud83d\udca9')
|
||||
assert.equal(decodeHtmlEntities('💾'), '\ud83d\udcbe')
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ const choices = [
|
||||
{ title: 'Photos', value: 'photos' }
|
||||
]
|
||||
|
||||
test('parseChecklistValue parses JSON-encoded values', () => {
|
||||
test('parseChecklistValue parses JSON-encoded values', () => {
|
||||
assert.deepEqual(parseChecklistValue('["documents","photos"]'), ['documents', 'photos'])
|
||||
assert.deepEqual(parseChecklistValue('["kitchen,bedroom","hallway"]'), ['kitchen,bedroom', 'hallway'])
|
||||
assert.deepEqual(parseChecklistValue(''), [])
|
||||
|
||||
@@ -4,31 +4,31 @@ import assert from 'node:assert/strict'
|
||||
import { getInitialArgumentValue, readPrefilledArgumentsFromNavigation } from './prefilledArguments.js'
|
||||
|
||||
test('readPrefilledArgumentsFromNavigation returns navigation state values', () => {
|
||||
assert.deepEqual(
|
||||
readPrefilledArgumentsFromNavigation({ prefilledArguments: { ansible_host: '10.0.0.1' } }),
|
||||
{ ansible_host: '10.0.0.1' }
|
||||
)
|
||||
assert.deepEqual(
|
||||
readPrefilledArgumentsFromNavigation({ prefilledArguments: { ansible_host: '10.0.0.1' } }),
|
||||
{ ansible_host: '10.0.0.1' }
|
||||
)
|
||||
})
|
||||
|
||||
test('readPrefilledArgumentsFromNavigation returns empty object when state is absent', () => {
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation({}), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation(undefined), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation({}), {})
|
||||
assert.deepEqual(readPrefilledArgumentsFromNavigation(undefined), {})
|
||||
})
|
||||
|
||||
test('getInitialArgumentValue prefers navigation state over query params', () => {
|
||||
assert.equal(
|
||||
getInitialArgumentValue(
|
||||
'ansible_host',
|
||||
{ ansible_host: '10.0.0.1' },
|
||||
'?ansible_host=10.0.0.2'
|
||||
),
|
||||
'10.0.0.1'
|
||||
)
|
||||
assert.equal(
|
||||
getInitialArgumentValue(
|
||||
'ansible_host',
|
||||
{ ansible_host: '10.0.0.1' },
|
||||
'?ansible_host=10.0.0.2'
|
||||
),
|
||||
'10.0.0.1'
|
||||
)
|
||||
})
|
||||
|
||||
test('getInitialArgumentValue falls back to query params when state is absent', () => {
|
||||
assert.equal(
|
||||
getInitialArgumentValue('ansible_host', {}, '?ansible_host=10.0.0.2'),
|
||||
'10.0.0.2'
|
||||
)
|
||||
assert.equal(
|
||||
getInitialArgumentValue('ansible_host', {}, '?ansible_host=10.0.0.2'),
|
||||
'10.0.0.2'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -355,10 +355,10 @@ async function fetchExecutionResult (executionTrackingIdParam) {
|
||||
} catch (err) {
|
||||
// Check if it's a "not found" error (404 or similar)
|
||||
if (err.status === 404 || err.code === 'NotFound' || err.message?.includes('not found')) {
|
||||
notFound.value = true
|
||||
errorMessage.value = err.message || 'The execution could not be found in the system.'
|
||||
notFound.value = true
|
||||
errorMessage.value = err.message || 'The execution could not be found in the system.'
|
||||
} else {
|
||||
renderError(err)
|
||||
renderError(err)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
@@ -434,7 +434,7 @@ async function renderExecutionResult (res) {
|
||||
if (terminal) {
|
||||
await terminal.reset()
|
||||
await terminal.write(res.logEntry.output, () => {
|
||||
terminal.fit()
|
||||
terminal.fit()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -488,11 +488,11 @@ onMounted(() => {
|
||||
watch(
|
||||
() => buttonResults[props.executionTrackingId],
|
||||
(newResult, oldResult) => {
|
||||
if (newResult) {
|
||||
if (newResult) {
|
||||
renderExecutionResult({
|
||||
logEntry: newResult
|
||||
logEntry: newResult
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user