Skip to content

Instantly share code, notes, and snippets.

@volkanunsal
Last active March 7, 2026 16:22
Show Gist options
  • Select an option

  • Save volkanunsal/fded9124d62422c0d2672b8a6a293c0d to your computer and use it in GitHub Desktop.

Select an option

Save volkanunsal/fded9124d62422c0d2672b8a6a293c0d to your computer and use it in GitHub Desktop.
Pop Note is a userscript that enhances the user experience of Google NotebookLM by opening the notes in full screen mode with right-click and exit with Escape key. This allows users to focus on their notes without distractions from other UI elements.

Pop Note for NotebookLM

Pop Note is a userscript that enhances the user experience of Google NotebookLM by opening the notes in full screen mode with right-click and exit with Escape key. This allows users to focus on their notes without distractions from other UI elements.

Key Features

  • Adds a right-click context menu option to open notes in full screen mode
  • Allows exiting full screen mode by pressing the Escape key
  • Adds a control panel on upper left corner to allow user to change the column layout between 1, and 2 columns.

Getting Started

🔧 Installation

  1. Install Tampermonkey browser extension
  2. Click on the "Raw" button of the script below.
  3. Visit notebooklm.google.com to see the magic! ✨
/* eslint-disable */
// @ts-nocheck
// ==UserScript==
// @name Pop Note to Full Screen
// @namespace https://github.com/volkanunsal
// @version 2026.0307.1122
// @description Open notes in full screen mode with right-click and exit with Escape key
// @author Volkan Unsal
// @downloadURL https://gist.githubusercontent.com/volkanunsal/fded9124d62422c0d2672b8a6a293c0d/raw/3fddc9f60900d340698fe51150ba89a95312f540/pop-note.user.js
// @updateURL https://gist.githubusercontent.com/volkanunsal/fded9124d62422c0d2672b8a6a293c0d/raw/3fddc9f60900d340698fe51150ba89a95312f540/pop-note.user.js
// @match https://notebooklm.google.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
"use strict";(()=>{var O=class{constructor(e={}){this.namespace=e.namespace,this.prefix=[this.namespace,e.prefix].filter(Boolean).join(" "),this.enabled=e.enabled!==!1,this.timestamp=e.timestamp!==!1,this.timestampFormat=e.timestampFormat||"locale"}isVerbose(){try{return!!localStorage.getItem("VERBOSE_LOGGING")}catch{return!1}}getTimestamp(){let e=new Date;return this.timestampFormat==="ISO"?e.toISOString():e.toLocaleString()}formatMessage(e,...t){let n=[this.prefix];return this.timestamp&&n.push(`[${this.getTimestamp()}]`),n.push(`[${e.toUpperCase()}]`),[n.join(" "),...t]}debug(...e){!this.enabled||!this.isVerbose()||console.debug(...this.formatMessage("debug",...e))}info(...e){!this.enabled||!this.isVerbose()||console.info(...this.formatMessage("info",...e))}warn(...e){this.enabled&&console.warn(...this.formatMessage("warn",...e))}error(...e){this.enabled&&console.error(...this.formatMessage("error",...e))}log(...e){!this.enabled||!this.isVerbose()||console.log(...this.formatMessage("log",...e))}custom(e,...t){this.enabled&&console.log(...this.formatMessage(e,...t))}group(e,t){if(!this.enabled)return t();console.group(`${this.prefix} ${e}`);try{t()}finally{console.groupEnd()}}groupCollapsed(e,t){if(!this.enabled)return t();console.groupCollapsed(`${this.prefix} ${e}`);try{t()}finally{console.groupEnd()}}table(e,t){this.enabled&&(this.info("Table data:"),console.table(e,t))}time(e){this.enabled&&console.time(`${this.prefix} ${e}`)}timeEnd(e){this.enabled&&console.timeEnd(`${this.prefix} ${e}`)}timeLog(e){this.enabled&&console.timeLog(`${this.prefix} ${e}`)}enable(){this.enabled=!0}disable(){this.enabled=!1}setPrefix(e){this.prefix=e}};function k(p={}){return new O(p)}function B(p){let{parentSelector:e="body",eventType:t,targetSelector:n,callback:r,useCapture:c=!1,preventDefault:s=!1,stopPropagation:i=!1,parentElement:d,namespace:w}=p,u=k({prefix:"[addEventDelegation]",namespace:w});if(u.info("Setting up event delegation",{eventType:t,targetSelector:n,parentSelector:e,useCapture:c,preventDefault:s,stopPropagation:i}),!t||!n||!r)return u.error("eventType, targetSelector, and callback are required"),null;if(!e&&!d)return u.error("Either parentSelector or parentElement must be provided"),null;let a=d;if(!a&&e){let g=document.querySelector(e);if(!g)return u.error(`Parent element not found with selector: ${e}`),null;a=g}if(!a)return u.error("No valid parent element found"),null;u.debug("Found parent element for event delegation",{parentTagName:a.tagName,parentId:a.id,parentClasses:a.className});let m=g=>{let x=g.target;if(!x||!x.closest)return;u.debug(`Event of type ${t} triggered`,{event:g,target:x});let y=x.closest(n);if(y&&a&&a.contains(y)){u.debug(`Event delegation triggered for ${t} on ${n}`,{eventType:t,targetSelector:n,matchingElement:y.tagName+(y.id?`#${y.id}`:"")+(y.className?`.${y.className.replace(/\s+/g,".")}`:""),parentSelector:e}),s&&(g.preventDefault(),u.debug("Prevented default event behavior")),i&&(g.stopPropagation(),u.debug("Stopped event propagation"));try{r({event:g,matchingElement:y})}catch($){u.error("Error in event delegation callback:",$)}}};return a.addEventListener(t,m,c),u.info(`Event delegation set up: ${t} on ${n} within ${e||"provided parent"}`,{eventType:t,targetSelector:n,parentSelector:e,useCapture:c,preventDefault:s,stopPropagation:i}),{cleanup(){a&&(a.removeEventListener(t,m,c),u.info(`Event delegation cleaned up: ${t} on ${n}`))},getInfo(){return{eventType:t,targetSelector:n,...e&&{parentSelector:e},useCapture:c,preventDefault:s,stopPropagation:i,isActive:!!a}}}}function z(p,e){function t(n){return n.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"")}if(window.trustedTypes){let r=window.trustedTypes.createPolicy("myHTMLPolicy",{createHTML:t}).createHTML(e);p.innerHTML=r}else{let n=t(e);p.innerHTML=n}}async function V(p){let{tagName:e,id:t,attributes:n={},parentSelector:r="body",parentElement:c,textContent:s,innerHTML:i,innerElement:d,returnElement:w=!0,checkExisting:u=!0,replaceExisting:a=!1,insertionMethod:m="append",contextElement:g,waitTimeout:x=3e4,persistent:y=!1,autoRemove:$=!0,debounceDelay:F=500,observerConfig:C={childList:!0,subtree:!0,attributes:!1,attributeOldValue:!1,characterData:!1,characterDataOldValue:!1},namespace:P,onMount:D}=p,o=k({prefix:"[createElement]",namespace:P}),l={action:"create",tagName:e,id:t,parentMethod:r?"selector":"element",parentIdentifier:r||(c?c.tagName:"unknown"),waitTimeout:x,insertionMethod:m};if(o.info(`Starting element creation: <${e}> with ID="${t}"${g?" (conditional)":""} using ${m} insertion`),!e||typeof e!="string")return o.error("Element creation failed: Tag parameter is required and must be a string",l),null;if(!t||typeof t!="string")return o.error("Element creation failed: ID parameter is required and must be a non-empty string",l),null;if(!r&&!c)return o.error("Element creation failed: Either parentSelector or parentElement must be provided",l),null;if(m!=="append"&&m!=="insertBeforeElement"&&m!=="prepend")return o.error("Element creation failed: insertionMethod must be 'append', 'insertBeforeElement', or 'prepend'",l),null;function q(h,b,f){try{let S=new DOMParser().parseFromString(b,"text/html"),v=S.querySelector("parsererror");if(v)throw new Error(`HTML parsing error: ${v.textContent}`);let M=Array.from(S.body.childNodes);return M.length===0?(o.warn(`No valid elements found in HTML string: "${b.substring(0,50)}${b.length>50?"...":""}"`,f),!1):(M.forEach(A=>{let I=h.ownerDocument.importNode(A,!0);h.appendChild(I)}),o.debug(`Created DOM elements programmatically: "${b.substring(0,50)}${b.length>50?"...":""}" (${M.length} elements)`,f),!0)}catch(E){o.warn(`DOMParser failed, attempting fallback method: ${E.message}`,f);try{return z(h,b),o.debug(`Successfully created DOM elements via fallback method: "${b.substring(0,50)}${b.length>50?"...":""}"`,f),!0}catch(S){return o.error(`DOM insertion failed: ${S.message}`,{...f,originalError:E.message,fallbackError:S.message}),!1}}}function L(){try{let h=null;if(u&&(h=document.getElementById(t),h))if(l.action="exists",a)o.info(`Found existing element with ID="${t}", replacing as requested`,l),h.remove(),l.action="replace";else return o.warn(`Element with ID="${t}" already exists, skipping creation. Use replaceExisting=true to replace.`,l),w?h:null;let b=c;if(!b&&r){let v=document.querySelector(r);if(!v)return o.error(`Element creation failed: Parent element not found with selector: ${r}`,l),null;b=v,o.debug(`Found parent element with selector: ${r}`,l)}if(!b)return o.error("Element creation failed: No valid parent element found",l),null;let f=document.createElement(e);o.debug(`Created DOM element: <${e}>`,l),f.setAttribute("id",t),o.debug(`Set required ID attribute: id="${t}"`,l);let E=Object.keys(n).length;if(E>0&&(Object.entries(n).forEach(([v,M])=>{M!=null&&(f.setAttribute(v,String(M)),o.debug(`Set attribute: ${v}="${M}"`,l))}),o.debug(`Applied ${E} additional attributes to element`,l)),d&&typeof d=="function")try{let v=d();v instanceof HTMLElement?(f.appendChild(v),o.debug("Inserted child element via innerElement callback",l)):(o.warn("innerElement callback did not return a valid HTMLElement, falling back to other content methods",l),s!==void 0?(f.textContent=s,o.debug(`Set textContent: "${s.substring(0,100)}${s.length>100?"...":""}"`,l)):i!==void 0&&q(f,i,l))}catch(v){o.error("Error executing innerElement callback, falling back to other content methods:",v,l),s!==void 0?(f.textContent=s,o.debug(`Set textContent: "${s.substring(0,100)}${s.length>100?"...":""}"`,l)):i!==void 0&&q(f,i,l)}else s!==void 0?(f.textContent=s,o.debug(`Set textContent: "${s.substring(0,100)}${s.length>100?"...":""}"`,l)):i!==void 0&&q(f,i,l);if(m==="insertBeforeElement")if(b.parentElement)b.parentElement.insertBefore(f,b),o.debug("Inserted element before parent",l);else return o.error("Element insertion failed: Parent element has no parent to insert before",l),null;else m==="prepend"?(b.insertBefore(f,b.firstChild),o.debug("Prepended element as first child of parent",l)):(b.appendChild(f),o.debug("Appended element to end of parent",l));if(D&&typeof D=="function")try{D(f),o.debug(`Successfully called onMount callback for element ID="${t}"`,l)}catch(v){o.warn(`Error in onMount callback for element ID="${t}":`,v,l)}let S=`Element creation successful: <${e}> (ID="${t}") ${l.action==="replace"?"replaced and ":""}${m}ed to ${l.parentMethod==="selector"?`parent selected by "${r}"`:"provided parent element"}${g?" (after content element)":""}`;return o.info(S,{...l,success:!0,hasAttributes:E>0,hasContent:!!(s||i||d),parentTagName:b.tagName,elementPath:f.tagName+"#"+f.id+(f.className?`.${f.className.replace(/\s+/g,".")}`:"")}),w?f:null}catch(h){let b=`Element creation failed with exception: ${h.message||"Unknown error"}`;return o.error(b,{...l,success:!1,error:h.message||"Unknown error",stack:h.stack||"No stack trace available"}),null}}async function N(){try{if(typeof g=="function"){let h=await g();return h instanceof HTMLElement&&document.contains(h)}else if(typeof g=="string")return document.querySelector(g)!==null}catch(h){o.warn(`Error checking content element for element ID="${t}":`,h)}return!1}function H(){if(!y&&!$)return;o.info(`Setting up ${y?"persistent":""}${y&&$?" and ":""}${$?"auto-removal":""} monitoring for element ID="${t}"`);let h=null,b=new MutationObserver(async()=>{h&&clearTimeout(h),h=setTimeout(async()=>{o.debug(`Persistent monitoring check triggered for element ID="${t}"`);let E=await N(),S=document.getElementById(t);E?y&&!S?(o.info(`Context element found but managed element missing, recreating element ID="${t}"`),L()):o.debug(`Both context and managed elements exist for ID="${t}"`):$&&S?(o.info(`Context element no longer present, removing managed element ID="${t}"`),S.remove()):o.debug(`Context element no longer present for element ID="${t}"`)},F)}),f={childList:C.childList===!0,subtree:C.subtree===!0,attributes:C.attributes===!0,attributeOldValue:C.attributeOldValue===!0,characterData:C.characterData===!0,characterDataOldValue:C.characterDataOldValue===!0};b.observe(document.documentElement||document.body,f),o.debug(`Started persistent MutationObserver for element ID="${t}"`)}if(!g)return L();if(o.info(`Checking initial condition for element ID="${t}"`,{...l,waitCondition:typeof g=="function"?"callback":g,persistent:y}),await N()){o.info(`Initial content element already satisfied for element ID="${t}"`,l);let h=L();return(y||$)&&H(),h}return new Promise(h=>{let b=Date.now(),f=null,E=null,S=!1;function v(A){S||$&&(S=!0,f&&(clearTimeout(f),f=null),E&&(E.disconnect(),E=null,o.debug(`Disconnected MutationObserver for element ID="${t}"`,l)),h(A))}f=setTimeout(()=>{o.error(`Element creation failed: Wait condition timeout after ${x}ms for element ID="${t}"`,{...l,success:!1,timeoutReached:!0,waitCondition:typeof g=="function"?"callback":g}),v(null)},x),o.info(`Setting up MutationObserver for element ID="${t}"`,{...l,observerConfig:C,waitCondition:typeof g=="function"?"callback":g}),E=new MutationObserver(async A=>{if(!S&&(o.debug(`MutationObserver detected ${A.length} mutations for element ID="${t}"`,l),await N())){let I=Date.now()-b;o.info(`Wait condition satisfied via MutationObserver for element ID="${t}" after DOM changes`,{...l,elapsedTime:I});let j=L();(y||$)&&H(),v(j)}});let M={childList:C.childList===!0,subtree:C.subtree===!0,attributes:C.attributes===!0,attributeOldValue:C.attributeOldValue===!0,characterData:C.characterData===!0,characterDataOldValue:C.characterDataOldValue===!0};E.observe(document.documentElement||document.body,M),o.debug(`Started MutationObserver monitoring for element ID="${t}"`,{...l,target:"document.documentElement"})})}var K=class{constructor(e={}){this.defaultPollDuration=e.defaultPollDuration||e.defaultWaitDuration||400,this.logger=e.logger||k({prefix:"[EventSequencer]",namespace:e.namespace}),this.maxRetries=e.maxRetries||3,this.retryDelay=e.retryDelay||100,this.isRunning=!1,this.currentSequence=null}async wait(e){return new Promise(t=>setTimeout(t,e))}async pollForElement(e,t,n=50,r="element"){let c=Date.now();for(;Date.now()-c<t;){try{let s=null;if(typeof e=="function"?s=await e():typeof e=="string"&&(s=document.querySelector(e)),s&&s instanceof Element)return this.logger.debug(`Found ${r} after ${Date.now()-c}ms`),s}catch(s){this.logger.warn(`Error polling for ${r}:`,s)}await this.wait(n)}return this.logger.warn(`Polling timeout for ${r} after ${t}ms`),null}async findElement(e,t=this.maxRetries){for(let n=0;n<=t;n++){let r=document.querySelector(e);if(r)return this.logger.debug(`Found element: ${e}`),r;n<t&&(this.logger.debug(`Element not found: ${e}, retrying... (${n+1}/${t})`),await this.wait(this.retryDelay))}return this.logger.warn(`Element not found after ${t} retries: ${e}`),null}async findElementByCallback(e,t=this.maxRetries,n="custom callback"){for(let r=0;r<=t;r++){try{let c=await e();if(c&&c instanceof Element)return this.logger.debug(`Found element via ${n}`),c}catch(c){this.logger.warn(`Error in element callback (${n}):`,c)}r<t&&(this.logger.debug(`Element not found via ${n}, retrying... (${r+1}/${t})`),await this.wait(this.retryDelay))}return this.logger.warn(`Element not found via ${n} after ${t} retries`),null}dispatchEvent(e,t,n={}){try{let r;switch(this.logger.debug("Window object:",window),t){case"click":case"mousedown":case"mouseup":case"mouseover":case"mouseout":case"mouseenter":case"mouseleave":case"mousemove":r=new MouseEvent(t,{bubbles:!0,cancelable:!0,...n});break;case"keydown":case"keyup":case"keypress":r=new KeyboardEvent(t,{bubbles:!0,cancelable:!0,...n});break;case"focus":case"blur":case"change":case"input":r=new Event(t,{bubbles:!0,cancelable:!0,...n});break;default:r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n,...n})}let c=e.dispatchEvent(r);return this.logger.debug(`Dispatched ${t} event on element:`,e),c}catch(r){return this.logger.error(`Failed to dispatch ${t} event:`,r),!1}}async executeAction(e){let{actionName:t,event:n,target:r,pollDuration:c,pollInterval:s=50,waitBeforePolling:i,pollTarget:d,eventOptions:w={}}=e,u=c??this.defaultPollDuration;this.logger.info(`Executing action: ${t}`),i&&i>0&&(this.logger.debug(`Waiting ${i}ms before starting action: ${t}`),await this.wait(i));let a=r,m=null;if(this.logger.debug(`Event: ${n}, Target: ${typeof a=="function"?"callback function":a}, Poll: ${u}ms`),m=await this.pollForElement(a,u,s,`target element for ${t}`),!m)return this.logger.error(`Action failed - element not found after ${u}ms polling: ${t}`),!1;let g=this.dispatchEvent(m,n,w);if(this.logger.debug(`Event dispatched: ${n} on ${t}. Success: ${g}`),!g)return this.logger.error(`Action failed - event dispatch failed: ${t}`),!1;if(u>0){let x=d||a,y=d?"poll target element":"action target element";this.logger.debug(`Polling for ${y} availability for up to ${u}ms after ${t}`),await this.pollForElement(x,u,s,y)||this.logger.warn(`Poll timeout - ${y} not found within ${u}ms after ${t}`)}return this.logger.info(`Action completed successfully: ${t}`),!0}async executeSequence(e,t={}){let{stopOnError:n=!0,onProgress:r,onComplete:c,onError:s}=t;if(this.isRunning){let d=new Error("EventSequencer is already running a sequence");return this.logger.error("Sequence execution blocked - already running",{currentSequenceLength:this.currentSequence?.length||0,requestedActions:e.length}),s&&s(d),{success:!1,totalActions:0,completedActions:0,failedActions:0,errors:[{error:d.message}]}}this.isRunning=!0,this.currentSequence=e;let i={success:!0,totalActions:e.length,completedActions:0,failedActions:0,errors:[]};this.logger.info(`Starting event sequence with ${e.length} actions`);try{for(let d=0;d<e.length;d++){let w=e[d];this.logger.debug(`Processing action ${d+1}/${e.length}`);let u=await this.executeAction(w);if(u)i.completedActions++;else if(i.failedActions++,i.errors.push({actionIndex:d,actionName:w.actionName,error:"Action execution failed"}),n){i.success=!1,this.logger.error(`Sequence stopped due to failed action: ${w.actionName}`);break}r&&r({currentAction:d+1,totalActions:e.length,actionName:w.actionName,actionSuccess:u,completedActions:i.completedActions,failedActions:i.failedActions})}i.failedActions>0&&n&&(i.success=!1),this.logger.info(`Event sequence completed. Success: ${i.success}, Completed: ${i.completedActions}/${i.totalActions}`),c&&c(i)}catch(d){i.success=!1,i.errors.push({error:d.message||"Unknown error",stack:d.stack||"No stack trace available"}),this.logger.error("Event sequence failed with error:",d),s&&s(d)}finally{this.isRunning=!1,this.currentSequence=null}return i}stop(){this.isRunning&&(this.logger.info("Stopping event sequence"),this.isRunning=!1,this.currentSequence=null)}getStatus(){return{isRunning:this.isRunning,hasCurrentSequence:this.currentSequence!==null,currentSequenceLength:this.currentSequence?this.currentSequence.length:0}}setDefaultPollDuration(e){this.defaultPollDuration=e,this.logger.debug(`Updated default poll duration to ${e}ms`)}setDefaultWaitDuration(e){this.defaultPollDuration=e,this.logger.debug(`Updated default poll duration to ${e}ms (via legacy method)`)}setLogger(e){this.logger=e}};function U(p={}){return new K(p)}var R=class{constructor(e={}){this.combos=new Map,this.pressedKeys=new Set,this.isListening=!1,this.storageKey=e.storageKey||"keyboard-combos",this.logger=k({prefix:"[KeyboardComboManager]",namespace:e.namespace})}normalizeKey(e){let n={Control:"Ctrl",Meta:"Cmd",Command:"Cmd"," ":"Space"}[e]||e;return n.length===1&&/[a-zA-Z]/.test(n)?n.toUpperCase():n}parseCombo(e){return e.split("+").map(t=>this.normalizeKey(t.trim()))}saveShortcutToStorage(e,t){try{let n=this.getStoredShortcuts(),r={combo:e,timestamp:Date.now()};t.description!==void 0&&(r.description=t.description),typeof t.context=="string"&&(r.context=t.context),t.priority!==void 0&&(r.priority=t.priority),n[e]||(n[e]=[]),n[e].push(r),localStorage.setItem(this.storageKey,JSON.stringify(n)),this.logger.debug(`Saved shortcut to localStorage: ${e}`)}catch(n){this.logger.warn("Failed to save shortcut to localStorage:",n)}}removeShortcutFromStorage(e){try{let t=this.getStoredShortcuts();delete t[e],localStorage.setItem(this.storageKey,JSON.stringify(t)),this.logger.debug(`Removed shortcut from localStorage: ${e}`)}catch(t){this.logger.warn("Failed to remove shortcut from localStorage:",t)}}getStoredShortcuts(){try{let e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):{}}catch(e){return this.logger.warn("Failed to read shortcuts from localStorage:",e),{}}}clearStoredShortcuts(){try{localStorage.removeItem(this.storageKey),this.logger.info("Cleared all shortcuts from localStorage")}catch(e){this.logger.warn("Failed to clear shortcuts from localStorage:",e)}}getStoredShortcutsList(){let e=this.getStoredShortcuts(),t=[];for(let[,n]of Object.entries(e))Array.isArray(n)&&t.push(...n);return t.sort((n,r)=>(r.timestamp||0)-(n.timestamp||0))}isContextActive(e){if(!e)return!0;try{if(typeof e=="function"){let t=e();return t instanceof HTMLElement&&document.contains(t)}else if(typeof e=="string")return document.querySelector(e)!==null}catch(t){this.logger.warn("Error checking context:",t)}return!1}findMatchingContext(e){for(let t of e)if(this.isContextActive(t.context))return this.logger.debug(`Context matched for: ${t.description||"unnamed"}`),t;return null}getCurrentCombo(){return Array.from(this.pressedKeys).sort().join("+")}register(e){let{combo:t,callback:n,description:r,context:c,priority:s=0}=e;if(!t||!n){this.logger.error("Both combo and callback are required for registration");return}let i=this.parseCombo(t).sort().join("+"),d={callback:n,priority:s};if(r!==void 0&&(d.description=r),c!==void 0&&(d.context=c),this.combos.has(i)){let w=this.combos.get(i);w&&(w.push(d),w.sort((u,a)=>(a.priority||0)-(u.priority||0)))}else this.combos.set(i,[d]);this.saveShortcutToStorage(i,d),this.logger.info(`Registered combo: ${i}${r?` (${r})`:""}${c?` [context: ${typeof c=="function"?"callback":c}]`:""}`),this.isListening||(this.logger.info("Starting keyboard listener"),this.startListening())}unregister(e){let t=this.parseCombo(e).sort().join("+");this.combos.delete(t),this.removeShortcutFromStorage(t),this.logger.info(`Unregistered combo: ${t}`)}startListening(){this.isListening||(this.logger.info("Starting to listen for keyboard combos"),this.isListening=!0,document.addEventListener("keydown",e=>{this.logger.debug("Keydown event:",e.key),this.handleKeyDown(e)}),document.addEventListener("keyup",this.handleKeyUp.bind(this)),window.addEventListener("blur",()=>{this.pressedKeys.clear()}))}stopListening(){this.isListening&&(this.logger.info("Stopping keyboard listener"),this.isListening=!1,document.removeEventListener("keydown",this.handleKeyDown.bind(this),!0),document.removeEventListener("keyup",this.handleKeyUp.bind(this),!0))}handleKeyDown(e){let t=this.normalizeKey(e.key);this.logger.debug("Key down:",t,"Pressed keys:",Array.from(this.pressedKeys)),e.ctrlKey&&this.pressedKeys.add("Ctrl"),e.shiftKey&&this.pressedKeys.add("Shift"),e.altKey&&this.pressedKeys.add("Alt"),e.metaKey&&this.pressedKeys.add("Cmd"),["Ctrl","Shift","Alt","Cmd"].includes(t)||this.pressedKeys.add(t);let n=this.getCurrentCombo(),r=this.combos.get(n);if(this.logger.debug("Current combo:",n,"Registered combos:",Array.from(this.combos.keys())),r){let c=this.findMatchingContext(r);c?(this.logger.debug(`Executing combo: ${n} with context: ${c.description||"unnamed"}`),e.preventDefault(),e.stopPropagation(),this.pressedKeys.clear(),this.logger.debug("Cleared pressed keys after combo execution"),c.callback(e)):this.logger.debug(`No matching context found for combo: ${n}.`,"Registered handlers:",{comboHandlers:r})}else this.logger.debug(`No handlers registered for combo: ${n}`)}handleKeyUp(e){let t=this.normalizeKey(e.key);this.logger.debug("Key up:",t,"Pressed keys before:",Array.from(this.pressedKeys)),e.ctrlKey||this.pressedKeys.delete("Ctrl"),e.shiftKey||this.pressedKeys.delete("Shift"),e.altKey||this.pressedKeys.delete("Alt"),e.metaKey||this.pressedKeys.delete("Cmd"),this.pressedKeys.delete(t)}getRegisteredCombos(){return Array.from(this.combos.keys())}clearAll(){this.combos.clear(),this.pressedKeys.clear(),this.clearStoredShortcuts(),this.logger.info("Cleared all registered combos and pressed keys")}};function _(p){return new R(p)}function T(p){let{parentElementSelector:e,textContent:t,exactMatch:n=!1,caseSensitive:r=!1,parent:c=document,returnAll:s=!1}=p;if(!e||!t)return k({prefix:"[selectElementByText]",namespace:p.namespace}).warn("Both tag and textContent are required"),s?[]:null;let i=c.querySelectorAll(e),d=[],w=r?t:t.toLowerCase();for(let u of i){let a=r?u.textContent?.trim()||"":u.textContent?.trim().toLowerCase()||"";if(n?a===w:a.includes(w))if(s)d.push(u);else return u}return s?d:null}function G({selector:p,timeout:e,pollInterval:t=100}){return new Promise(n=>{let r=t,c=0,s=setInterval(()=>{let i=typeof p=="function"?p():document.querySelector(p);i?(clearInterval(s),n(i)):e&&c>=e&&(clearInterval(s),n(null)),c+=r},r)})}(function(){"use strict";let p=GM_getValue("popNoteColumnCount","2");GM_addStyle(`
:root {
--pop-note-primary: #74a9ff;
}
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) {
.full-screen-note .ql-editor, .full-screen-note labs-tailwind-doc-viewer {
background: #fff !important;
color: #111 !important;
}
}
@media (prefers-color-scheme: dark) {
.full-screen-note .ql-editor,
.full-screen-note labs-tailwind-doc-viewer {
background: #1a1a1a !important;
color: #e8e8e8 !important;
}
}
.full-screen-note .ql-container {
overflow: auto !important;
border: none !important;
height: 100%;
width: 100%;
}
.full-screen-note labs-tailwind-doc-viewer {
padding: 2rem !important;
}
.full-screen-note.multi-column .ql-editor, .full-screen-note.multi-column labs-tailwind-doc-viewer {
column-count: ${p};
column-width: 8rem;
widows: 3;
orphans: 3;
gap: 2rem;
column-rule: 2px dashed #666666;
height: 99vh;
width: 99vw;
column-fill: balance;
}
.full-screen-note strong {
margin-right: 0.1rem;
}
.full-screen-note .ql-container, .full-screen-note labs-tailwind-doc-viewer {
position: fixed !important;
z-index: 99 !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
font-size: 1.3rem !important;
}
.full-screen-note .markdown-editor-wrap {
border: none !important;
}
.full-screen-note p, .full-screen-note div:not(.heading1) span, .full-screen-note b, .full-screen-note i {
font-size: 1.2rem !important;
line-height: 1.8rem !important;
}
.full-screen-note pre {
overflow: auto !important;
}
div.heading1 span {
line-height: 2.8rem !important;
font-size: 2.5rem !important;
font-weight: bold !important;
}
.full-screen-note .citation-marker {
height: unset !important;
}
.full-screen-note .citation-marker span {
font-size: 0.825rem !important;
}
.full-screen-note p {
margin: 0.5rem 0 1rem !important;
}
.full-screen-note h1 {
line-height: 3.2rem;
margin-bottom: 2.5rem !important;
}
.full-screen-note h2, .full-screen-note h3, .full-screen-note h4 {
font-weight: bold !important;
margin: 1rem 0 !important;
line-height: 2.2rem;
}
.full-screen-note h2 {
margin-top: 2.5rem !important;
}
.full-screen-note h2:first-child {
margin-top: 0 !important;
}
.full-screen-note .ql-editor > ul, .full-screen-note .ql-editor > ol {
padding-left: 0 !important;
}
.full-screen-note li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 2.5em !important;
}
.full-screen-note li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 3.5em !important;
}
.full-screen-note li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 4.5em !important;
}
.full-screen-note li {
margin-bottom: 0.5rem !important;
}
.show-none {
display: none !important;
}
.full-screen-note p:last-child {
margin-bottom: 0 !important;
}
/* Pop Note Control Panel Styles */
#pop-note-control-panel {
position: absolute !important;
top: 0 !important;
left: 0 !important;
z-index: 100 !important;
width: 0 !important;
height: 0 !important;
}
#pop-note-handle {
border-style: solid !important;
border-width: 0 15px 15px 0 !important;
border-color: transparent var(--pop-note-primary) transparent transparent !important;
transform: rotate(-90deg) !important;
cursor: pointer !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
}
.control-panel-content {
position: absolute !important;
top: 0px !important;
left: 0px !important;
padding: 0.5rem !important;
font-size: 0.9rem !important;
color: #888 !important;
transition: opacity 0.3s ease !important;
background: white !important;
display: none;
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 7px !important;
width: max-content !important;
border-bottom-right-radius: 3px !important;
}
.control-panel-content input[type="radio"] {
margin-right: 0.25rem !important;
display: none !important;
}
.control-panel-content label {
cursor: pointer !important;
}
.control-panel-content label[for="one-column"] {
margin-right: 0.5rem !important;
}
.control-panel-content input[type="radio"]:checked + label {
color: var(--pop-note-primary);
font-weight: bold;
}
`);let t=k({prefix:"[PopNote]",namespace:"UserScript"}),n=!1,r=U({namespace:"PopNote",defaultPollDuration:300}),c=_({namespace:"PopNote"});t.info("Full Text Note userscript initialized");async function s(a){try{t.info("Activating full screen mode");let m=[{actionName:"Click note button to open",event:"click",target:()=>a,description:"note button",pollDuration:300}];await r.executeSequence(m);let g="note-editor",x=await G({selector:()=>document.querySelector("note-editor")??document.querySelector("report-viewer"),timeout:1e3});if(!x){t.error("Note editor and report viewer not found, aborting");return}let y=x.tagName.toLowerCase();t.debug("Found editor element",{editorType:y});let $=x;$.classList.add("full-screen-note"),$.classList.add("multi-column"),document.querySelector(".boqOnegoogleliteOgbOneGoogleBar")?.classList.add("show-none"),V({id:"pop-note-control-panel",tagName:"div",innerHTML:`<div>
<div id='pop-note-handle'></div>
<div class="control-panel-content">
<input type="radio" id="one-column" name="column" value="1" ${String(p)==="1"?"checked":""}>
<label for="one-column" title="One Column Layout"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16px" height="16px"><path d="M3 5h18v2H3V5zm0 6h18v2H3v-2zm0 6h18v2H3v-2z"/></svg></label>
<input type="radio" id="two-column" name="column" value="2" ${String(p)==="2"?"checked":""}>
<label for="two-column" title="Two Column Layout"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16px" height="16px"><path d="M3 5h18v2H3V5zm0 6h8v2H3v-2zm0 6h8v2H3v-2zm10-12h8v2h-8V5zm0 6h8v2h-8v-2zm0 6h8v2h-8v-2z"/></svg></label>
</div>
</div>`,parentSelector:g,contextElement:".full-screen-note",onMount:P=>{let D=P;if(!D)return;t.debug("Control panel mounted");let o=L=>D.querySelector(L);D.addEventListener("mouseenter",()=>{let L=o("#pop-note-handle"),N=o(".control-panel-content");N&&(N.style.display="block")}),D.addEventListener("mouseleave",()=>{let L=o("#pop-note-handle"),N=o(".control-panel-content");N&&(N.style.display="none")});let l=D.querySelector("#one-column"),q=D.querySelector("#two-column");l&&l.addEventListener("change",()=>{$&&(t.info("Switching to one column layout"),$.classList.remove("multi-column"))}),q&&q.addEventListener("change",()=>{$&&(t.info("Switching to two column layout"),$.classList.add("multi-column"))})}}),n=!0,t.info("Full screen mode activated successfully")}catch(m){t.error("Error activating full screen mode:",m)}}async function i(){t.debug("Attempting to close note editor");let a=T({parentElementSelector:"mat-icon",textContent:"collapse_content"});if(a){let m=[{actionName:"Click close button",event:"click",target:()=>a,description:"close button",pollDuration:100}];await r.executeSequence(m),document.querySelector(".boqOnegoogleliteOgbOneGoogleBar")?.classList.remove("show-none"),t.info("Note editor closed")}else t.warn("Close button not found")}async function d(){try{t.info("Deactivating full screen mode"),n=!1,t.info("Full screen mode deactivated, original styles restored"),await i()}catch(a){t.error("Error deactivating full screen mode:",a)}}function w(a){let m=a.closest("artifact-library-item")??a.closest("artifact-library-note");if(!m)return t.debug("isTargetNoteButton: no artifact container found",{element:a.tagName+(a.className?`.${a.className}`:"")}),!1;let g=T({parentElementSelector:"mat-icon",textContent:"auto_tab_group",parent:m})||T({parentElementSelector:"mat-icon",textContent:"sticky_note_2",parent:m});return t.debug("isTargetNoteButton check:",{container:m.tagName,hasNoteIcon:!!g}),!!g}let u=B({parentSelector:"body",eventType:"contextmenu",targetSelector:".artifact-button-content",callback:({event:a})=>{let m=a.target;t.info("Right-click detected on note button",{target:m?.tagName+(m?.className?`.${m.className}`:"")}),a.target&&w(m)?(t.info("Valid note button detected, preventing default context menu"),a.preventDefault(),a.stopPropagation(),s(m)):t.debug("Element does not match note button criteria")},preventDefault:!1,stopPropagation:!1,namespace:"PopNote"});if(!u){t.error("Failed to set up event delegation for note buttons");return}t.info("Event delegation set up successfully for note buttons"),c.register({combo:"Escape",callback:()=>{n?(t.info("Escape key pressed, deactivating full screen mode"),d()):(t.info("Currently not in full screen mode. Closing note editor if open."),i())},description:"Exit full screen note mode",context:()=>document.querySelector("note-editor")??document.querySelector("artifact-viewer"),priority:100}),t.info("Keyboard shortcut registered for Escape key"),window.addEventListener("beforeunload",()=>{let a=u;a&&a.cleanup&&a.cleanup(),t.info("Userscript cleanup completed")})})();})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment