Your browser's new homepage.
- Embed YT, Spotify, or web pages.
- Customizable bookmark dock.
- Markdown notes.
- Dark/Light mode support.
- Rich shortcuts with sensible defaults.
- Extensible API for power user.
- Artwork: https://www.pixiv.net/artworks/55632215
- Website: http://abehiroshi.la.coocan.jp/
- Video: https://www.youtube.com/watch?v=dkSeImiW1fQ
- Song: https://open.spotify.com/track/6woV8uWxn7rcLZxJKYruS1
- Install Bun (or Node.js if preferred).
- Install dependencies:
bun install
- Start local development server, will automatically open the page.
bun dev-site
- Have fun!
Sticky is the core feature of mencrouche, allowing you to drag, resize, pin, or hide the border of stickies. You can also extend sticky functionality to fit your needs, such as creating Markdown sticky, YouTube sticky, or Spotify sticky.
Mencrouche provides rich API for extensions:
Let's create a Clock sticky that displays the current time. We'll add a span
element inside the sticky body and update the time using setInterval
. Paste the following code into the browser console:
mc.registerSticky({
type: "clock",
onCreate(sticky) {
const span = document.createElement("span");
sticky.replaceBody(span);
setInterval(() => {
span.textContent = new Date().toLocaleString();
}, 100);
},
onSave() {},
onDelete() {},
onRestore() {},
});
mc.stickyWorkspace.create({ type: "clock" });
After executing the code, you should see a sticky appended to the workspace:
The on
functions are sticky life cycles:
The previous code snippet does not clean up the setInterval
callback. If you close the sticky, the callback continues to run, leading to unnecessary calculations. Below is an improved version (remember to refresh the page before pasting):
mc.registerSticky({
type: "clock",
onCreate(sticky) {
const span = document.createElement("span");
sticky.replaceBody(span);
// Store in `sticky.plugin` to prevent name collision.
sticky.plugin.intervalId = setInterval(() => {
console.log("update time");
span.textContent = new Date().toLocaleString();
}, 100);
},
onSave() {},
onDelete(sticky) {
clearInterval(sticky.plugin.intervalId);
},
onRestore() {},
});
mc.stickyWorkspace.create({ type: "clock" });
In this version, "update time" is printed every 100ms. After closing the sticky, the message will no longer appear.
Next, we'll add a feature to change the clock's color.
function enableClock(sticky) {
const span = document.createElement("span");
const randomColorBtn = document.createElement("button");
randomColorBtn.textContent = "Change color";
randomColorBtn.addEventListener("click", () => {
const color = "#" + (~~(Math.random() * (1 << 24))).toString(16);
span.style.color = color;
sticky.plugin.color = color;
});
sticky.replaceBody(span, randomColorBtn);
sticky.plugin.intervalId = setInterval(() => {
span.textContent = new Date().toLocaleString();
}, 100);
}
mc.registerSticky({
type: "clock",
onCreate(sticky) {
enableClock(sticky);
},
onSave(sticky) {
// Save the color to use later in `onRestore`
return {
color: sticky.plugin.color,
};
},
onDelete(sticky) {
clearInterval(sticky.plugin.intervalId);
},
onRestore(sticky, pluginConfig) {
enableClock(sticky);
sticky.querySelector("span").style.color = pluginConfig.color;
},
});
mc.stickyWorkspace.create({ type: "clock" });
Ta-da!
The new version refactored the DOM elements initialization, and established interactivity into one enableClock
function. The onSave
function returns an object containing the color property, which is stored in a JSON file. During onRestore
, we retrieve and apply this saved color using the pluginConfig parameter.
In my opinion, the JQuery API is more intuitive than the native DOM API, but we didn’t want to include JQuery due to its package size. So, we created our own lightweight JQuery-like tool/alias for Mencrouche, called Dollars.
Function/Alias | Syntax | Purpose | Returns | Example |
---|---|---|---|---|
$ |
$("selector") |
Select first matching element | Single element | $("a").href = "url" |
$$ |
$$("selector") |
Select all matching elements | Array of elements | $$("a").forEach(el => ...) |
$$$ |
$$$("tagName") |
Create new element | New element | $$$("div") |
Element.$ |
element.$("selector") |
Select first match within element | Single element | myDiv.$(".item").value = "text" |
Element.$$ |
element.$$("selector") |
Select all matches within element | Array of elements | myDiv.$$(".item").forEach(...) |
on |
element.on("event", handler) |
Add event listener | - | button.on("click", () => ...) |
off |
element.off("event", handler) |
Remove event listener | - | button.off("click", handler) |
h |
h("html string") |
Convert HTML string to element | New element | h("<div>Hello</div>") |
- A single dollar sign selects only the first matching element:
// JQuery $("a")[0].href = "https://youtube.com"; // Dollars mc.$("a").href = "https://youtube.com";
- Double dollar signs return an array of selected elements. There are no helper functions:
// JQuery $("a").addClass("link"); // Dollars mc.$$("a").forEach((el) => el.classList.add("link"));
- Triple dollar signs are an alias for
document.createElement()
:// JQuery $("<div></div>"); // Dollars mc.$$$("div");
- Trailing
$
and$$
are used for selecting elements within a subtree:// JQuery $(pureDomElement, ".comment").value("Start typing"); // Dollars pureDomElement.$(".comment").value = "Start typing"; pureDomElement.$$(".comment").forEach((el) => el.value = "Start typing");
on
,off
for add or remove event listeners:// JQuery $(button).click(function() { alert("hello"); }); // Dollars button.on("click", function() { alert("hello"); });
h
are a HTML string to Element constructor:// JQuery $.parseHTML("<div></div>"); // Dollars mc.h("<div></div>");
We can combine previous clock example with Dollars API:
function enableClock(sticky, color) {
const clockBody = mc.h(`
<span ${color ? `style="color: ${color}` : ""}></span>
<button type="button">
Change Color
</button>
`);
const timeElement = clockBody.$("time");
const randomColorBtn = clockBody.$("button");
sticky.replaceBody(clockBody);
randomColorBtn.on("click", () => {
const color = "#" + (~~(Math.random() * (1 << 24))).toString(16);
timeElement.style.color = color;
sticky.plugin.color = color;
});
sticky.plugin.intervalId = setInterval(() => {
timeElement.textContent = new Date().toLocaleString();
}, 100);
}
mc.registerSticky({
type: "clock",
onCreate(sticky) {
enableClock(sticky);
},
onSave(sticky) {
// Save the color to use later in `onRestore`
return {
color: sticky.plugin.color,
};
},
onDelete(sticky) {
clearInterval(sticky.plugin.intervalId);
},
onRestore(sticky, pluginConfig) {
enableClock(sticky, pluginConfig.color);
},
});
mc.stickyWorkspace.create({ type: "clock" });
Mencrouche has a built-in internationalization feature. Use mc.n81i
(the palindrome of the i18n ;) ) to translate content into different locales.
mc.n81i.addTranslations({
ja: {
hello: {
message: "こんにちは",
description: "Greeting message for the nav bar.",
},
},
zh_TW: {
hello: {
message: "你好",
description: "Greeting message for the nav bar.",
},
},
});
await mc.n81i.changeLanguage("zh_TW");
console.log(mc.n81i.t("hello")); // prints "你好"
Use the data-i18n
attribute to specify a translation key. By default, it will replace the textContent
of element. Add a data-i18n-for
to inject translated message into a specific attribute.
// Before: <div data-i18n="author"></div>
// After: <div data-i18n="author">作者</div>
mc.n81i.translateElement(div);
// Before: <input data-i18n="email" data-i18n-for="placeholder" />
// After: <input data-i18n="email" data-i18n-for="placeholder" placeholder="電子郵件" />
mc.n81i.translateElement(input);
The apocalypse API allowing users to revert or reapply changes. Users can trigger undo/redo actions via Ctrl + Z
and Ctrl + Y
.
let bookmark;
mc.apocalypse.write({
execute() { // <-- Execute immediately.
bookmark = createBookmark();
},
undo() {
bookmark.remove();
},
});
mc.apocalypse.undo(); // Undo previous changes.
mc.apocalypse.redo(); // Redo changes.
You can use mc.registerContextMenu
to create custom context menus associated with specific DOM elements. By assigning a data-context-menu
attribute to a target element, the target element will returns for execute
function.
// Assign the data-context-menu attribute for DOM element as context menu target.
youtubeSticky.dataset.contextMenu = "youtube";
mc.registerContextMenu("youtube", [
{
name: "pause_video",
icon: "lucide-youtube",
execute(target) {
// In this example, target === youtubeSticky.
// You may have multiple youtubeStickies. In that case, the target
// indicates the sticky, from which the user triggers the context menu.
target.pauseVideo();
},
},
]);
Use mc.registerCommand
to register custom commands within Mencrouche. The commands will show in command palette or context menu. You can also give shortcuts for the commands (see KikeyJS Doc for more information).
mc.registerCommand({
name: "say_hi", // This string will be the key of `n81i`,
// you should add translations for this key.
execute() {
alert("Hi");
},
defaultShortcut: "C-A-h", // Ctrl + Alt + H
});
mc.executeCommand("say_hi");