mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
feat(frontend): Added tailwind classes auto-complete (#2712)
* feat(frontend): Added tailwind classes auto-complete * feat(frontend): handle tailwindcss as a language
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import fs from 'fs'
|
||||
import tailwindClasses from './tailwindClasses.js'
|
||||
|
||||
function filterTailwindClasses(classes) {
|
||||
const filters = [
|
||||
{ pattern: /^m(\w?)-.*$/ },
|
||||
{ pattern: /^p(\w?)-.*$/ },
|
||||
{ pattern: /^rounded-.*$/ },
|
||||
{ pattern: /^shadow-.*$/ },
|
||||
{ pattern: /^text-[^/]*$/ },
|
||||
{ pattern: /^bg-[^/]*$/ },
|
||||
{ pattern: /^border-[^/]*$/ },
|
||||
{ pattern: /^ring-[^/]*$/ }
|
||||
]
|
||||
|
||||
return classes.filter((className) => {
|
||||
return filters.some((filter) => filter.pattern.test(className))
|
||||
})
|
||||
}
|
||||
|
||||
const filteredClasses = filterTailwindClasses(tailwindClasses)
|
||||
|
||||
const output = `export const filteredTailwindClasses = ${JSON.stringify(filteredClasses, null, 2)};`
|
||||
fs.writeFileSync('filteredTailwindClasses.ts', output)
|
||||
@@ -14,7 +14,7 @@
|
||||
"generate-backend-client-mac": "openapi --input ../backend/windmill-api/openapi.yaml --output ./src/lib/gen --useOptions",
|
||||
"pretest": "tsc --incremental -p tests/tsconfig.json",
|
||||
"test": "playwright test --config=tests-out/playwright.config.js",
|
||||
"delele-orphan-components": "sh scripts/delete-orphan-components.sh"
|
||||
"filter-classes": "node filterTailwindClasses.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/core": "^1.3.1",
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ANSI color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# get the list of all .svelte files
|
||||
svelte_files=$(find src -type f -name "*.svelte")
|
||||
|
||||
# Array to hold unused files
|
||||
declare -a unused_files
|
||||
|
||||
echo -e "${NC}Scanning src folder to find all .svelte files"
|
||||
echo -e " ${GREEN}.${NC} means the .svelte is imported in another file"
|
||||
echo -e " ${RED}x${NC} means the .svelte is not imported and should likely be removed"
|
||||
|
||||
# loop over each svelte file
|
||||
for svelte_file in $svelte_files
|
||||
do
|
||||
# extract the filename
|
||||
filename=$(basename -- "$svelte_file")
|
||||
|
||||
# skip files starting with '+'
|
||||
if [[ "$filename" == +* ]]
|
||||
then
|
||||
echo -n -e "${GREEN}.${NC}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# search for the filename in all files
|
||||
found=$(grep -rl "$filename" src)
|
||||
|
||||
# if nothing was found, then the file is unused
|
||||
if [[ -z $found ]]
|
||||
then
|
||||
echo -n -e "${RED}x${NC}"
|
||||
unused_files+=("$svelte_file")
|
||||
else
|
||||
echo -n -e "${GREEN}.${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Print a newline after progress dots
|
||||
echo
|
||||
echo
|
||||
|
||||
# Print unused files
|
||||
for file in "${unused_files[@]}"
|
||||
do
|
||||
echo -e "${RED}Unused Svelte file: $file${NC}"
|
||||
done
|
||||
|
||||
# If no unused components found, print the message and exit
|
||||
if [ ${#unused_files[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}No unused components found.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete files if user confirms
|
||||
if [ ${#unused_files[@]} -gt 0 ]; then
|
||||
echo -e -n "${GREEN}Do you want to delete these ${#unused_files[@]} files? (y/n) ${NC}"
|
||||
read answer
|
||||
|
||||
if [ "$answer" != "${answer#[Yy]}" ] ;then
|
||||
for file in "${unused_files[@]}"
|
||||
do
|
||||
rm "$file"
|
||||
echo -e "${RED}Deleted $file${NC}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
@@ -1,3 +1,8 @@
|
||||
<script context="module">
|
||||
let cssClassesLoaded = writable(false)
|
||||
let tailwindClassesLoaded = writable(false)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { BROWSER } from 'esm-env'
|
||||
|
||||
@@ -20,7 +25,7 @@
|
||||
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
||||
import 'monaco-editor/esm/vs/language/css/monaco.contribution'
|
||||
|
||||
import { allClasses, authorizedClassnames } from './apps/editor/componentsPanel/cssUtils'
|
||||
import { allClasses } from './apps/editor/componentsPanel/cssUtils'
|
||||
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
@@ -28,6 +33,8 @@
|
||||
import { buildWorkerDefinition } from './build_workers'
|
||||
import { initializeVscode } from './vscode'
|
||||
import EditorTheme from './EditorTheme.svelte'
|
||||
import { tailwindClasses } from './apps/editor/componentsPanel/tailwindUtils'
|
||||
import { writable } from 'svelte/store'
|
||||
// import { createConfiguredEditor } from 'vscode/monaco'
|
||||
// import type { IStandaloneCodeEditor } from 'vscode/vscode/vs/editor/standalone/browser/standaloneCodeEditor'
|
||||
|
||||
@@ -116,6 +123,7 @@
|
||||
|
||||
let width = 0
|
||||
let initialized = false
|
||||
|
||||
async function loadMonaco() {
|
||||
await initializeVscode()
|
||||
initialized = true
|
||||
@@ -202,9 +210,18 @@
|
||||
editor.onDidBlurEditorText(() => {
|
||||
code = getCode()
|
||||
})
|
||||
}
|
||||
|
||||
$: lang == 'css' && initialized && addCSSClassCompletions()
|
||||
if (lang === 'css' && !$cssClassesLoaded) {
|
||||
$cssClassesLoaded = true
|
||||
addCSSClassCompletions()
|
||||
}
|
||||
|
||||
if (lang === 'tailwindcss' && !$tailwindClassesLoaded) {
|
||||
languages.register({ id: 'tailwindcss' })
|
||||
$tailwindClassesLoaded = true
|
||||
addTailwindClassCompletions()
|
||||
}
|
||||
}
|
||||
|
||||
function addCSSClassCompletions() {
|
||||
languages.registerCompletionItemProvider('css', {
|
||||
@@ -238,6 +255,38 @@
|
||||
})
|
||||
}
|
||||
|
||||
function addTailwindClassCompletions() {
|
||||
languages.registerCompletionItemProvider('tailwindcss', {
|
||||
provideCompletionItems: function (model, position, context, token) {
|
||||
const word = model.getWordUntilPosition(position)
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: word.endColumn
|
||||
}
|
||||
|
||||
if (word && word.word) {
|
||||
const currentWord = word.word
|
||||
|
||||
const suggestions = tailwindClasses
|
||||
.filter((className) => className.includes(currentWord))
|
||||
.map((className) => ({
|
||||
label: className,
|
||||
kind: languages.CompletionItemKind.Class,
|
||||
insertText: className,
|
||||
documentation: 'Custom CSS class',
|
||||
range: range
|
||||
}))
|
||||
|
||||
return { suggestions }
|
||||
}
|
||||
|
||||
return { suggestions: [] }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadExtraLib() {
|
||||
if (lang == 'javascript') {
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
@@ -252,38 +301,6 @@
|
||||
} else {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
}
|
||||
} else if (lang === 'css') {
|
||||
const cssClasses = authorizedClassnames.map((className) => '.' + className)
|
||||
|
||||
languages.registerCompletionItemProvider('css', {
|
||||
provideCompletionItems: function (model, position, context, token) {
|
||||
const word = model.getWordUntilPosition(position)
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: word.endColumn
|
||||
}
|
||||
|
||||
if (word && word.word) {
|
||||
const currentWord = word.word
|
||||
|
||||
const suggestions = cssClasses
|
||||
.filter((className) => className.includes(currentWord))
|
||||
.map((className) => ({
|
||||
label: className,
|
||||
kind: languages.CompletionItemKind.Class,
|
||||
insertText: className,
|
||||
documentation: 'Custom CSS class',
|
||||
range: range
|
||||
}))
|
||||
|
||||
return { suggestions }
|
||||
}
|
||||
|
||||
return { suggestions: [] }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import CssEval from './CssEval.svelte'
|
||||
import parse from 'style-to-object'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
|
||||
export let name: string
|
||||
export let value: ComponentCssProperty = {}
|
||||
@@ -177,11 +178,15 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<ClearableInput
|
||||
bind:value={value.class}
|
||||
type="textarea"
|
||||
wrapperClass="h-full"
|
||||
inputClass="h-full !text-xs !rounded-none !p-2 min-h-[72px]"
|
||||
<SimpleEditor
|
||||
class="h-24"
|
||||
lang="tailwindcss"
|
||||
bind:code={value.class}
|
||||
fixedOverflowWidgets={true}
|
||||
small
|
||||
automaticLayout
|
||||
deno={false}
|
||||
subType="tailwind"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -1,35 +1,5 @@
|
||||
import * as csstree from 'css-tree'
|
||||
import type { ComponentCssProperty } from '../../types'
|
||||
|
||||
export function sanitizeCss(css: string, authorizedClassNames: string[]) {
|
||||
const ast = csstree.parse(css)
|
||||
const removedClassNames: string[] = []
|
||||
|
||||
csstree.walk(ast, (node: any, item, list) => {
|
||||
if (node.type === 'Rule') {
|
||||
let shouldRemoveRule = true
|
||||
|
||||
csstree.walk(node, (innerNode: any) => {
|
||||
if (innerNode.type === 'ClassSelector' && authorizedClassNames.includes(innerNode.name)) {
|
||||
shouldRemoveRule = false
|
||||
}
|
||||
if (shouldRemoveRule && innerNode.name) {
|
||||
removedClassNames.push(innerNode.name)
|
||||
}
|
||||
})
|
||||
|
||||
if (shouldRemoveRule) {
|
||||
list.remove(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
css: csstree.generate(ast),
|
||||
removedClassNames
|
||||
}
|
||||
}
|
||||
|
||||
export const authorizedClassnames = [
|
||||
'wm-container',
|
||||
'wm-list',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user