|
/* 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)}})}})})();})(); |
Many thanks. I wrote the original in Typescript, so I'm going to copy your changes to the
onMounthook to the original and compile it again. The lint errors are probably happening because of the minification. I'll add a comment to disable lint checker in Tampermonkey output. Thanks again.