Skip to content

Commit 839e3ad

Browse files
committed
v2
1 parent 634b1cd commit 839e3ad

File tree

10 files changed

+190
-57
lines changed

10 files changed

+190
-57
lines changed

background.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
1010
const protos = await getAllCssProtos()
1111
console.log('protos', protos)
1212
protos
13-
.filter((proto) => proto.options.active && url.href.match(proto.urlMatch))
13+
.filter((proto) => proto.isActive && url.href.match(proto.urlMatch))
1414
.forEach((proto) => injectCss(proto, tabId))
1515
}
1616
})

public/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"manifest_version": 3,
3-
"name": "Prototype CSS",
3+
"name": "Kiss my CSS",
44
"description": "A quick way to prototype CSS :)",
55
"version": "0.0.1",
66
"action": {
77
"default_popup": "popup.html"
88
},
9-
"permissions": ["activeTab", "storage", "tabs", "scripting"],
9+
"permissions": ["storage", "tabs", "scripting"],
1010
"host_permissions": ["*://*/*"],
1111
"options_page": "options.html",
1212
"background": {

screenshots/in-action.png

28.8 KB
Loading

screenshots/options.png

25.7 KB
Loading

src/inject.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ export const injectCss = async (cssProto: CSSProto, tabId: number | null = null)
1212
// @ts-ignore
1313
func: (script: CSSProto) => {
1414
let cssToInject = script.cssRaw
15-
if (script.options.important) cssToInject = cssToInject.replaceAll(';', ' !important;')
15+
// TODO remove
16+
if (script.isImportant || true) cssToInject = cssToInject.replaceAll(';', ' !important;')
1617

1718
const style = document.createElement('style')
1819
style.setAttribute('data-source', 'CSS Prototype - ' + script.name)
1920
style.setAttribute('data-id', script.id)
2021
style.appendChild(document.createTextNode(cssToInject))
2122
document.head.append(style)
2223

23-
if (script.options.shadowDom) {
24+
if (script.isShadowDom) {
2425
;[...document.body.querySelectorAll('*')]
2526
.filter((el) => el.shadowRoot)
2627
.forEach((elWithShadow) => elWithShadow.shadowRoot?.append(style.cloneNode(true)))

src/options/options.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ import AceEditor from 'react-ace'
66
import { toast, ToastContainer } from 'react-toastify'
77
import 'react-toastify/dist/ReactToastify.css'
88
import short from 'short-uuid'
9-
import { addCSSProto, deleteCSSProto, getAllCssProtos, updateCSSProto } from '../storage'
9+
import {
10+
addCSSProto,
11+
deleteCSSProto,
12+
getAllCssProtos,
13+
getItem,
14+
importFromUrl,
15+
setItem,
16+
updateCSSProto,
17+
} from '../storage'
1018
import { CSSProto } from '../types'
1119
import { CSSEditor } from './cssEditor'
1220
import { useHash } from './hooks/useHash'
@@ -19,6 +27,7 @@ export const Options = () => {
1927

2028
const [protoName, setProtoName] = useState('')
2129
const [urlMatch, setUrlMatch] = useState('')
30+
const [importUrl, setImportUrl] = useState('')
2231
const editorRef = useRef<AceEditor>(null)
2332

2433
const updateProtos = () =>
@@ -66,7 +75,7 @@ export const Options = () => {
6675
<br />
6776
<span style={{ opacity: 0.5 }}>Check console for details</span>
6877
</>,
69-
{ type: 'error', autoClose: 2000, hideProgressBar: false }
78+
{ type: 'warning', autoClose: 2000, hideProgressBar: false }
7079
)
7180
// if formatting goes wrong i want to save the original css because
7281
// i do not want to lose work
@@ -80,8 +89,6 @@ export const Options = () => {
8089
name: protoName.trim() || 'untitled',
8190
urlMatch,
8291
cssRaw: formattedCss,
83-
cssCompiled: '',
84-
options: {},
8592
}
8693
await addCSSProto(newProto)
8794
// immediately navigate to the new hash
@@ -120,6 +127,7 @@ export const Options = () => {
120127

121128
useEffect(() => {
122129
updateProtos()
130+
getItem<string>('import-url').then((url) => setImportUrl(url || ''))
123131
}, [])
124132

125133
useEffect(() => {
@@ -132,13 +140,41 @@ export const Options = () => {
132140
<div className="grid" style={{ gridTemplateColumns: '.3fr .7fr', paddingBottom: '2rem' }}>
133141
<div>
134142
<h4 style={{ marginBottom: '.5rem' }}>Prototypes</h4>
143+
<div>
144+
<input
145+
type="text"
146+
placeholder="Import from URL"
147+
value={importUrl}
148+
onChange={(e) => setImportUrl(e.target.value)}
149+
style={{ fontSize: '0.8rem' }}
150+
/>
151+
<button
152+
onClick={() => {
153+
try {
154+
new URL(importUrl)
155+
} catch (error) {
156+
return toast('Invalid URL', { type: 'error' })
157+
}
158+
confirm('If you pull, prototypes with the same id will be overwritten.\nAre you sure?') &&
159+
importFromUrl(importUrl)
160+
.then(updateProtos)
161+
.then(() => toast('Pulled!', { type: 'success' }))
162+
.then(() => setItem('import-url', importUrl))
163+
}}
164+
className=" outline"
165+
style={{ fontSize: '1.5rem', padding: 0, lineHeight: 0, marginBottom: '1rem' }}
166+
>
167+
<i className="bi bi-arrow-down-short" />
168+
</button>
169+
</div>
135170
<button
136171
onClick={() => (location.hash = short.generate())}
137172
className=" outline"
138173
style={{ fontSize: '1.5rem', padding: 0, lineHeight: 0 }}
139174
>
140175
<i className="bi bi-plus" />
141176
</button>
177+
142178
{Object.keys(allCssProtosByUrl)
143179
.sort()
144180
.map((urlMatch) => (

src/popup/popup.tsx

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,45 +31,54 @@ export const Popup = () => {
3131
.then((cssProtos) => setCssProtos(cssProtos))
3232

3333
const handleSwitch = async (cssProto: CSSProto) => {
34-
await setActive(cssProto.id, !cssProto.options.active)
35-
if (cssProto.options.active) removeCss(cssProto)
34+
await setActive(cssProto.id, !cssProto.isActive)
35+
if (cssProto.isActive) removeCss(cssProto)
3636
else injectCss(cssProto)
3737
updateProtos()
3838
}
3939

4040
return (
41-
<div style={{ padding: '1rem 0' }}>
42-
<hgroup>
43-
<h5 className="">CSS Prototypes</h5>
44-
<h6>{url?.hostname}</h6>
45-
</hgroup>
46-
<div className="my-2">
47-
{cssProtos.map((proto) => (
48-
<div style={{ display: 'flex' }}>
49-
<label className="ellipsis" style={{ marginRight: 'auto' }}>
50-
<input
51-
type="checkbox"
52-
role="switch"
53-
checked={proto.options.active}
54-
onChange={() => handleSwitch(proto)}
55-
/>
56-
{proto.name}
57-
</label>
58-
<a href={'options.html#' + proto.id} target="_blank">
59-
<i className="bi bi-pencil" />
41+
<>
42+
<div style={{ padding: '1rem 0' }}>
43+
<hgroup>
44+
<h5>
45+
<img src="icons/128.png" style={{ height: '1em', verticalAlign: '-0.1em' }} /> Kiss my CSS
46+
<a
47+
href=""
48+
onClick={(e) => {
49+
e.preventDefault()
50+
chrome.runtime.openOptionsPage()
51+
}}
52+
style={{ float: 'right' }}
53+
>
54+
<i className="bi bi-gear" />
6055
</a>
61-
</div>
62-
))}
56+
</h5>
57+
<h6>{url?.hostname}</h6>
58+
</hgroup>
59+
<div className="my-2">
60+
{cssProtos.map((proto) => (
61+
<div style={{ display: 'flex' }}>
62+
<label className="ellipsis" style={{ marginRight: 'auto' }}>
63+
<input type="checkbox" role="switch" checked={proto.isActive} onChange={() => handleSwitch(proto)} />
64+
{proto.name}
65+
</label>
66+
<a href={'options.html#' + proto.id} target="_blank">
67+
<i className="bi bi-pencil" />
68+
</a>
69+
</div>
70+
))}
71+
</div>
72+
<a
73+
href={`options.html#${short.generate()}=//${url?.hostname}`}
74+
role="button"
75+
className="center-center outline"
76+
style={{ fontSize: '1.5rem', padding: '.2rem', lineHeight: 1 }}
77+
target="_blank"
78+
>
79+
<i className="bi bi-plus" />
80+
</a>
6381
</div>
64-
<a
65-
href={`options.html#${short.generate()}=//${url?.hostname}`}
66-
role="button"
67-
className="center-center outline"
68-
style={{ fontSize: '1.5rem', padding: '.2rem', lineHeight: 1 }}
69-
target="_blank"
70-
>
71-
<i className="bi bi-plus" />
72-
</a>
73-
</div>
82+
</>
7483
)
7584
}

src/storage.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import { CSSProto } from './types'
22

3-
const getItem = async <T>(key: string): Promise<T | null> => {
3+
export const getItem = async <T>(key: string): Promise<T | null> => {
44
const item = await chrome.storage.local.get(key)
55
return item[key] ? (JSON.parse(item[key]) as T) : null
66
}
77

8-
const setItem = (key: string, value: any) => chrome.storage.local.set({ [key]: JSON.stringify(value) })
8+
export const setItem = (key: string, value: any) => chrome.storage.local.set({ [key]: JSON.stringify(value) })
99

1010
export const getSettings = () => getItem('settings')
1111
export const getSites = () => getItem('sites')
1212

1313
export const setSettings = (settings: any) => setItem('settings', settings)
1414

1515
export const getAllCssProtos = () => getItem<CSSProto[]>('css-protos').then((res) => res || [])
16+
export const getAllCssProtosById = async () => {
17+
const cssProtos = await getAllCssProtos()
18+
return cssProtos.reduce((acc: { [id: string]: CSSProto }, cssProto) => ({ ...acc, [cssProto.id]: cssProto }), {})
19+
}
20+
1621
export const addCSSProto = async (cssProto: CSSProto) => setItem('css-protos', [...(await getAllCssProtos()), cssProto])
1722
export const deleteCSSProto = async (id: string) =>
1823
setItem(
@@ -34,30 +39,52 @@ export const updateCSSProto = async (id: string, partialCssProto: Partial<CSSPro
3439
export const setActive = async (id: string, active: boolean) => {
3540
const cssProtos = await getAllCssProtos()
3641
const index = cssProtos.findIndex((cssProto) => cssProto.id == id)
37-
cssProtos[index].options.active = active
42+
cssProtos[index].isActive = active
3843
return setItem('css-protos', cssProtos)
3944
}
4045

46+
export const bulkUpdateOrCreate = async (cssProtos: CSSProto[]) => {
47+
const allProtos = await getAllCssProtosById()
48+
for (const proto of cssProtos) {
49+
if (allProtos[proto.id]) await updateCSSProto(proto.id, proto)
50+
else await addCSSProto(proto)
51+
}
52+
}
53+
54+
export const importFromUrl = async (url: string) => {
55+
// fetch the gist content
56+
const rawGist = await fetch(url).then((res) => res.text())
57+
const cssProtos = transformGist(rawGist)
58+
return bulkUpdateOrCreate(cssProtos)
59+
}
60+
61+
export const transformGist = (rawGist: string) =>
62+
rawGist
63+
.split(/\/\*{3,}/)
64+
.map((blocks) => blocks.trim())
65+
.filter((blocks) => blocks.length > 0)
66+
.map((blocks) => blocks.split('\n').map((line) => line.trim()))
67+
.reduce((acc: CSSProto[], blockLines) => {
68+
const [id, name, urlMatch, _, maybeLF, ...cssRaw] = blockLines
69+
return [...acc, { id, name, urlMatch, cssRaw: (maybeLF == '\n' ? '' : maybeLF) + cssRaw.join('\n') }]
70+
}, [])
71+
4172
const cssProtos: CSSProto[] = [
4273
{
4374
id: '1',
4475
urlMatch: 'https://www.google.com/',
4576
name: 'Google',
4677
cssRaw: 'body { background-color: red; }',
4778
cssCompiled: 'body { background-color: red; }',
48-
options: {
49-
active: true,
50-
},
79+
isActive: true,
5180
},
5281
{
5382
id: '2',
5483
urlMatch: 'https://luca.gg/',
5584
name: 'Google 2',
5685
cssRaw: 'body { background-color: blue; }',
5786
cssCompiled: 'body { background-color: blue; }',
58-
options: {
59-
active: true,
60-
},
87+
isActive: true,
6188
},
6289
]
6390

src/types.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ export type CSSProto = {
33
name: string
44
urlMatch: string
55
cssRaw: string
6-
cssCompiled: string
7-
options: {
8-
active?: boolean
9-
important?: boolean
10-
shadowDom?: boolean
11-
}
6+
cssCompiled?: string
7+
isActive?: boolean
8+
isImportant?: boolean
9+
isShadowDom?: boolean
1210
}

transform.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const rawGist = `
2+
/***
3+
laskdjf9as8djfalkdsfj
4+
black background
5+
6+
*/
7+
8+
.body {
9+
background: #000;
10+
color: #fff;
11+
font-family: sans-serif;
12+
font-size: 16px;
13+
}
14+
15+
some-other-component {
16+
color: #fff;
17+
font-family: sans-serif;
18+
font-size: 16px;
19+
background: #000;
20+
}
21+
22+
/* some commant */
23+
/* some multi line comment
24+
cool
25+
*/
26+
27+
/***
28+
0a9s7doj21094ijdfsdf
29+
white background
30+
//www.google.com
31+
*/
32+
33+
.body {
34+
background: #fff;
35+
color: #000;
36+
font-family: sans-serif;
37+
}
38+
`
39+
40+
/*
41+
output format should be:
42+
{
43+
id: string,
44+
name: string,
45+
urlMatch: string,
46+
cssRaw: string,
47+
options: {}
48+
*/
49+
50+
const transform = (rawGist) => {
51+
return rawGist
52+
.split(/\/\*{3,}/)
53+
.map((blocks) => blocks.trim())
54+
.filter((blocks) => blocks.length > 0)
55+
.map((blocks) => blocks.split('\n').map((block) => block.trim()))
56+
.reduce((acc, block) => {
57+
const [id, name, urlMatch, _, maybeNewLine, ...cssRaw] = block
58+
return [...acc, { id, name, urlMatch, cssRaw: cssRaw.join('\n') }]
59+
}, [])
60+
}
61+
62+
console.log(transform(rawGist))

0 commit comments

Comments
 (0)