Last active
March 25, 2025 09:55
-
-
Save silverqx/99a8fe0b474d048e7fc8277a648e8ee1 to your computer and use it in GitHub Desktop.
Nomi.ai keyboard shortcuts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Nomi.ai | |
// @namespace https://beta.nomi.ai/ | |
// @version 0.2.0 | |
// @description Nomi.ai keyboard shortcuts | |
// @author Silver Zachara <[email protected]> | |
// @match https://beta.nomi.ai/nomis/* | |
// @icon https://www.google.com/s2/favicons?domain=nomi.ai&sz=64 | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
let memoryBulbBtn = null | |
let textarea = null | |
ready(() => { | |
// Initialize moveing the memory bulb into the main textarea | |
initializeMoveMemoryBulb() | |
}) | |
document.addEventListener('keydown', ev => { | |
// Add padding below the main input box | |
if (ev.code === 'F9') | |
modifyTextareaStyles() | |
}) | |
/* Add padding below the main input box. */ | |
function modifyTextareaStyles() { | |
const chatColumn = document.querySelector('.ChatSidebarLayout_root__jmxzE > :last-child') | |
if (chatColumn === null) | |
return | |
if (chatColumn.style.paddingBottom.length === 0) { | |
chatColumn.style.paddingBottom = '9rem' | |
moveMemoryBulb(true) | |
return | |
} | |
chatColumn.style.paddingBottom = '' | |
moveMemoryBulb() | |
} | |
/** | |
* Move the memory bulb into the main textarea. | |
* | |
* @param {boolean=} alternativePosition Alternative bottom position. | |
*/ | |
function moveMemoryBulb(alternativePosition = false) { | |
if (memoryBulbBtn === null || textarea === null) | |
return | |
const textareaLeft = elementPosition(textarea).left | |
const bulbStyles = { | |
'position': 'absolute', | |
'left' : textareaLeft + 8 + 'px', | |
'bottom' : alternativePosition ? '172px' : '28px', | |
'z-index' : '1000', | |
} | |
const textareaStyles = { | |
'padding-left': '2rem', | |
} | |
memoryBulbBtn.style.cssText += cssObjectToString(bulbStyles) | |
textarea.style.cssText += cssObjectToString(textareaStyles) | |
} | |
/* Update the bulb left position based on the window width. */ | |
function updateBulbPosition() { | |
// Nothing to do, media query kicks in and layout changes | |
if (window.innerWidth < 768) | |
return | |
if (memoryBulbBtn === null || textarea === null) | |
return | |
memoryBulbBtn.style.left = elementPosition(textarea).left + 8 + 'px' | |
} | |
/* Initialize moveing the memory bulb into the main textarea. */ | |
async function initializeMoveMemoryBulb() { | |
waitForElement('textarea[aria-label="Chat Input"], nav[role="navigation"] button[title="Memory Indicator"]', 2) | |
.then((elements) => { | |
// Initialize the global element variables | |
initializeElements(elements) | |
moveMemoryBulb() | |
// Update the bulb left position based on the window width | |
// window.addEventListener('resize', () => updateBulbPosition()) | |
window.onresize = updateBulbPosition | |
}) | |
} | |
/** | |
* Initialize the global element variables. | |
* | |
* @param {Nodelist} elements | |
*/ | |
function initializeElements(elements) { | |
// Nothing to do | |
if (elements.length !== 2) | |
return | |
memoryBulbBtn = elements[0] | |
textarea = elements[1] | |
} | |
/* Wait until the given selector is available in the DOM. */ | |
function waitForElement(selector, length = 1) { | |
return new Promise(resolve => { | |
const elements = document.querySelectorAll(selector) | |
if (elements.length === length) | |
return resolve(elements) | |
const observer = new MutationObserver((/*mutations*/) => { | |
const elements = document.querySelectorAll(selector) | |
// Nothing to do | |
if (elements.length !== length) | |
return | |
observer.disconnect() | |
resolve(elements) | |
}) | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}) | |
}) | |
} | |
/** Get an element position relative to the window. | |
* | |
* @param {HTMLElement} elem | |
*/ | |
function elementPosition(elem) { | |
const rect = elem.getBoundingClientRect() | |
const win = elem.ownerDocument.defaultView | |
return { | |
top: rect.top + win.pageYOffset, | |
left: rect.left + win.pageXOffset, | |
} | |
} | |
function cssObjectToString(styles) { | |
return Object.entries(styles).map(([k, v]) => `${k}:${v}`).join(';') | |
} | |
function ready(fn) { | |
if (document.readyState !== 'loading') | |
fn() | |
else | |
document.addEventListener('DOMContentLoaded', fn) | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment