diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e289bbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +*.crx +*.pem +*.zip +*_original* \ No newline at end of file diff --git a/LICENSE b/LICENSE index 32a2045..092e6f1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 gitd export - Git Download Manager +Copyright (c) 2022 Gitdownloadmanager.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index cc8fe63..4dc8c44 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,78 @@ -# gitd-extension -Browser extension download Github, Gitlab, Bitbucket repository on browser +# Gitd Download Manager Browser Extension +It is a browser extension that allows you to download only the files/folders you want without having to download all of the public repository. Github.com, Bitbucket.org, Gitlab.com provides all of the public repos in git services to download selected files and folders as a zip files with a single click, without the need for any API key or token. + +The "Gitd Start" button is ready for use on every screen you see. + +> Note: Gitd Download Manager browser extension creates download lists using Gitdownloadmanager.com api service. + +![screenshot](screenshots/gitd-manager-github-download.jpeg) + +## Features +- Support only Github.com, Bitbucket.org, Gitlab.com public repositories page +- Not neeeded ApiKey/ApiToken +- Support single or multiple files download +- Download selected contents as a zip file with one click +- Support all branches +- Maximum Selection Limit: 5 +- Maximum Download Files: 5000 + +## Next Features +- No limitations for selection and downloading files count +- Support branch names with includes slash +- Support Bitbucket.org commitId url (Maybe) + +## Compatibility +- Chrome (Manifestv3) +- Firefox (Manifestv2) +- Microsoft Edge (Manifestv3) + +## Installation +### Chrome Store/Firefox Add-Ons + +Go to [Gitd Download Manager](https://chrome.google.com/webstore/detail/gitd-download-manager/cbnplpkljokdodpligcaolkmodfondhl) Chrome Store Page + +Go to [Gitd Download Manager](https://addons.mozilla.org/en-US/firefox/addon/gitd-download-manager/) Firefox Add-Ons Page + +Go to [Gitd Download Manager](https://microsoftedge.microsoft.com/addons/detail/-/-) Microsoft Edge Add-Ons Page + +### Local Development + +- Run `./build..sh ` (./build.dev.sh 1.0.0) command after go to "build" folder. + +1. Open Chrome and go to: chrome://extensions/ (same as Firefox) +2. Enable: "Developer mode" +3. Click: "Load unpacked extension" +4. Select: "extension" directory +5. Ready to use +6. Go to github.com, gitlab.com or bitbucket.org website + +### Usage +- In the right corner of the browser, the "Gitd Start" button notifies you that the plugin is active. +- The plugin is automatically installed on Github.com and adds checkboxes, but on other websites you have to press the "Gitd Start" button. + +### Find Open Source Projects: Public Repository +[Github.com](https://github.com/search/advanced) advanced search page. +[Gitlab.com](https://gitlab.com/explore/projects) advanced search page. +[Bitbucket.org](https://bitbucket.org/repo/all) simple search page. + +## Licence +See LICENSE for more details. + +# Thanks +[Alpinejs](https://alpinejs.dev) for DOM manipulation + +[fflate](https://github.com/101arrowz/fflate) for generate zip packages + +[Bootstrap Icons](https://icons.getbootstrap.com) for svg icons + +[Photopea](https://www.photopea.com) for create logo, icon and favicon + +[Liozon/Edge add-on badge.md](https://gist.github.com/Liozon/cf898c47628bfecd9896f79e6c9a8db8) for Microsoft Edge Add-On Badge + +## Source +[Firefox Manifest Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json) * perfect docs + +[Google Chrome Manifest Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/) * not recomended **sorry** + +[Microsoft Edge Manifest Docs](https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/manifest-format) * firefox alternate + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/build.dev.sh b/build.dev.sh new file mode 100755 index 0000000..3254f1c --- /dev/null +++ b/build.dev.sh @@ -0,0 +1,5 @@ +#!/bin/bash +ARGS=("$@") +BUILD_NUMBER_NEW="${ARGS[0]}" + +./sh/build.sh "dev" $BUILD_NUMBER_NEW "https://localhost:3002" "https://localhost" \ No newline at end of file diff --git a/build.prod.sh b/build.prod.sh new file mode 100755 index 0000000..4baea25 --- /dev/null +++ b/build.prod.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +ARGS=("$@") +BUILD_NUMBER_NEW="${ARGS[0]}" + +./sh/build.sh "prod" $BUILD_NUMBER_NEW "https://api.gitdownloadmanager.com" "https://*.gitdownloadmanager.com" \ No newline at end of file diff --git a/extension/background.js b/extension/background.js new file mode 100644 index 0000000..0b838f4 --- /dev/null +++ b/extension/background.js @@ -0,0 +1,89 @@ +"use strict"; + +// Manifest json file to object data +let manifestData = chrome.runtime.getManifest() + +// console.log("manifestData", manifestData); +// Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version. +chrome.runtime.onInstalled.addListener( + function() { + console.info('%c' + manifestData.name + ' Extension: %cWelcome to my world!', 'color: orange;', 'color: default;') + } +) + +// Use APIs that support event filters to restrict listeners to the cases the extension cares about. +// If an extension is listening for the tabs.onUpdated event, try using the webNavigation.onCompleted event with filters instead, as the tabs API does not support filters. +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/events/UrlFilter +let urlFilters = { + url: [ + { + hostEquals:'github.com', + schemes:["https"] + }, + { + hostEquals:'gitlab.com', + schemes:["https"] + }, + { + hostEquals:'bitbucket.org', + schemes:["https"] + } + ] +} + +/*chrome.webNavigation.onDOMContentLoaded.addListener(function (details) { + // send message + chrome.tabs.sendMessage(details.tabId, { + action: 'IM_LOADING' + }) +}, urlFilters)*/ + +chrome.webNavigation.onCompleted.addListener(function (details) { + chrome.tabs.sendMessage(details.tabId, { + action: 'IM_READY' + }) +}, urlFilters) + +chrome.webNavigation.onHistoryStateUpdated.addListener(function (details) { + chrome.tabs.sendMessage(details.tabId, { + action: 'IM_CHANGED' + }) +}, urlFilters) + +// gitdmanager api request listener +chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { + // console.log("bg", request, sender, sendResponse) + + if (request.name === "gitd-api") { + // console.log(_findUrlFromManifest(request.url)) + fetch(_findUrlFromManifest(request.url), { + method: "POST", + body: JSON.stringify(request.body), + headers: { + "content-type": "application/json", + }, + }) + .then(resp => resp.json()) + .then(response => sendResponse(response)) + .catch(e => { + //console.warn("fetch Error", e); + + sendResponse({status: false, message: "internal server error. something wrong!"}) + }) + } + + return true // last error fixed +}) + +function _findUrlFromManifest(request_url) { + let version = manifestData.manifest_version + + let url = "" + if (version === 2) { + url = (manifestData.permissions[0]).replace("/*", request_url) + } else if (version === 3) { + url = (manifestData.host_permissions[0]).replace("/*", request_url) + } + + return url +} diff --git a/extension/contentScript.js b/extension/contentScript.js new file mode 100644 index 0000000..0d89363 --- /dev/null +++ b/extension/contentScript.js @@ -0,0 +1,168 @@ +"use strict"; + +// declare debug mode var +let gitdDebugMode = false + +// listen runtime message from bg +chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + switch (request.action) { + case "IM_CHANGED": + // Dom Content Loading but not finish + if (isDebugActive()) console.log("content-script", "im changed") + + // inject templates + injectGitdTemplates() + + break; + /*case "IM_LOADING": + // Dom Content Loading but not finish + if (isDebugActive()) console.log("content-script", "im loading") + + break;*/ + case "IM_READY": + // Everything Loading finished. Ready to use. + if (isDebugActive()) console.log("content-script", "im ready") + + // inject templates + injectGitdTemplates() + + // inject gitdmanager + injectGitdScripts("lib/gitdmanager.js") + /*let gitdmanagerjs = chrome.runtime.getURL("lib/gitdmanager.js") + let s2 = document.createElement('script') + s2.src = gitdmanagerjs; + s2.onload = function() { + s2.parentNode.removeChild(s2); + //this.remove(); + }; + (document.body || document.documentElement).appendChild(s2) + if (isDebugActive()) console.log(s2);*/ + + // inject alpine + injectGitdScripts("lib/alpine-scp.min.js") + /*let alpinejs = chrome.runtime.getURL("lib/alpine-scp.min.js") + let s = document.createElement('script') + s.setAttribute("defer", "defer") + s.src = alpinejs; + s.onload = function() { + s.parentNode.removeChild(s); + //this.remove(); + }; + (document.head || document.documentElement).appendChild(s) + if (isDebugActive()) console.log(s);*/ + + // inject fflate + injectGitdScripts("lib/fflate.min.js") + /*let afflatejs = chrome.runtime.getURL("lib/fflate.min.js") + let s1 = document.createElement('script') + s1.src = afflatejs; + s1.onload = function() { + s1.parentNode.removeChild(s1); + //this.remove(); + }; + (document.head || document.documentElement).appendChild(s1) + if (isDebugActive()) console.log(s1);*/ + + break; + default: + break; + } +}); + +// listen browser submit event +// Request: gitdmanager (browser) -> contentScript -> background +// Response: background -> contentScript -> gitdmanager (browser) +window.addEventListener("submit-action", function(evt) { + if (isDebugActive()) console.log("content-script","submit-action", evt.detail) + chrome.runtime.sendMessage(JSON.parse(evt.detail), function(response) { + if (isDebugActive()) console.log("bg-response", response); + + window.dispatchEvent(new CustomEvent( + 'submit-action-response', + { + bubbles: true, + detail: JSON.stringify(response) + } + )) + }); +}, false); + +// via: chrome dev tools: getEventListeners(window) -> return all events +// only for github "turbo:load" event listen +window.addEventListener("turbo:load", function(evt) { + if (isDebugActive()) console.log("content-script", "turbo:load", evt) + + setTimeout(function() { + + // checkbox add + let navItem = document.querySelectorAll("div.js-navigation-item > div:first-child > svg") + if (navItem.length > 0) { + + // inject checkbox + for (const key in navItem) { + if (Object.hasOwnProperty.call(navItem, key)) { + const element = navItem[key]; + + // type: none: 0 - file: 1 - folder: 2 + let itemType = element.getAttribute("aria-label") + if (itemType === "Directory") { + itemType = 2 + } else if (itemType === "File") { + itemType = 1 + } + + let pathElement = element.parentElement.nextElementSibling.querySelector("div > span > a") + if (!!pathElement) { + element.parentElement.insertAdjacentHTML("beforebegin", "
") + } + } + } + } + + }, 1500) + +}, false); + +/*window.addEventListener("turbo:visit", function(evt) { + if (isDebugActive()) console.log("content-script", "turbo:visit", evt) +}, false); + +window.addEventListener("pageshow", function(evt) { + if (isDebugActive()) console.log("content-script", "pageshow", evt) +}, false); + +window.addEventListener("turbo:frame-render", function(evt) { + if (isDebugActive()) console.log("content-script", "turbo:frame-render", evt) +}, false);*/ + +// debug mode listener +window.addEventListener("debug-mode-changed", function(evt) { + if (isDebugActive()) console.log("content-script", "debug-mode-changed", evt) + + gitdDebugMode = evt.detail + +}, false); + +// check debug mode +function isDebugActive() { + return gitdDebugMode +} + +// inject templates +function injectGitdTemplates() { + if (!document.body.hasAttribute("x-data")) { + document.body.setAttribute("x-data", "gitdManager") + document.body.insertAdjacentHTML("beforeend", gitdInitTemplate) + } +} + +// inject scripts +function injectGitdScripts(scrPath) { + let s = document.createElement('script') + s.src = chrome.runtime.getURL(scrPath); + s.onload = function() { + s.parentNode.removeChild(s); + }; + (document.body || document.documentElement).appendChild(s) + if (isDebugActive()) console.log(s); +} \ No newline at end of file diff --git a/extension/gitdStyles.css b/extension/gitdStyles.css new file mode 100644 index 0000000..2e8cf00 --- /dev/null +++ b/extension/gitdStyles.css @@ -0,0 +1,197 @@ +/*Gitd Download Manager Styles*/ +#gitd-init{ + width: 50%; + flex-wrap: wrap; + flex-direction: column-reverse; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 0.6em 1.8em; + z-index: 9999; + display: flex; + margin:0 auto; + border-radius: 0.2rem; +} + +.gitd-shortcut-button { + flex-wrap: wrap; + justify-content: center; + position: fixed; + bottom: 20px; + right: 20px; + z-index: 9999; + display: flex; +} + +.gitd-btn, #gitd-init button { + display: inline-block; + font-weight: 600; + color: #525f7f; + text-align: center; + vertical-align: middle; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.625rem 1.25rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: all 0.15s ease; + position: relative; + will-change: transform; + letter-spacing: 0.025em; + font-size: 0.875rem; +} + +.gitd-btn:hover, #gitd-init button:hover { + transform: translateY(-1px); +} + +.gitd-btn:focus, #gitd-init button:focus { + outline: 0; + box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); +} + +.gitd-btn-sm, #gitd-init button.gitd-btn-sm { + padding: 0.25rem 0.5rem; + text-transform: uppercase; + font-size: 0.75rem; + line-height: 1.5; + border-radius: 0.25rem; +} + +.gitd-btn-warning, #gitd-init button.gitd-btn-warning { + color: #fff; + background-color: #fb6340; + border-color: #fb6340; + box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); +} + +.gitd-btn-warning:hover, #gitd-init button.gitd-btn-warning:hover { + color: #fff; + background-color: #fa441b; + border-color: #fa3a0e; +} + + +.gitd-btn-warning:focus, #gitd-init button.gitd-btn-warning:focus { + box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 0 rgba(252, 122, 93, 0.5); +} + +.gitd-btn svg, #gitd-init button.gitd-btn svg { + overflow: hidden; + vertical-align: middle; +} + +.gitd-btn svg:not(:last-child), +.gitd-btn i:not(:last-child), +#gitd-init button.gitd-btn svg:not(:last-child), +#gitd-init button.gitd-btn i:not(:last-child) { + margin-right: 0.5rem; +} + +/*AlertBox*/ +.gitd-alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.gitd-alert p { + margin: 0; +} + +.gitd-alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.gitd-alert-light { + color: #818182; + background-color: #fefefe; + border-color: #adb5bd; +} + +.gitd-alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.gitd-alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} + +.gitd-alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.gitd-alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; +} + +.gitd-alert-dismissible { + padding-right: 4rem; +} + +.gitd-alert-dismissible .close, #gitd-init button.close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 0.75rem 1.25rem; + color: inherit; +} + +/*Progress*/ +.gitd-progress-percentage { + text-align: right; +} + +.gitd-progress { + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; + box-shadow: inset 0 0.1rem 0.1rem rgba(0, 0, 0, 0.1); +} + +.gitd-progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #5e72e4; + transition: width 0.6s ease; +} + +@media (prefers-reduced-motion: reduce) { + .gitd-progress-bar { + transition: none; + } +} + +.gitd-bg-success { + background-color: #2dce89 !important; +} + +.gitd-current-filename { + float:left !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/extension/icons/128.png b/extension/icons/128.png new file mode 100644 index 0000000..778f637 Binary files /dev/null and b/extension/icons/128.png differ diff --git a/extension/icons/16.png b/extension/icons/16.png new file mode 100644 index 0000000..a1c378b Binary files /dev/null and b/extension/icons/16.png differ diff --git a/extension/icons/32.png b/extension/icons/32.png new file mode 100644 index 0000000..330bf99 Binary files /dev/null and b/extension/icons/32.png differ diff --git a/extension/lib/alpine-scp.js b/extension/lib/alpine-scp.js new file mode 100644 index 0000000..4fdd058 --- /dev/null +++ b/extension/lib/alpine-scp.js @@ -0,0 +1,2974 @@ +(() => { + // packages/alpinejs/src/scheduler.js + var flushPending = false; + var flushing = false; + var queue = []; + function scheduler(callback) { + queueJob(callback); + } + function queueJob(job) { + if (!queue.includes(job)) + queue.push(job); + queueFlush(); + } + function dequeueJob(job) { + let index = queue.indexOf(job); + if (index !== -1) + queue.splice(index, 1); + } + function queueFlush() { + if (!flushing && !flushPending) { + flushPending = true; + queueMicrotask(flushJobs); + } + } + function flushJobs() { + flushPending = false; + flushing = true; + for (let i = 0; i < queue.length; i++) { + queue[i](); + } + queue.length = 0; + flushing = false; + } + + // packages/alpinejs/src/reactivity.js + var reactive; + var effect; + var release; + var raw; + var shouldSchedule = true; + function disableEffectScheduling(callback) { + shouldSchedule = false; + callback(); + shouldSchedule = true; + } + function setReactivityEngine(engine) { + reactive = engine.reactive; + release = engine.release; + effect = (callback) => engine.effect(callback, {scheduler: (task) => { + if (shouldSchedule) { + scheduler(task); + } else { + task(); + } + }}); + raw = engine.raw; + } + function overrideEffect(override) { + effect = override; + } + function elementBoundEffect(el) { + let cleanup2 = () => { + }; + let wrappedEffect = (callback) => { + let effectReference = effect(callback); + if (!el._x_effects) { + el._x_effects = new Set(); + el._x_runEffects = () => { + el._x_effects.forEach((i) => i()); + }; + } + el._x_effects.add(effectReference); + cleanup2 = () => { + if (effectReference === void 0) + return; + el._x_effects.delete(effectReference); + release(effectReference); + }; + return effectReference; + }; + return [wrappedEffect, () => { + cleanup2(); + }]; + } + + // packages/alpinejs/src/mutation.js + var onAttributeAddeds = []; + var onElRemoveds = []; + var onElAddeds = []; + function onElAdded(callback) { + onElAddeds.push(callback); + } + function onElRemoved(el, callback) { + if (typeof callback === "function") { + if (!el._x_cleanups) + el._x_cleanups = []; + el._x_cleanups.push(callback); + } else { + callback = el; + onElRemoveds.push(callback); + } + } + function onAttributesAdded(callback) { + onAttributeAddeds.push(callback); + } + function onAttributeRemoved(el, name, callback) { + if (!el._x_attributeCleanups) + el._x_attributeCleanups = {}; + if (!el._x_attributeCleanups[name]) + el._x_attributeCleanups[name] = []; + el._x_attributeCleanups[name].push(callback); + } + function cleanupAttributes(el, names) { + if (!el._x_attributeCleanups) + return; + Object.entries(el._x_attributeCleanups).forEach(([name, value]) => { + if (names === void 0 || names.includes(name)) { + value.forEach((i) => i()); + delete el._x_attributeCleanups[name]; + } + }); + } + var observer = new MutationObserver(onMutate); + var currentlyObserving = false; + function startObservingMutations() { + observer.observe(document, {subtree: true, childList: true, attributes: true, attributeOldValue: true}); + currentlyObserving = true; + } + function stopObservingMutations() { + flushObserver(); + observer.disconnect(); + currentlyObserving = false; + } + var recordQueue = []; + var willProcessRecordQueue = false; + function flushObserver() { + recordQueue = recordQueue.concat(observer.takeRecords()); + if (recordQueue.length && !willProcessRecordQueue) { + willProcessRecordQueue = true; + queueMicrotask(() => { + processRecordQueue(); + willProcessRecordQueue = false; + }); + } + } + function processRecordQueue() { + onMutate(recordQueue); + recordQueue.length = 0; + } + function mutateDom(callback) { + if (!currentlyObserving) + return callback(); + stopObservingMutations(); + let result = callback(); + startObservingMutations(); + return result; + } + var isCollecting = false; + var deferredMutations = []; + function deferMutations() { + isCollecting = true; + } + function flushAndStopDeferringMutations() { + isCollecting = false; + onMutate(deferredMutations); + deferredMutations = []; + } + function onMutate(mutations) { + if (isCollecting) { + deferredMutations = deferredMutations.concat(mutations); + return; + } + let addedNodes = []; + let removedNodes = []; + let addedAttributes = new Map(); + let removedAttributes = new Map(); + for (let i = 0; i < mutations.length; i++) { + if (mutations[i].target._x_ignoreMutationObserver) + continue; + if (mutations[i].type === "childList") { + mutations[i].addedNodes.forEach((node) => node.nodeType === 1 && addedNodes.push(node)); + mutations[i].removedNodes.forEach((node) => node.nodeType === 1 && removedNodes.push(node)); + } + if (mutations[i].type === "attributes") { + let el = mutations[i].target; + let name = mutations[i].attributeName; + let oldValue = mutations[i].oldValue; + let add2 = () => { + if (!addedAttributes.has(el)) + addedAttributes.set(el, []); + addedAttributes.get(el).push({name, value: el.getAttribute(name)}); + }; + let remove = () => { + if (!removedAttributes.has(el)) + removedAttributes.set(el, []); + removedAttributes.get(el).push(name); + }; + if (el.hasAttribute(name) && oldValue === null) { + add2(); + } else if (el.hasAttribute(name)) { + remove(); + add2(); + } else { + remove(); + } + } + } + removedAttributes.forEach((attrs, el) => { + cleanupAttributes(el, attrs); + }); + addedAttributes.forEach((attrs, el) => { + onAttributeAddeds.forEach((i) => i(el, attrs)); + }); + for (let node of removedNodes) { + if (addedNodes.includes(node)) + continue; + onElRemoveds.forEach((i) => i(node)); + if (node._x_cleanups) { + while (node._x_cleanups.length) + node._x_cleanups.pop()(); + } + } + addedNodes.forEach((node) => { + node._x_ignoreSelf = true; + node._x_ignore = true; + }); + for (let node of addedNodes) { + if (removedNodes.includes(node)) + continue; + if (!node.isConnected) + continue; + delete node._x_ignoreSelf; + delete node._x_ignore; + onElAddeds.forEach((i) => i(node)); + node._x_ignore = true; + node._x_ignoreSelf = true; + } + addedNodes.forEach((node) => { + delete node._x_ignoreSelf; + delete node._x_ignore; + }); + addedNodes = null; + removedNodes = null; + addedAttributes = null; + removedAttributes = null; + } + + // packages/alpinejs/src/scope.js + function scope(node) { + return mergeProxies(closestDataStack(node)); + } + function addScopeToNode(node, data2, referenceNode) { + node._x_dataStack = [data2, ...closestDataStack(referenceNode || node)]; + return () => { + node._x_dataStack = node._x_dataStack.filter((i) => i !== data2); + }; + } + function refreshScope(element, scope2) { + let existingScope = element._x_dataStack[0]; + Object.entries(scope2).forEach(([key, value]) => { + existingScope[key] = value; + }); + } + function closestDataStack(node) { + if (node._x_dataStack) + return node._x_dataStack; + if (typeof ShadowRoot === "function" && node instanceof ShadowRoot) { + return closestDataStack(node.host); + } + if (!node.parentNode) { + return []; + } + return closestDataStack(node.parentNode); + } + function mergeProxies(objects) { + let thisProxy = new Proxy({}, { + ownKeys: () => { + return Array.from(new Set(objects.flatMap((i) => Object.keys(i)))); + }, + has: (target, name) => { + return objects.some((obj) => obj.hasOwnProperty(name)); + }, + get: (target, name) => { + return (objects.find((obj) => { + if (obj.hasOwnProperty(name)) { + let descriptor = Object.getOwnPropertyDescriptor(obj, name); + if (descriptor.get && descriptor.get._x_alreadyBound || descriptor.set && descriptor.set._x_alreadyBound) { + return true; + } + if ((descriptor.get || descriptor.set) && descriptor.enumerable) { + let getter = descriptor.get; + let setter = descriptor.set; + let property = descriptor; + getter = getter && getter.bind(thisProxy); + setter = setter && setter.bind(thisProxy); + if (getter) + getter._x_alreadyBound = true; + if (setter) + setter._x_alreadyBound = true; + Object.defineProperty(obj, name, { + ...property, + get: getter, + set: setter + }); + } + return true; + } + return false; + }) || {})[name]; + }, + set: (target, name, value) => { + let closestObjectWithKey = objects.find((obj) => obj.hasOwnProperty(name)); + if (closestObjectWithKey) { + closestObjectWithKey[name] = value; + } else { + objects[objects.length - 1][name] = value; + } + return true; + } + }); + return thisProxy; + } + + // packages/alpinejs/src/interceptor.js + function initInterceptors(data2) { + let isObject2 = (val) => typeof val === "object" && !Array.isArray(val) && val !== null; + let recurse = (obj, basePath = "") => { + Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, {value, enumerable}]) => { + if (enumerable === false || value === void 0) + return; + let path = basePath === "" ? key : `${basePath}.${key}`; + if (typeof value === "object" && value !== null && value._x_interceptor) { + obj[key] = value.initialize(data2, path, key); + } else { + if (isObject2(value) && value !== obj && !(value instanceof Element)) { + recurse(value, path); + } + } + }); + }; + return recurse(data2); + } + function interceptor(callback, mutateObj = () => { + }) { + let obj = { + initialValue: void 0, + _x_interceptor: true, + initialize(data2, path, key) { + return callback(this.initialValue, () => get(data2, path), (value) => set(data2, path, value), path, key); + } + }; + mutateObj(obj); + return (initialValue) => { + if (typeof initialValue === "object" && initialValue !== null && initialValue._x_interceptor) { + let initialize = obj.initialize.bind(obj); + obj.initialize = (data2, path, key) => { + let innerValue = initialValue.initialize(data2, path, key); + obj.initialValue = innerValue; + return initialize(data2, path, key); + }; + } else { + obj.initialValue = initialValue; + } + return obj; + }; + } + function get(obj, path) { + return path.split(".").reduce((carry, segment) => carry[segment], obj); + } + function set(obj, path, value) { + if (typeof path === "string") + path = path.split("."); + if (path.length === 1) + obj[path[0]] = value; + else if (path.length === 0) + throw error; + else { + if (obj[path[0]]) + return set(obj[path[0]], path.slice(1), value); + else { + obj[path[0]] = {}; + return set(obj[path[0]], path.slice(1), value); + } + } + } + + // packages/alpinejs/src/magics.js + var magics = {}; + function magic(name, callback) { + magics[name] = callback; + } + function injectMagics(obj, el) { + Object.entries(magics).forEach(([name, callback]) => { + Object.defineProperty(obj, `$${name}`, { + get() { + let [utilities, cleanup2] = getElementBoundUtilities(el); + utilities = {interceptor, ...utilities}; + onElRemoved(el, cleanup2); + return callback(el, utilities); + }, + enumerable: false + }); + }); + return obj; + } + + // packages/alpinejs/src/utils/error.js + function tryCatch(el, expression, callback, ...args) { + try { + return callback(...args); + } catch (e) { + handleError(e, el, expression); + } + } + function handleError(error2, el, expression = void 0) { + Object.assign(error2, {el, expression}); + console.warn(`Alpine Expression Error: ${error2.message} + +${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); + setTimeout(() => { + throw error2; + }, 0); + } + + // packages/alpinejs/src/evaluator.js + var shouldAutoEvaluateFunctions = true; + function dontAutoEvaluateFunctions(callback) { + let cache = shouldAutoEvaluateFunctions; + shouldAutoEvaluateFunctions = false; + callback(); + shouldAutoEvaluateFunctions = cache; + } + function evaluate(el, expression, extras = {}) { + let result; + evaluateLater(el, expression)((value) => result = value, extras); + return result; + } + function evaluateLater(...args) { + return theEvaluatorFunction(...args); + } + var theEvaluatorFunction = normalEvaluator; + function setEvaluator(newEvaluator) { + theEvaluatorFunction = newEvaluator; + } + function normalEvaluator(el, expression) { + let overriddenMagics = {}; + injectMagics(overriddenMagics, el); + let dataStack = [overriddenMagics, ...closestDataStack(el)]; + if (typeof expression === "function") { + return generateEvaluatorFromFunction(dataStack, expression); + } + let evaluator = generateEvaluatorFromString(dataStack, expression, el); + return tryCatch.bind(null, el, expression, evaluator); + } + function generateEvaluatorFromFunction(dataStack, func) { + return (receiver = () => { + }, {scope: scope2 = {}, params = []} = {}) => { + let result = func.apply(mergeProxies([scope2, ...dataStack]), params); + runIfTypeOfFunction(receiver, result); + }; + } + var evaluatorMemo = {}; + function generateFunctionFromString(expression, el) { + if (evaluatorMemo[expression]) { + return evaluatorMemo[expression]; + } + let AsyncFunction = Object.getPrototypeOf(async function() { + }).constructor; + let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /^(let|const)\s/.test(expression) ? `(() => { ${expression} })()` : expression; + const safeAsyncFunction = () => { + try { + return new AsyncFunction(["__self", "scope"], `with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;`); + } catch (error2) { + handleError(error2, el, expression); + return Promise.resolve(); + } + }; + let func = safeAsyncFunction(); + evaluatorMemo[expression] = func; + return func; + } + function generateEvaluatorFromString(dataStack, expression, el) { + let func = generateFunctionFromString(expression, el); + return (receiver = () => { + }, {scope: scope2 = {}, params = []} = {}) => { + func.result = void 0; + func.finished = false; + let completeScope = mergeProxies([scope2, ...dataStack]); + if (typeof func === "function") { + let promise = func(func, completeScope).catch((error2) => handleError(error2, el, expression)); + if (func.finished) { + runIfTypeOfFunction(receiver, func.result, completeScope, params, el); + func.result = void 0; + } else { + promise.then((result) => { + runIfTypeOfFunction(receiver, result, completeScope, params, el); + }).catch((error2) => handleError(error2, el, expression)).finally(() => func.result = void 0); + } + } + }; + } + function runIfTypeOfFunction(receiver, value, scope2, params, el) { + if (shouldAutoEvaluateFunctions && typeof value === "function") { + let result = value.apply(scope2, params); + if (result instanceof Promise) { + result.then((i) => runIfTypeOfFunction(receiver, i, scope2, params)).catch((error2) => handleError(error2, el, value)); + } else { + receiver(result); + } + } else { + receiver(value); + } + } + + // packages/alpinejs/src/directives.js + var prefixAsString = "x-"; + function prefix(subject = "") { + return prefixAsString + subject; + } + function setPrefix(newPrefix) { + prefixAsString = newPrefix; + } + var directiveHandlers = {}; + function directive(name, callback) { + directiveHandlers[name] = callback; + } + function directives(el, attributes, originalAttributeOverride) { + attributes = Array.from(attributes); + if (el._x_virtualDirectives) { + let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({name, value})); + let staticAttributes = attributesOnly(vAttributes); + vAttributes = vAttributes.map((attribute) => { + if (staticAttributes.find((attr) => attr.name === attribute.name)) { + return { + name: `x-bind:${attribute.name}`, + value: `"${attribute.value}"` + }; + } + return attribute; + }); + attributes = attributes.concat(vAttributes); + } + let transformedAttributeMap = {}; + let directives2 = attributes.map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName)).filter(outNonAlpineAttributes).map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride)).sort(byPriority); + return directives2.map((directive2) => { + return getDirectiveHandler(el, directive2); + }); + } + function attributesOnly(attributes) { + return Array.from(attributes).map(toTransformedAttributes()).filter((attr) => !outNonAlpineAttributes(attr)); + } + var isDeferringHandlers = false; + var directiveHandlerStacks = new Map(); + var currentHandlerStackKey = Symbol(); + function deferHandlingDirectives(callback) { + isDeferringHandlers = true; + let key = Symbol(); + currentHandlerStackKey = key; + directiveHandlerStacks.set(key, []); + let flushHandlers = () => { + while (directiveHandlerStacks.get(key).length) + directiveHandlerStacks.get(key).shift()(); + directiveHandlerStacks.delete(key); + }; + let stopDeferring = () => { + isDeferringHandlers = false; + flushHandlers(); + }; + callback(flushHandlers); + stopDeferring(); + } + function getElementBoundUtilities(el) { + let cleanups = []; + let cleanup2 = (callback) => cleanups.push(callback); + let [effect3, cleanupEffect] = elementBoundEffect(el); + cleanups.push(cleanupEffect); + let utilities = { + Alpine: alpine_default, + effect: effect3, + cleanup: cleanup2, + evaluateLater: evaluateLater.bind(evaluateLater, el), + evaluate: evaluate.bind(evaluate, el) + }; + let doCleanup = () => cleanups.forEach((i) => i()); + return [utilities, doCleanup]; + } + function getDirectiveHandler(el, directive2) { + let noop = () => { + }; + let handler3 = directiveHandlers[directive2.type] || noop; + let [utilities, cleanup2] = getElementBoundUtilities(el); + onAttributeRemoved(el, directive2.original, cleanup2); + let fullHandler = () => { + if (el._x_ignore || el._x_ignoreSelf) + return; + handler3.inline && handler3.inline(el, directive2, utilities); + handler3 = handler3.bind(handler3, el, directive2, utilities); + isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler3) : handler3(); + }; + fullHandler.runCleanups = cleanup2; + return fullHandler; + } + var startingWith = (subject, replacement) => ({name, value}) => { + if (name.startsWith(subject)) + name = name.replace(subject, replacement); + return {name, value}; + }; + var into = (i) => i; + function toTransformedAttributes(callback = () => { + }) { + return ({name, value}) => { + let {name: newName, value: newValue} = attributeTransformers.reduce((carry, transform) => { + return transform(carry); + }, {name, value}); + if (newName !== name) + callback(newName, name); + return {name: newName, value: newValue}; + }; + } + var attributeTransformers = []; + function mapAttributes(callback) { + attributeTransformers.push(callback); + } + function outNonAlpineAttributes({name}) { + return alpineAttributeRegex().test(name); + } + var alpineAttributeRegex = () => new RegExp(`^${prefixAsString}([^:^.]+)\\b`); + function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) { + return ({name, value}) => { + let typeMatch = name.match(alpineAttributeRegex()); + let valueMatch = name.match(/:([a-zA-Z0-9\-:]+)/); + let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []; + let original = originalAttributeOverride || transformedAttributeMap[name] || name; + return { + type: typeMatch ? typeMatch[1] : null, + value: valueMatch ? valueMatch[1] : null, + modifiers: modifiers.map((i) => i.replace(".", "")), + expression: value, + original + }; + }; + } + var DEFAULT = "DEFAULT"; + var directiveOrder = [ + "ignore", + "ref", + "data", + "id", + "bind", + "init", + "for", + "mask", + "model", + "modelable", + "transition", + "show", + "if", + DEFAULT, + "teleport" + ]; + function byPriority(a, b) { + let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type; + let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type; + return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB); + } + + // packages/alpinejs/src/utils/dispatch.js + function dispatch(el, name, detail = {}) { + el.dispatchEvent(new CustomEvent(name, { + detail, + bubbles: true, + composed: true, + cancelable: true + })); + } + + // packages/alpinejs/src/nextTick.js + var tickStack = []; + var isHolding = false; + function nextTick(callback = () => { + }) { + queueMicrotask(() => { + isHolding || setTimeout(() => { + releaseNextTicks(); + }); + }); + return new Promise((res) => { + tickStack.push(() => { + callback(); + res(); + }); + }); + } + function releaseNextTicks() { + isHolding = false; + while (tickStack.length) + tickStack.shift()(); + } + function holdNextTicks() { + isHolding = true; + } + + // packages/alpinejs/src/utils/walk.js + function walk(el, callback) { + if (typeof ShadowRoot === "function" && el instanceof ShadowRoot) { + Array.from(el.children).forEach((el2) => walk(el2, callback)); + return; + } + let skip = false; + callback(el, () => skip = true); + if (skip) + return; + let node = el.firstElementChild; + while (node) { + walk(node, callback, false); + node = node.nextElementSibling; + } + } + + // packages/alpinejs/src/utils/warn.js + function warn(message, ...args) { + console.warn(`Alpine Warning: ${message}`, ...args); + } + + // packages/alpinejs/src/lifecycle.js + function start() { + if (!document.body) + warn("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `