Skip to content

Instantly share code, notes, and snippets.

@volkanunsal
Last active October 3, 2025 11:37
Show Gist options
  • Save volkanunsal/fded9124d62422c0d2672b8a6a293c0d to your computer and use it in GitHub Desktop.
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! ✨
// @ts-nocheck
// ==UserScript==
// @name Pop Note to Full Screen
// @namespace https://github.com/volkanunsal
// @version 2025-09-20
// @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/pop-note.user.js
// @updateURL https://gist.githubusercontent.com/volkanunsal/fded9124d62422c0d2672b8a6a293c0d/raw/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 K=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"}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&&console.debug(...this.formatMessage("debug",...e))}info(...e){this.enabled&&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&&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 C(h={}){return new K(h)}function z(h){let{parentSelector:e="body",eventType:t,targetSelector:n,callback:r,useCapture:c=!1,preventDefault:a=!1,stopPropagation:i=!1,parentElement:d,namespace:w}=h,u=C({prefix:"[addEventDelegation]",namespace:w});if(u.info("Setting up event delegation",{eventType:t,targetSelector:n,parentSelector:e,useCapture:c,preventDefault:a,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 s=d;if(!s&&e){let f=document.querySelector(e);if(!f)return u.error(`Parent element not found with selector: ${e}`),null;s=f}if(!s)return u.error("No valid parent element found"),null;u.debug("Found parent element for event delegation",{parentTagName:s.tagName,parentId:s.id,parentClasses:s.className});let g=f=>{let x=f.target;if(!x||!x.closest)return;u.debug(`Event of type ${t} triggered`,{event:f,target:x});let p=x.closest(n);if(p&&s&&s.contains(p)){u.debug(`Event delegation triggered for ${t} on ${n}`,{eventType:t,targetSelector:n,matchingElement:p.tagName+(p.id?`#${p.id}`:"")+(p.className?`.${p.className.replace(/\s+/g,".")}`:""),parentSelector:e}),a&&(f.preventDefault(),u.debug("Prevented default event behavior")),i&&(f.stopPropagation(),u.debug("Stopped event propagation"));try{r({event:f,matchingElement:p})}catch(k){u.error("Error in event delegation callback:",k)}}};return s.addEventListener(t,g,c),u.info(`Event delegation set up: ${t} on ${n} within ${e||"provided parent"}`,{eventType:t,targetSelector:n,parentSelector:e,useCapture:c,preventDefault:a,stopPropagation:i}),{cleanup(){s&&(s.removeEventListener(t,g,c),u.info(`Event delegation cleaned up: ${t} on ${n}`))},getInfo(){return{eventType:t,targetSelector:n,...e&&{parentSelector:e},useCapture:c,preventDefault:a,stopPropagation:i,isActive:!!s}}}}function B(h,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);h.innerHTML=r}else{let n=t(e);h.innerHTML=n}}async function V(h){let{tagName:e,id:t,attributes:n={},parentSelector:r="body",parentElement:c,textContent:a,innerHTML:i,innerElement:d,returnElement:w=!0,checkExisting:u=!0,replaceExisting:s=!1,insertionMethod:g="append",contextElement:f,waitTimeout:x=3e4,persistent:p=!1,autoRemove:k=!0,debounceDelay:H=500,observerConfig:$={childList:!0,subtree:!0,attributes:!1,attributeOldValue:!1,characterData:!1,characterDataOldValue:!1},namespace:q,onMount:A}=h,o=C({prefix:"[createElement]",namespace:q}),l={action:"create",tagName:e,id:t,parentMethod:r?"selector":"element",parentIdentifier:r||(c?c.tagName:"unknown"),waitTimeout:x,insertionMethod:g};if(o.info(`Starting element creation: <${e}> with ID="${t}"${f?" (conditional)":""} using ${g} 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(g!=="append"&&g!=="insertBeforeElement"&&g!=="prepend")return o.error("Element creation failed: insertionMethod must be 'append', 'insertBeforeElement', or 'prepend'",l),null;function M(b,y,m){try{let S=new DOMParser().parseFromString(y,"text/html"),v=S.querySelector("parsererror");if(v)throw new Error(`HTML parsing error: ${v.textContent}`);let L=Array.from(S.body.childNodes);return L.length===0?(o.warn(`No valid elements found in HTML string: "${y.substring(0,50)}${y.length>50?"...":""}"`,m),!1):(L.forEach(P=>{let I=b.ownerDocument.importNode(P,!0);b.appendChild(I)}),o.debug(`Created DOM elements programmatically: "${y.substring(0,50)}${y.length>50?"...":""}" (${L.length} elements)`,m),!0)}catch(E){o.warn(`DOMParser failed, attempting fallback method: ${E.message}`,m);try{return B(b,y),o.debug(`Successfully created DOM elements via fallback method: "${y.substring(0,50)}${y.length>50?"...":""}"`,m),!0}catch(S){return o.error(`DOM insertion failed: ${S.message}`,{...m,originalError:E.message,fallbackError:S.message}),!1}}}function D(){try{let b=null;if(u&&(b=document.getElementById(t),b))if(l.action="exists",s)o.info(`Found existing element with ID="${t}", replacing as requested`,l),b.remove(),l.action="replace";else return o.warn(`Element with ID="${t}" already exists, skipping creation. Use replaceExisting=true to replace.`,l),w?b:null;let y=c;if(!y&&r){let v=document.querySelector(r);if(!v)return o.error(`Element creation failed: Parent element not found with selector: ${r}`,l),null;y=v,o.debug(`Found parent element with selector: ${r}`,l)}if(!y)return o.error("Element creation failed: No valid parent element found",l),null;let m=document.createElement(e);o.debug(`Created DOM element: <${e}>`,l),m.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,L])=>{L!=null&&(m.setAttribute(v,String(L)),o.debug(`Set attribute: ${v}="${L}"`,l))}),o.debug(`Applied ${E} additional attributes to element`,l)),d&&typeof d=="function")try{let v=d();v instanceof HTMLElement?(m.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),a!==void 0?(m.textContent=a,o.debug(`Set textContent: "${a.substring(0,100)}${a.length>100?"...":""}"`,l)):i!==void 0&&M(m,i,l))}catch(v){o.error("Error executing innerElement callback, falling back to other content methods:",v,l),a!==void 0?(m.textContent=a,o.debug(`Set textContent: "${a.substring(0,100)}${a.length>100?"...":""}"`,l)):i!==void 0&&M(m,i,l)}else a!==void 0?(m.textContent=a,o.debug(`Set textContent: "${a.substring(0,100)}${a.length>100?"...":""}"`,l)):i!==void 0&&M(m,i,l);if(g==="insertBeforeElement")if(y.parentElement)y.parentElement.insertBefore(m,y),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 g==="prepend"?(y.insertBefore(m,y.firstChild),o.debug("Prepended element as first child of parent",l)):(y.appendChild(m),o.debug("Appended element to end of parent",l));if(A&&typeof A=="function")try{A(m),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 ":""}${g}ed to ${l.parentMethod==="selector"?`parent selected by "${r}"`:"provided parent element"}${f?" (after content element)":""}`;return o.info(S,{...l,success:!0,hasAttributes:E>0,hasContent:!!(a||i||d),parentTagName:y.tagName,elementPath:m.tagName+"#"+m.id+(m.className?`.${m.className.replace(/\s+/g,".")}`:"")}),w?m:null}catch(b){let y=`Element creation failed with exception: ${b.message||"Unknown error"}`;return o.error(y,{...l,success:!1,error:b.message||"Unknown error",stack:b.stack||"No stack trace available"}),null}}async function N(){try{if(typeof f=="function"){let b=await f();return b instanceof HTMLElement&&document.contains(b)}else if(typeof f=="string")return document.querySelector(f)!==null}catch(b){o.warn(`Error checking content element for element ID="${t}":`,b)}return!1}function F(){if(!p&&!k)return;o.info(`Setting up ${p?"persistent":""}${p&&k?" and ":""}${k?"auto-removal":""} monitoring for element ID="${t}"`);let b=null,y=new MutationObserver(async()=>{b&&clearTimeout(b),b=setTimeout(async()=>{o.debug(`Persistent monitoring check triggered for element ID="${t}"`);let E=await N(),S=document.getElementById(t);E?p&&!S?(o.info(`Context element found but managed element missing, recreating element ID="${t}"`),D()):o.debug(`Both context and managed elements exist for ID="${t}"`):k&&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}"`)},H)}),m={childList:$.childList===!0,subtree:$.subtree===!0,attributes:$.attributes===!0,attributeOldValue:$.attributeOldValue===!0,characterData:$.characterData===!0,characterDataOldValue:$.characterDataOldValue===!0};y.observe(document.documentElement||document.body,m),o.debug(`Started persistent MutationObserver for element ID="${t}"`)}if(!f)return D();if(o.info(`Checking initial condition for element ID="${t}"`,{...l,waitCondition:typeof f=="function"?"callback":f,persistent:p}),await N()){o.info(`Initial content element already satisfied for element ID="${t}"`,l);let b=D();return(p||k)&&F(),b}return new Promise(b=>{let y=Date.now(),m=null,E=null,S=!1;function v(P){S||k&&(S=!0,m&&(clearTimeout(m),m=null),E&&(E.disconnect(),E=null,o.debug(`Disconnected MutationObserver for element ID="${t}"`,l)),b(P))}m=setTimeout(()=>{o.error(`Element creation failed: Wait condition timeout after ${x}ms for element ID="${t}"`,{...l,success:!1,timeoutReached:!0,waitCondition:typeof f=="function"?"callback":f}),v(null)},x),o.info(`Setting up MutationObserver for element ID="${t}"`,{...l,observerConfig:$,waitCondition:typeof f=="function"?"callback":f}),E=new MutationObserver(async P=>{if(!S&&(o.debug(`MutationObserver detected ${P.length} mutations for element ID="${t}"`,l),await N())){let I=Date.now()-y;o.info(`Wait condition satisfied via MutationObserver for element ID="${t}" after DOM changes`,{...l,elapsedTime:I});let G=D();(p||k)&&F(),v(G)}});let L={childList:$.childList===!0,subtree:$.subtree===!0,attributes:$.attributes===!0,attributeOldValue:$.attributeOldValue===!0,characterData:$.characterData===!0,characterDataOldValue:$.characterDataOldValue===!0};E.observe(document.documentElement||document.body,L),o.debug(`Started MutationObserver monitoring for element ID="${t}"`,{...l,target:"document.documentElement"})})}var O=class{constructor(e={}){this.defaultPollDuration=e.defaultPollDuration||e.defaultWaitDuration||400,this.logger=e.logger||C({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 a=null;if(typeof e=="function"?a=await e():typeof e=="string"&&(a=document.querySelector(e)),a&&a instanceof Element)return this.logger.debug(`Found ${r} after ${Date.now()-c}ms`),a}catch(a){this.logger.warn(`Error polling for ${r}:`,a)}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:a=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 s=r,g=null;if(this.logger.debug(`Event: ${n}, Target: ${typeof s=="function"?"callback function":s}, Poll: ${u}ms`),g=await this.pollForElement(s,u,a,`target element for ${t}`),!g)return this.logger.error(`Action failed - element not found after ${u}ms polling: ${t}`),!1;let f=this.dispatchEvent(g,n,w);if(this.logger.debug(`Event dispatched: ${n} on ${t}. Success: ${f}`),!f)return this.logger.error(`Action failed - event dispatch failed: ${t}`),!1;if(u>0){let x=d||s,p=d?"poll target element":"action target element";this.logger.debug(`Polling for ${p} availability for up to ${u}ms after ${t}`),await this.pollForElement(x,u,a,p)||this.logger.warn(`Poll timeout - ${p} 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:a}=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}),a&&a(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),a&&a(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(h={}){return new O(h)}var R=class{constructor(e={}){this.combos=new Map,this.pressedKeys=new Set,this.isListening=!1,this.storageKey=e.storageKey||"keyboard-combos",this.logger=C({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:a=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:a};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,s)=>(s.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 _(h){return new R(h)}function T(h){let{parentElementSelector:e,textContent:t,exactMatch:n=!1,caseSensitive:r=!1,parent:c=document,returnAll:a=!1}=h;if(!e||!t)return C({prefix:"[selectElementByText]",namespace:h.namespace}).warn("Both tag and textContent are required"),a?[]:null;let i=c.querySelectorAll(e),d=[],w=r?t:t.toLowerCase();for(let u of i){let s=r?u.textContent?.trim()||"":u.textContent?.trim().toLowerCase()||"";if(n?s===w:s.includes(w))if(a)d.push(u);else return u}return a?d:null}function j({selector:h,timeout:e,pollInterval:t=100}){return new Promise(n=>{let r=t,c=0,a=setInterval(()=>{let i=typeof h=="function"?h():document.querySelector(h);i?(clearInterval(a),n(i)):e&&c>=e&&(clearInterval(a),n(null)),c+=r},r)})}(function(){"use strict";let h=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: ${h};
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=C({prefix:"[PopNote]",namespace:"UserScript"}),n=!1,r=U({namespace:"PopNote",defaultPollDuration:300}),c=_({namespace:"PopNote"});t.info("Full Text Note userscript initialized");async function a(s){try{t.info("Activating full screen mode");let g=[{actionName:"Click note button to open",event:"click",target:()=>s,description:"note button",pollDuration:300}];await r.executeSequence(g);let f="note-editor",x=await j({selector:()=>document.querySelector("note-editor")??document.querySelector("report-viewer"),timeout:1e3});if(!x){t.error("Report viewer not found either, aborting");return}let p=x;p.classList.add("full-screen-note"),p.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(h)==="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(h)==="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:f,contextElement:".full-screen-note",onMount:$=>{let q=$;if(!q)return;let A=M=>q.querySelector(M);q.addEventListener("mouseenter",()=>{let M=A("#pop-note-handle"),D=A(".control-panel-content");D&&(D.style.display="block")}),q.addEventListener("mouseleave",()=>{let M=A("#pop-note-handle"),D=A(".control-panel-content");D&&(D.style.display="none")});let o=q.querySelector("#one-column"),l=q.querySelector("#two-column");o&&o.addEventListener("change",()=>{p&&(t.info("Switching to one column layout"),p.classList.remove("multi-column"))}),l&&l.addEventListener("change",()=>{p&&(t.info("Switching to two column layout"),p.classList.add("multi-column"))})}}),n=!0,t.info("Full screen mode activated successfully")}catch(g){t.error("Error activating full screen mode:",g)}}async function i(){let s=T({parentElementSelector:"mat-icon",textContent:"collapse_content"});if(s){let g=[{actionName:"Click close button",event:"click",target:()=>s,description:"close button",pollDuration:100}];await r.executeSequence(g),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(s){t.error("Error deactivating full screen mode:",s)}}function w(s){let g=s.closest("artifact-library-item")??s.closest("artifact-library-note"),f=g!==null&&(T({parentElementSelector:"mat-icon",textContent:"auto_tab_group",parent:g})||T({parentElementSelector:"mat-icon",textContent:"sticky_note_2",parent:g}));return t.debug("isTargetNoteButton check:",{element:s,el:g,el2:f}),!!f}let u=z({parentSelector:"body",eventType:"contextmenu",targetSelector:".mdc-button.artifact-button-content",callback:({event:s})=>{t.info("Right-click detected on note button"),s.target&&w(s.target)?(t.info("Valid note button detected, preventing default context menu"),s.preventDefault(),s.stopPropagation(),a(s.target)):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 s=u;s&&s.cleanup&&s.cleanup(),t.info("Userscript cleanup completed")})})();})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment