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:
Faton Ramadani
2023-11-28 11:13:49 +01:00
committed by GitHub
parent 726866b410
commit 2d3ce8a49c
8 changed files with 13538 additions and 143 deletions
+24
View File
@@ -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)
+1 -1
View File
@@ -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
+52 -35
View File
@@ -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