Skip to content
This repository was archived by the owner on Dec 28, 2023. It is now read-only.

Commit de6ea90

Browse files
committed
v1.0.0
1 parent 3578d47 commit de6ea90

File tree

13 files changed

+172
-7114
lines changed

13 files changed

+172
-7114
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
# ⓈⓅⒸ
22

3-
Inserting special characters made easy.
3+
![spc-screenshot](screenshot.png)
4+
5+
I'm a dev and I use special characters frequently.
6+
7+
## Installation
8+
9+
* [Chrome/Edge](https://chrome.google.com/webstore/detail/ebgmcealbemklkofilkmnnfnjebedmkd)
10+
* [Firefox](https://addons.mozilla.org/en-US/firefox/addon/spc/)
11+
12+
## Usage
13+
14+
* Right click on the editing area and choose **Special Characters**
15+
* Or, press `Alt + Period` (keys can be [configured](https://www.google.com/search?q=change+shortcut+keys+for+browser+extensions))
16+
17+
## Issues, contributing, etc.
18+
19+
Please feel free to report issues or contribute, and star this repo if it is useful to you. Any support is welcome.

package-lock.json

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"name": "spc",
33
"version": "1.0.0",
44
"description": "A browser extension to insert special characters.",
5-
"homepage": "",
5+
"repository": "https://github.com/khang-nd/spc",
6+
"license": "MIT",
67
"scripts": {
78
"build": "rollup -c",
89
"dev": "rollup -c -w",
@@ -11,14 +12,15 @@
1112
"devDependencies": {
1213
"@rollup/plugin-commonjs": "^17.0.0",
1314
"@rollup/plugin-json": "^4.1.0",
14-
"@rollup/plugin-node-resolve": "^11.0.0",
15-
"rollup": "^2.3.4",
15+
"@rollup/plugin-node-resolve": "^11.2.0",
16+
"rollup": "^2.39.0",
1617
"rollup-plugin-css-only": "^3.1.0",
1718
"rollup-plugin-livereload": "^2.0.0",
1819
"rollup-plugin-svelte": "^7.0.0",
1920
"rollup-plugin-terser": "^7.0.0",
21+
"special-chars": "^0.1.1",
2022
"svelte": "^3.0.0",
21-
"svelte-tabs": "^1.1.0",
23+
"svelte-materialify": "^0.3.5",
2224
"webextension-polyfill": "^0.7.0"
2325
},
2426
"dependencies": {

public/icon.png

10.9 KB
Loading

public/manifest.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,25 @@
33
"name": "SPC",
44
"description": "Inserting special characters made easy.",
55
"version": "1.0.0",
6+
"homepage_url": "https://github.com/khang-nd/spc",
67
"permissions": ["contextMenus", "activeTab"],
8+
"icons": { "48": "icon.png" },
79
"background": {
810
"scripts": ["build/background.js"],
911
"persistent": false
1012
},
1113
"content_scripts": [
1214
{
13-
"matches": ["https://*/*", "http://*/*"],
14-
"css": ["build/main.css"],
15-
"js": ["build/shortcut.js"]
15+
"matches": ["<all_urls>"],
16+
"css": ["build/main.css"]
1617
}
17-
]
18+
],
19+
"commands": {
20+
"open-popup": {
21+
"description": "Activate the extension",
22+
"suggested_key": {
23+
"default": "Alt+Period"
24+
}
25+
}
26+
}
1827
}

rollup.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ function buildConfig(fileName, plugins = []) {
4848

4949
export default [
5050
buildConfig("background.js"),
51-
buildConfig("shortcut.js"),
5251
buildConfig("main.js", [
5352
svelte({
5453
compilerOptions: { dev: !production },

screenshot.png

50.2 KB
Loading

src/Popup.svelte

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,76 @@
11
<script>
2-
import characters from "./characters.json";
2+
import spc from "special-chars";
33
import { onMount } from "svelte";
4-
import { Tabs, Tab, TabList, TabPanel } from "svelte-tabs";
4+
import { Tabs, Tab, TabContent } from "svelte-materialify/dist";
5+
import { isFormElement } from "./utils";
56
6-
let self;
7-
let keyword = "";
8-
let isSearching = false;
9-
let filteredChars = [];
7+
/** @type {Document} */
8+
export let document;
9+
export let id;
10+
export let offset;
1011
1112
const { activeElement } = document;
12-
const { top, left, height } = activeElement.getBoundingClientRect();
13-
const categories = Object.keys(characters);
14-
const allChars = categories.map((category) => characters[category]).flat();
13+
const allChars = Object.keys(spc)
14+
.map((category) => spc[category])
15+
.flat();
16+
17+
let self;
18+
let characters = { ...spc, "🔎": [] };
19+
let categories = Object.keys(characters);
20+
let searchText = "";
1521
1622
function insert({ target }) {
17-
const { tagName } = activeElement;
23+
const attribute = isFormElement(activeElement) ? "value" : "textContent";
1824
activeElement.focus();
19-
tagName === "INPUT" || tagName === "TEXTAREA"
20-
? (activeElement.value += target.textContent)
21-
: (activeElement.textContent += target.textContent);
25+
activeElement[attribute] += target.textContent;
2226
}
2327
2428
function dismiss() {
2529
self.remove();
2630
}
2731
28-
function search({ key }) {
29-
isSearching = true;
30-
keyword += key;
31-
// filteredChars = allChars.filter(({ name }) => name.includes(keyword));
32+
function stopPropagation(event) {
33+
event.stopPropagation();
3234
}
3335
3436
onMount(() => {
35-
self.addEventListener("click", (event) => event.stopPropagation());
3637
document.addEventListener("click", dismiss);
37-
activeElement.addEventListener("keypress", search);
38+
self.addEventListener("click", stopPropagation);
39+
activeElement.addEventListener("click", stopPropagation);
3840
});
41+
42+
$: if (searchText) {
43+
let count = 0;
44+
const condition = ({ name }) =>
45+
name.includes(searchText.toUpperCase()) && ++count <= 150;
46+
characters["🔎"] = allChars.filter(condition);
47+
} else {
48+
characters["🔎"] = [];
49+
}
3950
</script>
4051

41-
<div
42-
bind:this={self}
43-
id="spc"
44-
style="top: {top + height}px; left: {left + 3}px"
45-
>
46-
<p>
47-
Keep typing to find a character
48-
<button aria-label="Close" on:click={dismiss}>x</button>
49-
</p>
50-
<Tabs>
52+
<main bind:this={self} {id} style="top: {offset.y}px; left: {offset.x}px">
53+
<header>
54+
<input type="search" placeholder="Search..." bind:value={searchText} />
55+
<button aria-label="Close" on:click={dismiss}>✕</button>
56+
</header>
57+
<Tabs vertical value={searchText ? categories.length - 1 : 0}>
5158
{#each categories as category}
52-
<TabPanel>
53-
{#if isSearching}
54-
<ul>
55-
{#each filteredChars as { char }}
56-
<li on:click={insert}>{char}</li>
57-
{/each}
58-
</ul>
59-
{:else}
60-
<ul>
61-
{#each characters[category] as { char }}
62-
<li on:click={insert}>{char}</li>
63-
{/each}
64-
</ul>
65-
{/if}
66-
</TabPanel>
59+
<TabContent>
60+
<ul>
61+
{#each characters[category] as { name, char }}
62+
<li title={name} on:click={insert}>{char}</li>
63+
{/each}
64+
</ul>
65+
</TabContent>
6766
{/each}
68-
69-
<TabList>
67+
<div slot="tabs">
7068
{#each categories as category}
7169
<Tab>{category}</Tab>
7270
{/each}
73-
</TabList>
71+
</div>
7472
</Tabs>
75-
</div>
73+
</main>
7674

7775
<style>
7876
:root {
@@ -82,24 +80,31 @@
8280
#spc {
8381
z-index: 9999;
8482
position: absolute;
85-
background: #f9f9f9;
86-
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
83+
background: #f8f8f8;
84+
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
8785
font-size: 16px;
86+
padding-bottom: var(--spacing);
8887
}
8988
90-
p {
91-
margin: var(--spacing);
92-
color: #666;
89+
header {
90+
display: flex;
91+
padding: var(--spacing);
92+
}
93+
94+
input[type="search"] {
95+
outline-offset: initial;
96+
padding: 0.1em 0.3em;
97+
margin-right: var(--spacing);
98+
background: #fff;
99+
box-sizing: border-box;
100+
flex-grow: 2;
93101
}
94102
95103
button[aria-label="Close"] {
96-
float: right;
97-
font-family: monospace;
98104
background: #eee;
99105
color: #000;
100106
border: none;
101107
padding: 0;
102-
margin: calc(var(--spacing) * -1 + 0.1em);
103108
width: 30px;
104109
height: 30px;
105110
line-height: 0;
@@ -111,18 +116,33 @@
111116
}
112117
113118
ul {
114-
width: 400px;
119+
width: 310px;
115120
height: 300px;
121+
margin: 0;
116122
padding: var(--spacing) !important;
117123
text-align: center;
124+
box-sizing: border-box;
118125
overflow: auto;
119126
list-style: none;
120-
display: grid;
121-
grid-template-columns: repeat(12, auto);
127+
display: flex;
128+
flex-wrap: wrap;
129+
}
130+
131+
li {
132+
width: 25px;
133+
height: 25px;
134+
display: flex;
135+
justify-content: center;
136+
align-items: center;
137+
color: #000;
122138
}
123139
124140
li:hover {
125141
background: #eee;
126142
cursor: default;
127143
}
144+
145+
:global(.s-window) {
146+
background: #fff;
147+
}
128148
</style>

src/background.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import browser from "webextension-polyfill";
22

3-
const { contextMenus, tabs } = browser;
3+
const { contextMenus, tabs, commands } = browser;
44

5-
contextMenus.create({
6-
id: "spc",
7-
title: "Special characters",
8-
contexts: ["editable"],
9-
});
5+
function run() {
6+
tabs.executeScript({ file: "/build/main.js" });
7+
}
108

11-
contextMenus.onClicked.addListener(() =>
12-
tabs.executeScript({ file: "/build/main.js" })
13-
);
9+
contextMenus.removeAll().then(() => {
10+
contextMenus.create({
11+
id: "spc",
12+
title: "Special characters",
13+
contexts: ["editable"],
14+
});
15+
contextMenus.onClicked.addListener(run);
16+
commands.onCommand.addListener(run);
17+
});

0 commit comments

Comments
 (0)