mirror of
https://github.com/gra0007/obsidian-css-inlay-colors.git
synced 2026-07-22 05:18:59 +00:00
Parse and display inlays for palette colors
This commit is contained in:
parent
863b0c1bdf
commit
8b446c94f0
7 changed files with 110 additions and 27 deletions
|
|
@ -9,3 +9,7 @@ fs: Federal Standard (FS 595C, ANA)
|
|||
pantone: Pantone
|
||||
ral: RAL Colors (Classic, Design, Effect, Plastics)
|
||||
*/
|
||||
|
||||
.css-color-inlay {
|
||||
&.aci-0 { color: #000000; }
|
||||
}
|
||||
|
|
|
|||
21
src/live.ts
21
src/live.ts
|
|
@ -12,6 +12,7 @@ import type { NodeProp } from '@lezer/common'
|
|||
import { type Color, formatHex } from 'culori'
|
||||
import { editorLivePreviewField } from 'obsidian'
|
||||
import { formatColor } from './formatColor'
|
||||
import { getPaletteClass, getPaletteClasses } from './palettes'
|
||||
import { parseColor } from './parseColor'
|
||||
import type { CssColorsPluginSettings } from './settings'
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ export const inlayExtension = (settings: CssColorsPluginSettings) => {
|
|||
class CSSColorInlayWidget extends WidgetType {
|
||||
constructor(
|
||||
readonly text: string,
|
||||
readonly color: Color,
|
||||
readonly color: Color | 'palette',
|
||||
readonly colorPickerEnabled: boolean,
|
||||
readonly hideName: boolean,
|
||||
readonly view: EditorView,
|
||||
|
|
@ -58,10 +59,14 @@ class CSSColorInlayWidget extends WidgetType {
|
|||
|
||||
toDOM() {
|
||||
const inlay = document.createElement('label')
|
||||
inlay.className = `css-color-inlay ${this.hideName ? 'css-color-name-hidden' : ''}`
|
||||
inlay.style.setProperty('--css-color-inlay-color', this.text)
|
||||
inlay.className = `css-color-inlay ${
|
||||
this.color === 'palette'
|
||||
? `css-color-palette ${getPaletteClass(this.text)}`
|
||||
: ''
|
||||
} ${this.hideName ? 'css-color-name-hidden' : ''}`
|
||||
inlay.style.color = this.text
|
||||
|
||||
if (this.colorPickerEnabled) {
|
||||
if (this.colorPickerEnabled && this.color !== 'palette') {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'color'
|
||||
input.value = formatHex(this.color)
|
||||
|
|
@ -78,7 +83,7 @@ class CSSColorInlayWidget extends WidgetType {
|
|||
to: pos + this.text.length,
|
||||
insert: formatColor(
|
||||
this.text,
|
||||
this.color,
|
||||
this.color as Color,
|
||||
(e.currentTarget as HTMLInputElement).value,
|
||||
),
|
||||
},
|
||||
|
|
@ -103,6 +108,7 @@ const createColorWidgets = (
|
|||
if (!view.state.field(editorLivePreviewField)) return Decoration.none
|
||||
|
||||
const widgets: Range<Decoration>[] = []
|
||||
const paletteClasses = getPaletteClasses()
|
||||
|
||||
for (const { from, to } of view.visibleRanges) {
|
||||
syntaxTree(view.state).iterate({
|
||||
|
|
@ -112,7 +118,10 @@ const createColorWidgets = (
|
|||
if (
|
||||
node.type.prop(tokenClassNodeProp)?.split(' ').includes('inline-code')
|
||||
) {
|
||||
const res = parseColor(view.state.sliceDoc(node.from, node.to))
|
||||
const res = parseColor(
|
||||
view.state.sliceDoc(node.from, node.to),
|
||||
paletteClasses,
|
||||
)
|
||||
if (!res) return
|
||||
const { text, color, isNameHidden } = res
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,57 @@ import { type App, Modal, Notice, normalizePath, Setting } from 'obsidian'
|
|||
|
||||
export const SNIPPET_NAME = 'css-inlay-palettes'
|
||||
|
||||
/** Normalize a palette name into a class */
|
||||
export const getPaletteClass = (text: string) =>
|
||||
text.trim().toLocaleLowerCase().replace(/\s+/, '-')
|
||||
|
||||
const parsePaletteClasses = (selectorText: string) => {
|
||||
return selectorText.split(',').flatMap((selector) => {
|
||||
const name = selector
|
||||
.replaceAll('.css-color-inlay', '')
|
||||
.replace(/[&.]/g, '')
|
||||
.trim()
|
||||
return name.toLocaleLowerCase() === name ? [name] : []
|
||||
})
|
||||
}
|
||||
|
||||
/** Get a list of all palette classes available */
|
||||
export const getPaletteClasses = () => {
|
||||
const classes = []
|
||||
|
||||
for (const sheet of document.styleSheets) {
|
||||
for (const rule of sheet.cssRules) {
|
||||
if (
|
||||
rule instanceof CSSStyleRule &&
|
||||
rule.selectorText.contains('.css-color-inlay')
|
||||
) {
|
||||
// Nested rule
|
||||
if (rule.cssRules.length > 0) {
|
||||
for (const nestedRule of rule.cssRules) {
|
||||
if (
|
||||
nestedRule instanceof CSSStyleRule &&
|
||||
nestedRule.selectorText.startsWith('&') &&
|
||||
nestedRule.styleMap.has('color')
|
||||
) {
|
||||
classes.push(...parsePaletteClasses(nestedRule.selectorText))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Individual rule (.css-color-inlay.my-color)
|
||||
if (
|
||||
rule.selectorText !== '.css-color-inlay' &&
|
||||
rule.cssRules.length === 0 &&
|
||||
rule.styleMap.has('color')
|
||||
) {
|
||||
classes.push(...parsePaletteClasses(rule.selectorText))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
const createPaletteFilePath = (app: App) =>
|
||||
normalizePath(`${app.vault.configDir}/snippets/${SNIPPET_NAME}.css`)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { type Color, parse, parseHex } from 'culori'
|
||||
import { getPaletteClass } from 'src/palettes'
|
||||
|
||||
export const parseColor = (text: string) => {
|
||||
export const parseColor = (text: string, paletteClasses: string[]) => {
|
||||
// If color is surrounded with square brackets, the name should be hidden
|
||||
let isNameHidden = false
|
||||
if (text.startsWith('[') && text.endsWith(']')) {
|
||||
|
|
@ -9,14 +10,28 @@ export const parseColor = (text: string) => {
|
|||
}
|
||||
|
||||
// Check if color is valid
|
||||
let color: Color | undefined
|
||||
try {
|
||||
color = parse(text)
|
||||
if (color === undefined) return
|
||||
// Ignore hex colors that don't start with a hash
|
||||
if (color.mode === 'rgb' && parseHex(text) && text.charAt(0) !== '#') return
|
||||
} catch {
|
||||
return
|
||||
let color: Color | 'palette' | undefined
|
||||
|
||||
// Is palette color
|
||||
if (
|
||||
text.startsWith('(') &&
|
||||
text.endsWith(')') &&
|
||||
paletteClasses.includes(getPaletteClass(text.slice(1, -1)))
|
||||
) {
|
||||
text = text.slice(1, -1)
|
||||
color = 'palette'
|
||||
}
|
||||
|
||||
if (!color) {
|
||||
try {
|
||||
color = parse(text)
|
||||
if (color === undefined) return
|
||||
// Ignore hex colors that don't start with a hash
|
||||
if (color.mode === 'rgb' && parseHex(text) && text.charAt(0) !== '#')
|
||||
return
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return { text, color, isNameHidden }
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
import { Notice } from 'obsidian'
|
||||
import { getPaletteClass, getPaletteClasses } from './palettes'
|
||||
import { parseColor } from './parseColor'
|
||||
import type { CssColorsPluginSettings } from './settings'
|
||||
|
||||
export const inlayPostProcessor =
|
||||
(settings: CssColorsPluginSettings) => (el: HTMLElement) => {
|
||||
const paletteClasses = getPaletteClasses()
|
||||
|
||||
for (const code of el.findAll('code')) {
|
||||
const res = parseColor(code.innerText.trim())
|
||||
const res = parseColor(code.innerText.trim(), paletteClasses)
|
||||
if (!res) continue
|
||||
const { text, isNameHidden } = res
|
||||
const { text, color, isNameHidden } = res
|
||||
|
||||
// Clear codeblock before adding inlay
|
||||
if (settings.hideNames || isNameHidden) {
|
||||
code.innerHTML = ''
|
||||
}
|
||||
if (settings.hideNames || isNameHidden) code.empty()
|
||||
|
||||
const paletteClass =
|
||||
color === 'palette' ? `css-color-palette ${getPaletteClass(text)}` : ''
|
||||
|
||||
code.createSpan({
|
||||
prepend: true,
|
||||
cls: `css-color-inlay ${settings.hideNames || isNameHidden ? 'css-color-name-hidden' : ''}`,
|
||||
attr: { style: `--css-color-inlay-color: ${text};` },
|
||||
cls: `css-color-inlay ${paletteClass} ${settings.hideNames || isNameHidden ? 'css-color-name-hidden' : ''}`,
|
||||
attr: { style: `color: ${text};` },
|
||||
})
|
||||
|
||||
if (settings.copyOnClick) {
|
||||
|
|
@ -31,8 +35,8 @@ export const inlayPostProcessor =
|
|||
.then(() => {
|
||||
const toast = document.createDocumentFragment()
|
||||
const toastColor = document.createElement('span')
|
||||
toastColor.className = 'css-color-inlay'
|
||||
toastColor.style = `--css-color-inlay-color: ${text};`
|
||||
toastColor.className = `css-color-inlay ${paletteClass}`
|
||||
toastColor.style.color = text
|
||||
toast.append(toastColor)
|
||||
toast.append(
|
||||
document.createTextNode('Copied color to the clipboard'),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
.css-color-inlay {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
border: 1px solid currentColor;
|
||||
border: 1px solid var(--code-normal);
|
||||
display: inline-block;
|
||||
border-radius: 0.2em;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.3em;
|
||||
margin-top: -0.1em;
|
||||
background: var(--css-color-inlay-color);
|
||||
background: currentColor;
|
||||
|
||||
&.css-color-name-hidden {
|
||||
margin-right: 0;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7", "ES2024"]
|
||||
"lib": ["DOM", "DOM.Iterable", "ES5", "ES6", "ES7", "ES2024"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue