|
/* eslint-disable */ |
|
// @ts-nocheck |
|
// ==UserScript== |
|
// @name Copy Note to Markdown |
|
// @namespace https://github.com/volkanunsal |
|
// @version 2025-11-07 |
|
// @description Copies the current note content as Markdown to clipboard |
|
// @author Volkan Unsal |
|
// @downloadURL https://gist.githubusercontent.com/volkanunsal/5d67c79c3c117bc81d1301a3a5e3d18e/raw/copy-note-to-markdown.user.js |
|
// @updateURL https://gist.githubusercontent.com/volkanunsal/5d67c79c3c117bc81d1301a3a5e3d18e/raw/copy-note-to-markdown.user.js |
|
// @match https://notebooklm.google.com/* |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=google.com |
|
// @grant GM_addStyle |
|
|
|
// ==/UserScript== |
|
|
|
"use strict";(()=>{var P=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 s=[this.prefix];return this.timestamp&&s.push(`[${this.getTimestamp()}]`),s.push(`[${e.toUpperCase()}]`),[s.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 I(n={}){return new P(n)}function q(n,e){function t(s){return s.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"")}if(window.trustedTypes){let r=window.trustedTypes.createPolicy("myHTMLPolicy",{createHTML:t}).createHTML(e);n.innerHTML=r}else{let s=t(e);n.innerHTML=s}}async function B(n){let{tagName:e,id:t,attributes:s={},parentSelector:r="body",parentElement:f,textContent:l,innerHTML:p,innerElement:m,returnElement:T=!0,checkExisting:N=!0,replaceExisting:D=!1,insertionMethod:y="append",contextElement:$,waitTimeout:k=3e4,persistent:M=!1,autoRemove:E=!0,debounceDelay:V=500,observerConfig:w={childList:!0,subtree:!0,attributes:!1,attributeOldValue:!1,characterData:!1,characterDataOldValue:!1},namespace:R,onMount:O}=n,o=I({prefix:"[createElement]",namespace:R}),a={action:"create",tagName:e,id:t,parentMethod:r?"selector":"element",parentIdentifier:r||(f?f.tagName:"unknown"),waitTimeout:k,insertionMethod:y};if(o.info(`Starting element creation: <${e}> with ID="${t}"${$?" (conditional)":""} using ${y} insertion`),!e||typeof e!="string")return o.error("Element creation failed: Tag parameter is required and must be a string",a),null;if(!t||typeof t!="string")return o.error("Element creation failed: ID parameter is required and must be a non-empty string",a),null;if(!r&&!f)return o.error("Element creation failed: Either parentSelector or parentElement must be provided",a),null;if(y!=="append"&&y!=="insertBeforeElement"&&y!=="prepend")return o.error("Element creation failed: insertionMethod must be 'append', 'insertBeforeElement', or 'prepend'",a),null;function S(c,u,i){try{let b=new DOMParser().parseFromString(u,"text/html"),g=b.querySelector("parsererror");if(g)throw new Error(`HTML parsing error: ${g.textContent}`);let C=Array.from(b.body.childNodes);return C.length===0?(o.warn(`No valid elements found in HTML string: "${u.substring(0,50)}${u.length>50?"...":""}"`,i),!1):(C.forEach(x=>{let H=c.ownerDocument.importNode(x,!0);c.appendChild(H)}),o.debug(`Created DOM elements programmatically: "${u.substring(0,50)}${u.length>50?"...":""}" (${C.length} elements)`,i),!0)}catch(h){o.warn(`DOMParser failed, attempting fallback method: ${h.message}`,i);try{return q(c,u),o.debug(`Successfully created DOM elements via fallback method: "${u.substring(0,50)}${u.length>50?"...":""}"`,i),!0}catch(b){return o.error(`DOM insertion failed: ${b.message}`,{...i,originalError:h.message,fallbackError:b.message}),!1}}}function L(){try{let c=null;if(N&&(c=document.getElementById(t),c))if(a.action="exists",D)o.info(`Found existing element with ID="${t}", replacing as requested`,a),c.remove(),a.action="replace";else return o.warn(`Element with ID="${t}" already exists, skipping creation. Use replaceExisting=true to replace.`,a),T?c:null;let u=f;if(!u&&r){let g=document.querySelector(r);if(!g)return o.error(`Element creation failed: Parent element not found with selector: ${r}`,a),null;u=g,o.debug(`Found parent element with selector: ${r}`,a)}if(!u)return o.error("Element creation failed: No valid parent element found",a),null;let i=document.createElement(e);o.debug(`Created DOM element: <${e}>`,a),i.setAttribute("id",t),o.debug(`Set required ID attribute: id="${t}"`,a);let h=Object.keys(s).length;if(h>0&&(Object.entries(s).forEach(([g,C])=>{C!=null&&(i.setAttribute(g,String(C)),o.debug(`Set attribute: ${g}="${C}"`,a))}),o.debug(`Applied ${h} additional attributes to element`,a)),m&&typeof m=="function")try{let g=m();g instanceof HTMLElement?(i.appendChild(g),o.debug("Inserted child element via innerElement callback",a)):(o.warn("innerElement callback did not return a valid HTMLElement, falling back to other content methods",a),l!==void 0?(i.textContent=l,o.debug(`Set textContent: "${l.substring(0,100)}${l.length>100?"...":""}"`,a)):p!==void 0&&S(i,p,a))}catch(g){o.error("Error executing innerElement callback, falling back to other content methods:",g,a),l!==void 0?(i.textContent=l,o.debug(`Set textContent: "${l.substring(0,100)}${l.length>100?"...":""}"`,a)):p!==void 0&&S(i,p,a)}else l!==void 0?(i.textContent=l,o.debug(`Set textContent: "${l.substring(0,100)}${l.length>100?"...":""}"`,a)):p!==void 0&&S(i,p,a);if(y==="insertBeforeElement")if(u.parentElement)u.parentElement.insertBefore(i,u),o.debug("Inserted element before parent",a);else return o.error("Element insertion failed: Parent element has no parent to insert before",a),null;else y==="prepend"?(u.insertBefore(i,u.firstChild),o.debug("Prepended element as first child of parent",a)):(u.appendChild(i),o.debug("Appended element to end of parent",a));if(O&&typeof O=="function")try{O(i),o.debug(`Successfully called onMount callback for element ID="${t}"`,a)}catch(g){o.warn(`Error in onMount callback for element ID="${t}":`,g,a)}let b=`Element creation successful: <${e}> (ID="${t}") ${a.action==="replace"?"replaced and ":""}${y}ed to ${a.parentMethod==="selector"?`parent selected by "${r}"`:"provided parent element"}${$?" (after content element)":""}`;return o.info(b,{...a,success:!0,hasAttributes:h>0,hasContent:!!(l||p||m),parentTagName:u.tagName,elementPath:i.tagName+"#"+i.id+(i.className?`.${i.className.replace(/\s+/g,".")}`:"")}),T?i:null}catch(c){let u=`Element creation failed with exception: ${c.message||"Unknown error"}`;return o.error(u,{...a,success:!1,error:c.message||"Unknown error",stack:c.stack||"No stack trace available"}),null}}async function A(){try{if(typeof $=="function"){let c=await $();return c instanceof HTMLElement&&document.contains(c)}else if(typeof $=="string")return document.querySelector($)!==null}catch(c){o.warn(`Error checking content element for element ID="${t}":`,c)}return!1}function _(){if(!M&&!E)return;o.info(`Setting up ${M?"persistent":""}${M&&E?" and ":""}${E?"auto-removal":""} monitoring for element ID="${t}"`);let c=null,u=new MutationObserver(async()=>{c&&clearTimeout(c),c=setTimeout(async()=>{o.debug(`Persistent monitoring check triggered for element ID="${t}"`);let h=await A(),b=document.getElementById(t);h?M&&!b?(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}"`):E&&b?(o.info(`Context element no longer present, removing managed element ID="${t}"`),b.remove()):o.debug(`Context element no longer present for element ID="${t}"`)},V)}),i={childList:w.childList===!0,subtree:w.subtree===!0,attributes:w.attributes===!0,attributeOldValue:w.attributeOldValue===!0,characterData:w.characterData===!0,characterDataOldValue:w.characterDataOldValue===!0};u.observe(document.documentElement||document.body,i),o.debug(`Started persistent MutationObserver for element ID="${t}"`)}if(!$)return L();if(o.info(`Checking initial condition for element ID="${t}"`,{...a,waitCondition:typeof $=="function"?"callback":$,persistent:M}),await A()){o.info(`Initial content element already satisfied for element ID="${t}"`,a);let c=L();return(M||E)&&_(),c}return new Promise(c=>{let u=Date.now(),i=null,h=null,b=!1;function g(x){b||E&&(b=!0,i&&(clearTimeout(i),i=null),h&&(h.disconnect(),h=null,o.debug(`Disconnected MutationObserver for element ID="${t}"`,a)),c(x))}i=setTimeout(()=>{o.error(`Element creation failed: Wait condition timeout after ${k}ms for element ID="${t}"`,{...a,success:!1,timeoutReached:!0,waitCondition:typeof $=="function"?"callback":$}),g(null)},k),o.info(`Setting up MutationObserver for element ID="${t}"`,{...a,observerConfig:w,waitCondition:typeof $=="function"?"callback":$}),h=new MutationObserver(async x=>{if(!b&&(o.debug(`MutationObserver detected ${x.length} mutations for element ID="${t}"`,a),await A())){let H=Date.now()-u;o.info(`Wait condition satisfied via MutationObserver for element ID="${t}" after DOM changes`,{...a,elapsedTime:H});let W=L();(M||E)&&_(),g(W)}});let C={childList:w.childList===!0,subtree:w.subtree===!0,attributes:w.attributes===!0,attributeOldValue:w.attributeOldValue===!0,characterData:w.characterData===!0,characterDataOldValue:w.characterDataOldValue===!0};h.observe(document.documentElement||document.body,C),o.debug(`Started MutationObserver monitoring for element ID="${t}"`,{...a,target:"document.documentElement"})})}function j(n){if(!n||typeof n!="string")return"";let e=n.trim().replace(/\s+/g," ").replace(/<(\w+)[^>]*>\s*<\/\1>/g,"").replace(/(<\/(?:h[1-6]|p|div|ul|ol|li|blockquote)>)\s*(<(?:h[1-6]|p|div|ul|ol|li|blockquote))/g,`$1 |
|
$2`),t=document.createElement("div");return q(t,e),U(t).trim().replace(/\n{3,}/g,` |
|
|
|
`).replace(/^\n+|\n+$/g,"").replace(/\*\*\s+\*\*/g,"").replace(/\*\s+\*/g,"").replace(/`\s*`/g,"").replace(/\[\s*\]\(\s*\)/g,"").replace(/[ \t]+$/gm,"").trim()}function U(n,e={listDepth:0,inList:!1}){if(!n)return"";if(n.nodeType===Node.TEXT_NODE){let t=n.textContent||"";return z(t)}if(n.nodeType===Node.ELEMENT_NODE){let t=n.tagName.toLowerCase(),r=Array.from(n.childNodes);switch(t){case"h1":return`# ${v(n)} |
|
|
|
`;case"h2":return`## ${v(n)} |
|
|
|
`;case"h3":return`### ${v(n)} |
|
|
|
`;case"h4":return`#### ${v(n)} |
|
|
|
`;case"h5":return`##### ${v(n)} |
|
|
|
`;case"h6":return`###### ${v(n)} |
|
|
|
`;case"p":let f=d(r,e);return f?`${f} |
|
|
|
`:"";case"strong":case"b":return`**${d(r,e)}**`;case"em":case"i":return`*${d(r,e)}*`;case"code":return`\`${n.textContent||""}\``;case"pre":return`\`\`\` |
|
${n.textContent||""} |
|
\`\`\` |
|
|
|
`;case"blockquote":return d(r,e).split(` |
|
`).map(E=>`> ${E}`).join(` |
|
`)+` |
|
|
|
`;case"ul":return G(r,e);case"ol":return X(r,e);case"li":return F(r,e);case"a":let T=n.getAttribute("href"),N=d(r,e);return T?`[${N}](${T})`:N;case"img":let D=n.getAttribute("src"),y=n.getAttribute("alt")||"";return D?``:"";case"br":return` |
|
`;case"hr":return` |
|
--- |
|
|
|
`;case"table":return K(n,e);case"div":case"span":case"section":case"article":if(n.className&&n.className.includes("ql-indent")){let E=J(n.className);return d(r,{...e,listDepth:E})}return d(r,e);case"kbd":return`\`${v(n)}\``;case"del":case"s":return`~~${d(r,e)}~~`;case"sup":return`^${d(r,e)}^`;case"sub":return`~${d(r,e)}~`;case"mark":return`**${d(r,e)}**`;case"u":return d(r,e);case"q":return`"${d(r,e)}"`;case"abbr":case"acronym":let k=n.getAttribute("title"),M=d(r,e);return k?`${M} (${k})`:M;case"time":return d(r,e);case"small":return d(r,e);case"cite":return`*${d(r,e)}*`;case"address":return d(r,e)+` |
|
|
|
`;case"details":return d(r,e)+` |
|
|
|
`;case"summary":return`**${d(r,e)}** |
|
|
|
`;case"figure":return d(r,e)+` |
|
|
|
`;case"figcaption":return`*${d(r,e)}* |
|
|
|
`;default:return d(r,e)}}return""}function d(n,e){return n.map(t=>U(t,e)).join("")}function v(n){return(n.textContent||"").replace(/\s+/g," ").trim()}function z(n,e=!1){if(!n)return"";let t=n.replace(/\s+/g," ");return!e&&t&&(t=t.replace(/([\\`])/g,"\\$1")),t}function G(n,e){let t={...e,inList:!0,listDepth:e.listDepth+1},s=n.filter(r=>r.nodeType===Node.ELEMENT_NODE&&r.tagName.toLowerCase()==="li").map(r=>{let f=0;if(r.className){let D=r.className.match(/ql-indent-(\d+)/);D&&(f=parseInt(D[1],10))}let l=Math.max(0,e.listDepth+f),p=" ".repeat(l),T=Array.from(r.childNodes),N=F(T,t);return`${p}- ${N}`}).filter(r=>r.trim());return s.length>0?s.join(` |
|
`)+` |
|
|
|
`:""}function X(n,e){let t={...e,inList:!0,listDepth:e.listDepth+1},s=n.filter(r=>r.nodeType===Node.ELEMENT_NODE&&r.tagName.toLowerCase()==="li").map((r,f)=>{let l=0;if(r.className){let y=r.className.match(/ql-indent-(\d+)/);y&&(l=parseInt(y[1],10))}let p=Math.max(0,e.listDepth+l),m=" ".repeat(p),N=Array.from(r.childNodes),D=F(N,t);return`${m}${f+1}. ${D}`}).filter(r=>r.trim());return s.length>0?s.join(` |
|
`)+` |
|
|
|
`:""}function F(n,e){return d(n,e).replace(/^\s+|\s+$/g,"").replace(/\n\n+/g,` |
|
`)}function J(n){let e=n.match(/ql-indent-(\d+)/);return e?parseInt(e[1],10):0}function K(n,e){let t=Array.from(n.querySelectorAll("tr"));if(t.length===0)return"";let s=t.map(r=>"| "+Array.from(r.querySelectorAll("td, th")).map(l=>{let m=Array.from(l.childNodes);return d(m,e).trim()}).join(" | ")+" |");if(t[0]&&t[0].querySelector("th")){let r=t[0].querySelectorAll("th").length,f="| "+Array(r).fill("---").join(" | ")+" |";s.splice(1,0,f)}return s.join(` |
|
`)+` |
|
|
|
`}(function(){"use strict";GM_addStyle(` |
|
button#copy-note-to-markdown-btn { |
|
background-color: #fff !important; |
|
margin-right: 4px !important; |
|
} |
|
button#copy-note-to-markdown-btn:hover { |
|
background-color: #f0f0f0 !important; |
|
} |
|
button#copy-note-to-markdown-btn:active { |
|
background-color: #e0e0e0 !important; |
|
} |
|
.note-header__notices { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
`);let n=I({prefix:"UserScript",namespace:"[CopyNoteToMarkdown]"});B({tagName:"button",namespace:"[CopyNoteToMarkdown]",id:"copy-note-to-markdown-btn",insertionMethod:"append",autoRemove:!1,attributes:{title:"Copy Note as Markdown",style:"all:unset;cursor:pointer;display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:4px;padding:2px;"},parentSelector:".note-header__notices.note-header__notices-3panel.ng-star-inserted",contextElement:"note-editor",innerHTML:`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-copy" style="width:20px;height:20px;"> |
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> |
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> |
|
</svg>`,onMount:e=>{e.addEventListener("click",async()=>{try{let t=document.querySelector(".editor.ql-container");if(!t){n.error("Editor not found");return}let s=t.innerHTML,r=j(s);await navigator.clipboard.writeText(r),n.log("Note content copied as Markdown to clipboard")}catch(t){n.error("Failed to copy note content:",t)}try{n.debug("Searching for editor element...");let t=null;if(t=document.querySelector(".editor.ql-container"),t)n.info("Found editor with original selector '.editor.ql-container'");else{if(n.error("Original selector '.editor.ql-container' not found"),t=document.querySelector("labs-tailwind-doc-viewer"),t)n.info("Found editor with selector 'labs-tailwind-doc-viewer'");else{let f=["labs-tailwind-doc-viewer","note-editor labs-tailwind-doc-viewer",".ql-editor",".editor","[contenteditable='true']",".note-content",".note-editor",".rich-text-editor",".prosemirror-editor","form labs-tailwind-doc-viewer","note-editor form labs-tailwind-doc-viewer"];for(let l of f){let p=document.querySelector(l);if(p){n.info(`Found editor with alternative selector: ${l}`),t=p;break}}}if(!t){let f=document.querySelectorAll('[class*="editor"]');n.debug(`Found ${f.length} elements with 'editor' in class:`,Array.from(f).map(m=>({tagName:m.tagName,className:m.className,id:m.id})));let l=document.querySelectorAll('[class*="ql-container"]');n.debug(`Found ${l.length} elements with 'ql-container' in class:`,Array.from(l).map(m=>({tagName:m.tagName,className:m.className,id:m.id})));let p=document.querySelectorAll("labs-tailwind-doc-viewer");n.debug(`Found ${p.length} labs-tailwind-doc-viewer elements:`,Array.from(p).map(m=>({tagName:m.tagName,className:m.className,id:m.id,innerHTML:m.innerHTML.substring(0,100)+"..."})))}if(!t){n.error("Editor not found with any selector");return}}let s=t.innerHTML;t.tagName.toLowerCase()==="labs-tailwind-doc-viewer"&&(s=(t.querySelector(".ql-editor")||t.querySelector('[contenteditable="true"]')||t.querySelector(".editor")||t).innerHTML);let r=j(s);await navigator.clipboard.writeText(r),n.log("Note content copied as Markdown to clipboard")}catch(t){n.error("Failed to copy note content:",t)}})}})})();})(); |
I think there may have some changes in the NotebookLM page that caused the script to not work anymore, it also had some lint errors.
I have fixed it, keep the credits: https://gist.github.com/heltonteixeira/116237abb54e2b55d519056a31d5d6b4