您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
add extra settings to sourcegraph
当前为
// ==UserScript== // @name missing settings for sourcegraph(MSFS) // @namespace https://greasyfork.org // @version 1.0.0 // @description add extra settings to sourcegraph // @author pot-code // @match https://sourcegraph.com/* // @grant none // ==/UserScript== // TODO: // - 切换主题时,需要重新绑定 // - 切换文件时,重新绑定 ;(function() { 'use strict' const PREFIX = 'MSFS' const noop = function() {} const { logger, LogLevels } = (function() { let defaultLevel = 3 // warn level let logMethods = ['trace', 'debug', 'info', 'warn', 'error'] function Logger() { const self = this function replaceLogMethod(level) { logMethods.forEach((methodName, index) => { this[methodName] = index < level ? noop : console[methodName === 'debug' ? 'log' : methodName].bind( console, `[${PREFIX}][${methodName.toUpperCase()}]:` ) }) } self.setLevel = level => { replaceLogMethod.call(self, level) } replaceLogMethod.call(self, defaultLevel) } return { logger: new Logger(), LogLevels: logMethods.reduce((acc, cur, index) => { acc[cur] = index return acc }, {}) } })() function offsetToBody(element) { let offsetTop = 0, offsetLeft = 0 while (element !== document.body) { offsetLeft += element.offsetLeft offsetTop += element.offsetTop element = element.offsetParent } return { offsetLeft, offsetTop } } /** * hepler function to iterate map object * @param {Map|Object} mapLikeObject * @param {(key, value)=>any} fn function to call on map entry */ function mapIterator(mapLikeObject, fn) { if (Object.prototype.toString.call(mapLikeObject) === '[object Map]') { mapLikeObject.forEach((value, key) => fn(key, value)) } else { Object.keys(mapLikeObject).forEach(key => fn(key, mapLikeObject[key])) } } function mapAddEntry(mapLikeObject, key, value) { if (Object.prototype.toString.call(mapLikeObject) === '[object Map]') { mapLikeObject.set(key, value) } else { mapLikeObject[key] = value } } function createFontController({ codeArea, dropdown }) { const DEFAULT_SIZE = '12' const fontSizeController = document.createElement('div'), label = document.createElement('span'), input = document.createElement('input'), indicator = document.createElement('span') label.innerText = 'font-size' input.type = 'range' input.min = DEFAULT_SIZE input.max = '17' input.step = '1' input.value = DEFAULT_SIZE input.style.margin = '0 0.5rem' indicator.style.display = 'inline-block' // prevent layout from changing while the character is not monospaced indicator.style.width = '17px' indicator.innerText = DEFAULT_SIZE fontSizeController.appendChild(label) fontSizeController.appendChild(input) fontSizeController.appendChild(indicator) function sizeChangeHandlerFactory(_codeArea) { return function() { const newSize = input.value indicator.innerText = newSize _codeArea.style.fontSize = `${newSize}px` // logger.debug(`new font size is ${newSize}`); } } let sizeChangeHandler = sizeChangeHandlerFactory(codeArea) input.addEventListener('input', sizeChangeHandler) dropdown.dom.appendChild(fontSizeController) return { root: fontSizeController, reload: function({ codeArea }) { input.removeEventListener('input', sizeChangeHandler) sizeChangeHandler = sizeChangeHandlerFactory(codeArea) input.addEventListener('input', sizeChangeHandler) sizeChangeHandler() } } } function initNavItem(navRoot) { function create_nav_button(actionName) { return ` <div class="popover-button popover-button__btn popover-button__anchor" style="margin: .425rem .175rem;padding: 0 .425rem;color: #566e9f;"> <span class="popover-button__container"> ${actionName} <svg class="mdi-icon icon-inline popover-button__icon" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"> <path d="M7,10L12,15L17,10H7Z"></path> </svg> </span> </div>` } const navItem = document.createElement('li') navItem.classList.add('nav-item') navItem.innerHTML = create_nav_button('settings') navRoot.appendChild(navItem) return { dom: navItem } } function initDropdownMenu(navItem) { let openState = false // dropdown state const container = document.createElement('div') const { offsetTop } = offsetToBody(navItem.dom) // create container container.className = `popover popover-button2__popover rounded ${PREFIX}-settings` container.style.transform = `translateY(${offsetTop + navItem.dom.offsetHeight}px)` const toggle = event => { if (event) { event.stopPropagation() } container.style.display = ((openState = !openState), openState) ? 'block' : 'none' } // document.body click fix document.body.addEventListener('click', event => { if (event.target === container) { toggle() } else { if (openState === true) { toggle() } } }) navItem.dom.addEventListener('click', toggle) container.addEventListener('click', event => { event.stopPropagation() }) logger.debug('dropdown container initialized') // insert DOM document.body.appendChild(container) return { dom: container, toggle } } function getNavRoot() { return document.querySelector( '#root > div.layout > div.layout__app-router-container.layout__app-router-container--full-width > div > nav > ul:nth-child(7)' ) } function getCodeArea() { return document.querySelector('div.blob.blob-page__blob') } function patch_style(definition) { const style = document.createElement('style') style.innerHTML = definition document.head.appendChild(style) logger.debug('======================patched styles======================') logger.debug(definition) logger.debug('==========================================================') } const plugin = (function createPlugin() { function Plugin() { const self = this const defaultStyles = ` .${PREFIX}-settings{ padding: 0.3em 0.5em; position: absolute; display: none; top: 0; right: 10px; } .${PREFIX}-settings__item{ display: flex; align-items: center; margin-bottom: 6px; } .${PREFIX}-settings__item:last-child{ margin-bottom: 0; } div.blob.blob-page__blob{ font-size: 12px; } code.blob__code.e2e-blob{ font-size: inherit; line-height: 1.33; } ` const definitions = new Map() const components = new Map() const reloadListeners = [] const styles = [defaultStyles] let stylePatched = false let initialized = false let navRoot = getNavRoot() let codeArea = getCodeArea() let navItem = null let dropdown = null function reloadComponent(newDep, name, component) { component.reload && component.reload(newDep) logger.debug(`component ${name} is reloaded`) } function initComponent(dep, name, definition, context) { let component = definition(dep, context) styles.push(component.style) components.set(name, component) component.root && component.root.classList.add(`${PREFIX}-settings__item`) logger.debug(`component ${name} is initialized`) } function ensureCriticalElement(reload = false) { const MAX_RETRY_COUNT = 10 const RETRY_DELAY = 1000 let tried = 0 let lastNavRoot = navRoot let lastCodeArea = codeArea return new Promise((res, rej) => { function queryElement() { ;[navRoot, codeArea] = [reload ? navRoot : getNavRoot(), getCodeArea()] if (navRoot && codeArea) { if (reload && lastCodeArea === codeArea) { if (++tried > MAX_RETRY_COUNT) { logger.warn('max retry count reached, plugin failed to reload or reload is not needed') return } // supress log // logger.debug('failed to detect critical element changes, retrying...') setTimeout(queryElement, RETRY_DELAY, reload) return } tried = 0 ;[lastNavRoot, lastCodeArea] = [navRoot, codeArea] res({ navRoot, codeArea }) logger.debug('critical path created, initializing plugin...') } else { logger.debug('failed to detect critical element, retrying...') if (++tried > MAX_RETRY_COUNT) { rej('max retry count reached, plugin failed to initialize') return } setTimeout(queryElement, RETRY_DELAY) } } queryElement() }) } /** * @param name {string} component name * @param definition {(dep, plugin:Plugin)=>{uninstall?:Function, reload?:Function, style?: string, root?:HTMLElement}} component object */ this.registerComponent = (name, definition) => { if (name in definition || name in components) { logger.warn(`component ${name} is already registered, registration cancelled`) return } mapAddEntry(definitions, name, definition) } this.reload = () => { ensureCriticalElement(true) .then(({ codeArea }) => { return { navItem, codeArea, dropdown } }) .then(newDep => { mapIterator(components, function(name, component) { reloadComponent(newDep, name, component) }) logger.debug('plugin reloaded') }) .catch(err => { logger.error(err) }) } const initComponents = dep => { mapIterator(definitions, function(name, definition) { initComponent(dep, name, definition, self) }) } this.init = () => { return ensureCriticalElement() .then(({ navRoot, codeArea }) => { navItem = initNavItem(navRoot) dropdown = initDropdownMenu(navItem) return { navItem, codeArea, dropdown } }) .then(dep => { initComponents(dep) patch_style(styles.join('\n')) stylePatched = true initialized = true }) } /** * @param target {HTMLElement} event source element which may change the dependencies * @param event {string} event name * @param shouldReload {(event:Event)=>boolean} should trigger the reload * @param capture should event be capture type */ this.addReloadListener = (target, event, shouldReload, capture = false) => { shouldReload = shouldReload.bind(target) let handler = _event => { if (shouldReload(_event)) { this.reload() logger.debug(`plugin will reload due to the ${event} event on ${target}`) } } target.addEventListener(event, handler, capture) reloadListeners.push({ target, event, handler, capture, dispose: function() { target.removeEventListener(event, handler, capture) } }) } } return new Plugin() })() // logger.setLevel(LogLevels.debug) plugin.registerComponent('font-controller', createFontController) //===============================Danger Zone=============================== plugin .init() .then(() => { plugin.addReloadListener( document.querySelector('#explorer>div.tree>table>tbody>tr>td>div>table'), 'click', function(event) { let target = event.target return target.tagName === 'A' && target.className === 'tree__row-contents' } ) }) .catch(err => { logger.error(err) }) //========================================================================= })()