@hienlh/ppm 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/web/assets/ai-settings-section-CBpXqM-I.js +1 -0
- package/dist/web/assets/chat-tab-DXtCcWg2.js +7 -0
- package/dist/web/assets/code-editor-CxMuSJrX.js +1 -0
- package/dist/web/assets/{database-viewer-BxUpM_uA.js → database-viewer-C6riNG1v.js} +1 -1
- package/dist/web/assets/{diff-viewer-DAhrHpNM.js → diff-viewer-CQIockZr.js} +1 -1
- package/dist/web/assets/git-graph-D2BR2rfP.js +1 -0
- package/dist/web/assets/index-4pPCbWJp.css +2 -0
- package/dist/web/assets/index-B9ZHWVob.js +29 -0
- package/dist/web/assets/{input-nI4xe1Y9.js → input-BpFvhpT8.js} +1 -1
- package/dist/web/assets/keybindings-store-fcYIcK0C.js +1 -0
- package/dist/web/assets/{markdown-renderer-CvGYO9sH.js → markdown-renderer-DKVNZXEw.js} +2 -2
- package/dist/web/assets/{postgres-viewer-BL99auSm.js → postgres-viewer-DHvwHGEL.js} +1 -1
- package/dist/web/assets/settings-store-DS-ifJ7c.js +1 -0
- package/dist/web/assets/settings-tab-D_quOlcC.js +1 -0
- package/dist/web/assets/{sqlite-viewer-DfgaCbWT.js → sqlite-viewer-C5Vj_kSU.js} +1 -1
- package/dist/web/assets/{tab-store-Bm1Hw8OR.js → tab-store-D7tRt0VT.js} +1 -1
- package/dist/web/assets/{terminal-tab-D27e4ZTD.js → terminal-tab-B95lVSty.js} +2 -2
- package/dist/web/assets/{use-monaco-theme-Dexl3s3E.js → use-monaco-theme-M04jkKDM.js} +1 -1
- package/dist/web/index.html +9 -8
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/server/routes/files.ts +81 -0
- package/src/web/components/chat/chat-history-bar.tsx +15 -1
- package/src/web/components/chat/chat-tab.tsx +23 -0
- package/src/web/components/editor/code-editor.tsx +10 -0
- package/src/web/components/explorer/search-panel.tsx +310 -0
- package/src/web/components/layout/sidebar.tsx +6 -1
- package/src/web/hooks/use-global-keybindings.ts +9 -0
- package/src/web/hooks/use-notification-badge.ts +20 -11
- package/src/web/hooks/use-tab-overflow.ts +8 -2
- package/src/web/stores/keybindings-store.ts +1 -0
- package/src/web/stores/settings-store.ts +2 -2
- package/dist/web/assets/chat-tab-CbNbBMGw.js +0 -7
- package/dist/web/assets/code-editor-D6OuzcC-.js +0 -1
- package/dist/web/assets/git-graph-BpTt5iOd.js +0 -1
- package/dist/web/assets/index-BU_07_oW.js +0 -29
- package/dist/web/assets/index-CBQhXXeV.css +0 -2
- package/dist/web/assets/keybindings-store-C0m8_V9X.js +0 -1
- package/dist/web/assets/settings-store-CfB0vCtQ.js +0 -1
- package/dist/web/assets/settings-tab-Bwsxb41F.js +0 -1
- /package/dist/web/assets/{api-client-4Ni0i4Hl.js → api-client-B0aMOJxF.js} +0 -0
- /package/dist/web/assets/{dist-CNRrBoQi.js → dist-QgqOdSYG.js} +0 -0
- /package/dist/web/assets/{react-DHSo28we.js → react-Dk7fkoaB.js} +0 -0
- /package/dist/web/assets/{table-DCVKGOr2.js → table-B6neW6Hr.js} +0 -0
- /package/dist/web/assets/{utils-siJJ3uG0.js → utils-DBpa1UZX.js} +0 -0
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"4276cff85869e97ffbe37bc4b5426eda","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-siJJ3uG0.js"},{"revision":null,"url":"assets/use-monaco-theme-Dexl3s3E.js"},{"revision":null,"url":"assets/terminal-tab-D27e4ZTD.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/table-DCVKGOr2.js"},{"revision":null,"url":"assets/tab-store-Bm1Hw8OR.js"},{"revision":null,"url":"assets/sqlite-viewer-DfgaCbWT.js"},{"revision":null,"url":"assets/settings-tab-Bwsxb41F.js"},{"revision":null,"url":"assets/settings-store-CfB0vCtQ.js"},{"revision":null,"url":"assets/react-DHSo28we.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-BL99auSm.js"},{"revision":null,"url":"assets/markdown-renderer-CvGYO9sH.js"},{"revision":null,"url":"assets/keybindings-store-C0m8_V9X.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-nI4xe1Y9.js"},{"revision":null,"url":"assets/index-CBQhXXeV.css"},{"revision":null,"url":"assets/index-BU_07_oW.js"},{"revision":null,"url":"assets/git-graph-BpTt5iOd.js"},{"revision":null,"url":"assets/dist-CNRrBoQi.js"},{"revision":null,"url":"assets/diff-viewer-DAhrHpNM.js"},{"revision":null,"url":"assets/database-viewer-BxUpM_uA.js"},{"revision":null,"url":"assets/code-editor-D6OuzcC-.js"},{"revision":null,"url":"assets/chat-tab-CbNbBMGw.js"},{"revision":null,"url":"assets/api-client-4Ni0i4Hl.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"2d6f942c9da6283860d6fd34d33a4539","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DBpa1UZX.js"},{"revision":null,"url":"assets/use-monaco-theme-M04jkKDM.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-B95lVSty.js"},{"revision":null,"url":"assets/table-B6neW6Hr.js"},{"revision":null,"url":"assets/tab-store-D7tRt0VT.js"},{"revision":null,"url":"assets/sqlite-viewer-C5Vj_kSU.js"},{"revision":null,"url":"assets/settings-tab-D_quOlcC.js"},{"revision":null,"url":"assets/settings-store-DS-ifJ7c.js"},{"revision":null,"url":"assets/react-Dk7fkoaB.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-DHvwHGEL.js"},{"revision":null,"url":"assets/markdown-renderer-DKVNZXEw.js"},{"revision":null,"url":"assets/keybindings-store-fcYIcK0C.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-BpFvhpT8.js"},{"revision":null,"url":"assets/index-B9ZHWVob.js"},{"revision":null,"url":"assets/index-4pPCbWJp.css"},{"revision":null,"url":"assets/git-graph-D2BR2rfP.js"},{"revision":null,"url":"assets/dist-QgqOdSYG.js"},{"revision":null,"url":"assets/diff-viewer-CQIockZr.js"},{"revision":null,"url":"assets/database-viewer-C6riNG1v.js"},{"revision":null,"url":"assets/code-editor-CxMuSJrX.js"},{"revision":null,"url":"assets/chat-tab-DXtCcWg2.js"},{"revision":null,"url":"assets/api-client-B0aMOJxF.js"},{"revision":null,"url":"assets/ai-settings-section-CBpXqM-I.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
|
@@ -152,6 +152,87 @@ fileRoutes.post("/rename", async (c) => {
|
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
/** Convert glob pattern (VSCode-style) to RegExp for path filtering.
|
|
156
|
+
* - `*.ts` → matches any .ts file in any directory
|
|
157
|
+
* - `src/**` → matches any file under src/
|
|
158
|
+
* - `src/**\/*.ts` → matches .ts files under src/
|
|
159
|
+
*/
|
|
160
|
+
function globToPathRegex(glob: string): RegExp {
|
|
161
|
+
const escaped = glob
|
|
162
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape regex special chars
|
|
163
|
+
.replace(/\*\*/g, "\x00") // temp placeholder for **
|
|
164
|
+
.replace(/\*/g, "[^/]*") // * = within one segment
|
|
165
|
+
.replace(/\x00/g, ".*") // ** = across segments
|
|
166
|
+
.replace(/\?/g, "[^/]"); // ? = single non-slash char
|
|
167
|
+
// No slash in pattern → match at any depth (like **/<pattern>)
|
|
168
|
+
const re = glob.includes("/") ? `^${escaped}($|/)` : `(^|/)${escaped}($|/)`;
|
|
169
|
+
return new RegExp(re);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** GET /files/search?q=...&caseSensitive=false — search file content with grep */
|
|
173
|
+
fileRoutes.get("/search", async (c) => {
|
|
174
|
+
const projectPath = c.get("projectPath");
|
|
175
|
+
const q = (c.req.query("q") ?? "").trim();
|
|
176
|
+
const caseSensitive = c.req.query("caseSensitive") === "true";
|
|
177
|
+
const wholeWord = c.req.query("wholeWord") === "true";
|
|
178
|
+
const useRegex = c.req.query("regex") === "true";
|
|
179
|
+
const include = (c.req.query("include") ?? "").trim();
|
|
180
|
+
|
|
181
|
+
if (!useRegex && q.length < 2) return c.json(ok({ results: [], total: 0 }));
|
|
182
|
+
if (useRegex && q.length < 1) return c.json(ok({ results: [], total: 0 }));
|
|
183
|
+
|
|
184
|
+
// Build include path filter regexes (post-filter, supports paths + globs)
|
|
185
|
+
const includeFilters = include
|
|
186
|
+
? include.split(",").map((s) => s.trim()).filter(Boolean).map(globToPathRegex)
|
|
187
|
+
: [];
|
|
188
|
+
|
|
189
|
+
// Build the grep pattern
|
|
190
|
+
let pattern = useRegex ? q : q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
191
|
+
if (wholeWord) pattern = `\\b${pattern}\\b`;
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const EXCLUDE_DIRS = ["node_modules", ".git", "dist", ".next", "build", ".turbo", "coverage", "__pycache__"];
|
|
195
|
+
const excludeDirArgs = EXCLUDE_DIRS.flatMap((d) => ["--exclude-dir", d]);
|
|
196
|
+
const excludeArgs = ["--exclude=*.min.js", "--exclude=*.map", "--exclude=*.lock", "--exclude=bun.lock"];
|
|
197
|
+
const flags = ["-rn", "--max-count=5", "-I", "-E", ...(caseSensitive ? [] : ["-i"])];
|
|
198
|
+
|
|
199
|
+
const proc = Bun.spawnSync({
|
|
200
|
+
cmd: ["grep", ...flags, ...excludeDirArgs, ...excludeArgs, "--", pattern, projectPath],
|
|
201
|
+
stdout: "pipe",
|
|
202
|
+
stderr: "pipe",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const raw = proc.stdout.toString();
|
|
206
|
+
if (!raw.trim()) return c.json(ok({ results: [], total: 0 }));
|
|
207
|
+
|
|
208
|
+
// Parse grep output: /abs/path/file.ts:42:content
|
|
209
|
+
const fileMap = new Map<string, { lineNum: number; content: string }[]>();
|
|
210
|
+
for (const line of raw.split("\n")) {
|
|
211
|
+
if (!line) continue;
|
|
212
|
+
// Strip projectPath prefix, then split on first two colons
|
|
213
|
+
const rel = line.startsWith(projectPath) ? line.slice(projectPath.length + 1) : line;
|
|
214
|
+
const firstColon = rel.indexOf(":");
|
|
215
|
+
if (firstColon < 0) continue;
|
|
216
|
+
const secondColon = rel.indexOf(":", firstColon + 1);
|
|
217
|
+
if (secondColon < 0) continue;
|
|
218
|
+
const filePath = rel.slice(0, firstColon);
|
|
219
|
+
const lineNum = parseInt(rel.slice(firstColon + 1, secondColon), 10);
|
|
220
|
+
const content = rel.slice(secondColon + 1).trimEnd();
|
|
221
|
+
if (!filePath || isNaN(lineNum)) continue;
|
|
222
|
+
// Apply include path filter if specified
|
|
223
|
+
if (includeFilters.length > 0 && !includeFilters.some((re) => re.test(filePath))) continue;
|
|
224
|
+
if (!fileMap.has(filePath)) fileMap.set(filePath, []);
|
|
225
|
+
fileMap.get(filePath)!.push({ lineNum, content });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const results = Array.from(fileMap.entries()).map(([file, matches]) => ({ file, matches }));
|
|
229
|
+
const total = results.reduce((sum, r) => sum + r.matches.length, 0);
|
|
230
|
+
return c.json(ok({ results, total }));
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return c.json(err((e as Error).message), 500);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
155
236
|
/** POST /files/move — body: { source, destination } */
|
|
156
237
|
fileRoutes.post("/move", async (c) => {
|
|
157
238
|
try {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X } from "lucide-react";
|
|
2
|
+
import { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X, BellOff } from "lucide-react";
|
|
3
3
|
import { Activity } from "lucide-react";
|
|
4
4
|
import { api, projectUrl } from "@/lib/api-client";
|
|
5
5
|
import { useTabStore } from "@/stores/tab-store";
|
|
6
|
+
import { useNotificationStore } from "@/stores/notification-store";
|
|
6
7
|
import { AISettingsSection } from "@/components/settings/ai-settings-section";
|
|
7
8
|
import { UsageDetailPanel } from "./usage-badge";
|
|
8
9
|
import type { SessionInfo } from "../../../types/chat";
|
|
@@ -54,6 +55,8 @@ export function ChatHistoryBar({
|
|
|
54
55
|
const [activePanel, setActivePanel] = useState<PanelType>(null);
|
|
55
56
|
const [sessions, setSessions] = useState<SessionInfo[]>([]);
|
|
56
57
|
const [loading, setLoading] = useState(false);
|
|
58
|
+
const hasUnread = useNotificationStore((s) => sessionId ? s.notifications.has(sessionId) : false);
|
|
59
|
+
const clearForSession = useNotificationStore((s) => s.clearForSession);
|
|
57
60
|
const [searchQuery, setSearchQuery] = useState("");
|
|
58
61
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
59
62
|
const [editingTitle, setEditingTitle] = useState("");
|
|
@@ -178,6 +181,17 @@ export function ChatHistoryBar({
|
|
|
178
181
|
{/* Spacer */}
|
|
179
182
|
<div className="flex-1" />
|
|
180
183
|
|
|
184
|
+
{/* Mark as read */}
|
|
185
|
+
{hasUnread && sessionId && (
|
|
186
|
+
<button
|
|
187
|
+
onClick={() => clearForSession(sessionId)}
|
|
188
|
+
className="p-1 rounded text-amber-500 hover:text-amber-400 hover:bg-surface-elevated transition-colors"
|
|
189
|
+
title="Mark as read"
|
|
190
|
+
>
|
|
191
|
+
<BellOff className="size-3" />
|
|
192
|
+
</button>
|
|
193
|
+
)}
|
|
194
|
+
|
|
181
195
|
{/* Connection indicator */}
|
|
182
196
|
{onReconnect && (
|
|
183
197
|
<button
|
|
@@ -5,6 +5,8 @@ import { useChat } from "@/hooks/use-chat";
|
|
|
5
5
|
import { useUsage } from "@/hooks/use-usage";
|
|
6
6
|
import { useTabStore } from "@/stores/tab-store";
|
|
7
7
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
8
|
+
import { usePanelStore } from "@/stores/panel-store";
|
|
9
|
+
import { useNotificationStore } from "@/stores/notification-store";
|
|
8
10
|
import { openBugReportPopup } from "@/lib/report-bug";
|
|
9
11
|
import { MessageList } from "./message-list";
|
|
10
12
|
import { MessageInput, type ChatAttachment } from "./message-input";
|
|
@@ -79,6 +81,27 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
79
81
|
isConnected,
|
|
80
82
|
} = useChat(sessionId, providerId, projectName);
|
|
81
83
|
|
|
84
|
+
// Auto-clear notification badge when this tab is active and document is visible.
|
|
85
|
+
// Handles the case where notification arrived while browser tab was hidden.
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!sessionId || !tabId) return;
|
|
88
|
+
const maybeClear = () => {
|
|
89
|
+
if (document.hidden) return;
|
|
90
|
+
const { panels, focusedPanelId } = usePanelStore.getState();
|
|
91
|
+
const panel = panels[focusedPanelId];
|
|
92
|
+
if (panel?.activeTabId === tabId) {
|
|
93
|
+
useNotificationStore.getState().clearForSession(sessionId);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
maybeClear();
|
|
97
|
+
document.addEventListener("visibilitychange", maybeClear);
|
|
98
|
+
const unsub = usePanelStore.subscribe(maybeClear);
|
|
99
|
+
return () => {
|
|
100
|
+
document.removeEventListener("visibilitychange", maybeClear);
|
|
101
|
+
unsub();
|
|
102
|
+
};
|
|
103
|
+
}, [sessionId, tabId]);
|
|
104
|
+
|
|
82
105
|
// Update tab title when SDK summary arrives
|
|
83
106
|
useEffect(() => {
|
|
84
107
|
if (tabId && sessionTitle) {
|
|
@@ -130,8 +130,18 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
130
130
|
saveTimerRef.current = setTimeout(() => saveFile(latestContentRef.current), 1000);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Jump to line when metadata.lineNumber is set (e.g. from search panel)
|
|
134
|
+
const lineNumber = metadata?.lineNumber as number | undefined;
|
|
133
135
|
const handleEditorMount: OnMount = useCallback((editor, monaco) => {
|
|
134
136
|
editorRef.current = editor;
|
|
137
|
+
if (lineNumber && lineNumber > 0) {
|
|
138
|
+
// Defer until content is rendered
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
editor.revealLineInCenter(lineNumber);
|
|
141
|
+
editor.setPosition({ lineNumber, column: 1 });
|
|
142
|
+
editor.focus();
|
|
143
|
+
}, 100);
|
|
144
|
+
}
|
|
135
145
|
// Alt+Z → toggle word wrap
|
|
136
146
|
editor.addCommand(
|
|
137
147
|
monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
|
+
import { Search, CaseSensitive, ChevronRight, ChevronDown, FileText, X, Loader2, WholeWord, Regex, ReplaceAll } from "lucide-react";
|
|
3
|
+
import { useProjectStore } from "@/stores/project-store";
|
|
4
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
5
|
+
import { projectUrl, api } from "@/lib/api-client";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
interface SearchMatch {
|
|
9
|
+
lineNum: number;
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SearchResult {
|
|
14
|
+
file: string;
|
|
15
|
+
matches: SearchMatch[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Build highlight regex from query + options */
|
|
19
|
+
function buildHighlightRegex(query: string, caseSensitive: boolean, wholeWord: boolean, useRegex: boolean): RegExp | null {
|
|
20
|
+
if (!query) return null;
|
|
21
|
+
try {
|
|
22
|
+
let pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23
|
+
if (wholeWord) pattern = `\\b${pattern}\\b`;
|
|
24
|
+
return new RegExp(`(${pattern})`, caseSensitive ? "g" : "gi");
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function HighlightMatch({ text, re }: { text: string; re: RegExp | null }) {
|
|
31
|
+
if (!re) return <span>{text}</span>;
|
|
32
|
+
try {
|
|
33
|
+
re.lastIndex = 0;
|
|
34
|
+
const parts = text.split(re);
|
|
35
|
+
re.lastIndex = 0;
|
|
36
|
+
return (
|
|
37
|
+
<span>
|
|
38
|
+
{parts.map((p, i) => {
|
|
39
|
+
re.lastIndex = 0;
|
|
40
|
+
return re.test(p) ? <mark key={i} className="bg-yellow-300/40 text-foreground rounded-sm">{p}</mark> : p;
|
|
41
|
+
})}
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
return <span>{text}</span>;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function OptionButton({ active, onClick, title, children }: { active: boolean; onClick: () => void; title: string; children: React.ReactNode }) {
|
|
50
|
+
return (
|
|
51
|
+
<button
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
title={title}
|
|
54
|
+
className={cn(
|
|
55
|
+
"flex items-center justify-center w-6 h-6 rounded border shrink-0",
|
|
56
|
+
active ? "border-primary text-primary bg-primary/10" : "border-border text-text-subtle hover:text-foreground hover:border-border/80"
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function SearchPanel() {
|
|
65
|
+
const { activeProject } = useProjectStore();
|
|
66
|
+
const openTab = useTabStore((s) => s.openTab);
|
|
67
|
+
|
|
68
|
+
const [query, setQuery] = useState("");
|
|
69
|
+
const [caseSensitive, setCaseSensitive] = useState(false);
|
|
70
|
+
const [wholeWord, setWholeWord] = useState(false);
|
|
71
|
+
const [useRegex, setUseRegex] = useState(false);
|
|
72
|
+
const [regexError, setRegexError] = useState(false);
|
|
73
|
+
const [filesFilter, setFilesFilter] = useState("");
|
|
74
|
+
const [replace, setReplace] = useState("");
|
|
75
|
+
const [replacing, setReplacing] = useState(false);
|
|
76
|
+
const [replaceCount, setReplaceCount] = useState<number | null>(null);
|
|
77
|
+
const [results, setResults] = useState<SearchResult[]>([]);
|
|
78
|
+
const [total, setTotal] = useState(0);
|
|
79
|
+
const [loading, setLoading] = useState(false);
|
|
80
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
|
|
81
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
82
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
83
|
+
|
|
84
|
+
const doSearch = useCallback(async (q: string, cs: boolean, ww: boolean, rx: boolean, ff: string) => {
|
|
85
|
+
setRegexError(false);
|
|
86
|
+
if (!activeProject || (!rx && q.length < 2) || (rx && q.length < 1)) {
|
|
87
|
+
setResults([]);
|
|
88
|
+
setTotal(0);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (rx) {
|
|
92
|
+
try { new RegExp(q); } catch { setRegexError(true); setResults([]); return; }
|
|
93
|
+
}
|
|
94
|
+
setLoading(true);
|
|
95
|
+
try {
|
|
96
|
+
const params = new URLSearchParams({ q, caseSensitive: String(cs), wholeWord: String(ww), regex: String(rx) });
|
|
97
|
+
if (ff) params.set("include", ff);
|
|
98
|
+
const data = await api.get<{ results: SearchResult[]; total: number }>(
|
|
99
|
+
`${projectUrl(activeProject.name)}/files/search?${params}`
|
|
100
|
+
);
|
|
101
|
+
setResults(data.results);
|
|
102
|
+
setTotal(data.total);
|
|
103
|
+
} catch {
|
|
104
|
+
setResults([]);
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
}, [activeProject]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
112
|
+
debounceRef.current = setTimeout(() => doSearch(query, caseSensitive, wholeWord, useRegex, filesFilter), 300);
|
|
113
|
+
return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
|
|
114
|
+
}, [query, caseSensitive, wholeWord, useRegex, filesFilter, doSearch]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => { inputRef.current?.focus(); }, []);
|
|
117
|
+
|
|
118
|
+
const highlightRe = buildHighlightRegex(query, caseSensitive, wholeWord, useRegex);
|
|
119
|
+
|
|
120
|
+
function openFile(file: string, lineNum?: number) {
|
|
121
|
+
if (!activeProject) return;
|
|
122
|
+
const name = file.split("/").pop() ?? file;
|
|
123
|
+
openTab({
|
|
124
|
+
type: "editor",
|
|
125
|
+
title: name,
|
|
126
|
+
metadata: { filePath: file, projectName: activeProject.name, lineNumber: lineNum },
|
|
127
|
+
projectId: activeProject.name,
|
|
128
|
+
closable: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function toggleCollapse(file: string) {
|
|
133
|
+
setCollapsed((prev) => {
|
|
134
|
+
const next = new Set(prev);
|
|
135
|
+
next.has(file) ? next.delete(file) : next.add(file);
|
|
136
|
+
return next;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function doReplaceAll() {
|
|
141
|
+
if (!activeProject || !query || results.length === 0 || replacing) return;
|
|
142
|
+
setReplacing(true);
|
|
143
|
+
setReplaceCount(null);
|
|
144
|
+
let count = 0;
|
|
145
|
+
try {
|
|
146
|
+
for (const r of results) {
|
|
147
|
+
const fileData = await api.get<{ content: string }>(
|
|
148
|
+
`${projectUrl(activeProject.name)}/files/read?path=${encodeURIComponent(r.file)}`
|
|
149
|
+
);
|
|
150
|
+
const re = buildHighlightRegex(query, caseSensitive, wholeWord, useRegex);
|
|
151
|
+
if (!re) continue;
|
|
152
|
+
re.lastIndex = 0;
|
|
153
|
+
const matches = fileData.content.match(re) ?? [];
|
|
154
|
+
if (!matches.length) continue;
|
|
155
|
+
count += matches.length;
|
|
156
|
+
re.lastIndex = 0;
|
|
157
|
+
const newContent = fileData.content.replace(re, replace);
|
|
158
|
+
await api.put(`${projectUrl(activeProject.name)}/files/write`, { path: r.file, content: newContent });
|
|
159
|
+
}
|
|
160
|
+
setReplaceCount(count);
|
|
161
|
+
doSearch(query, caseSensitive, wholeWord, useRegex, filesFilter);
|
|
162
|
+
} catch {
|
|
163
|
+
// ignore
|
|
164
|
+
} finally {
|
|
165
|
+
setReplacing(false);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className="flex flex-col h-full">
|
|
171
|
+
{/* Search input + options */}
|
|
172
|
+
<div className="p-2 border-b border-border space-y-1.5">
|
|
173
|
+
{/* Search row */}
|
|
174
|
+
<div className="relative flex items-center gap-1">
|
|
175
|
+
<div className="relative flex-1">
|
|
176
|
+
<Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-text-subtle pointer-events-none" />
|
|
177
|
+
<input
|
|
178
|
+
ref={inputRef}
|
|
179
|
+
value={query}
|
|
180
|
+
onChange={(e) => { setQuery(e.target.value); setReplaceCount(null); }}
|
|
181
|
+
placeholder={activeProject ? "Search files…" : "Select a project first"}
|
|
182
|
+
disabled={!activeProject}
|
|
183
|
+
className={cn(
|
|
184
|
+
"w-full pl-7 pr-6 py-1 text-xs bg-input border rounded focus:outline-none focus:ring-1 focus:ring-primary/50 disabled:opacity-50",
|
|
185
|
+
regexError ? "border-destructive" : "border-border"
|
|
186
|
+
)}
|
|
187
|
+
/>
|
|
188
|
+
{query && (
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => { setQuery(""); setResults([]); setTotal(0); setRegexError(false); setReplaceCount(null); }}
|
|
191
|
+
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-text-subtle hover:text-foreground"
|
|
192
|
+
>
|
|
193
|
+
<X className="size-3" />
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{/* Option toggles */}
|
|
200
|
+
<div className="flex items-center gap-1">
|
|
201
|
+
<OptionButton active={caseSensitive} onClick={() => setCaseSensitive((v) => !v)} title="Match Case (Alt+C)">
|
|
202
|
+
<CaseSensitive className="size-3.5" />
|
|
203
|
+
</OptionButton>
|
|
204
|
+
<OptionButton active={wholeWord} onClick={() => { setWholeWord((v) => !v); if (useRegex) setUseRegex(false); }} title="Match Whole Word (Alt+W)">
|
|
205
|
+
<WholeWord className="size-3.5" />
|
|
206
|
+
</OptionButton>
|
|
207
|
+
<OptionButton active={useRegex} onClick={() => { setUseRegex((v) => !v); if (wholeWord) setWholeWord(false); }} title="Use Regular Expression (Alt+R)">
|
|
208
|
+
<Regex className="size-3.5" />
|
|
209
|
+
</OptionButton>
|
|
210
|
+
{regexError && <span className="text-[10px] text-destructive ml-1">Invalid regex</span>}
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{/* Replace row */}
|
|
214
|
+
<div className="flex items-center gap-1">
|
|
215
|
+
<div className="relative flex-1">
|
|
216
|
+
<input
|
|
217
|
+
value={replace}
|
|
218
|
+
onChange={(e) => setReplace(e.target.value)}
|
|
219
|
+
placeholder="Replace…"
|
|
220
|
+
disabled={!activeProject}
|
|
221
|
+
className="w-full pl-2 pr-6 py-1 text-xs bg-input border border-border rounded focus:outline-none focus:ring-1 focus:ring-primary/50 disabled:opacity-50"
|
|
222
|
+
/>
|
|
223
|
+
{replace && (
|
|
224
|
+
<button onClick={() => setReplace("")} className="absolute right-1.5 top-1/2 -translate-y-1/2 text-text-subtle hover:text-foreground">
|
|
225
|
+
<X className="size-3" />
|
|
226
|
+
</button>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
<button
|
|
230
|
+
onClick={doReplaceAll}
|
|
231
|
+
disabled={!query || results.length === 0 || replacing}
|
|
232
|
+
title="Replace All"
|
|
233
|
+
className="flex items-center justify-center w-6 h-6 rounded border border-border text-text-subtle hover:text-foreground hover:border-border/80 disabled:opacity-40 shrink-0"
|
|
234
|
+
>
|
|
235
|
+
{replacing ? <Loader2 className="size-3.5 animate-spin" /> : <ReplaceAll className="size-3.5" />}
|
|
236
|
+
</button>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Files filter row */}
|
|
240
|
+
<div className="relative">
|
|
241
|
+
<input
|
|
242
|
+
value={filesFilter}
|
|
243
|
+
onChange={(e) => setFilesFilter(e.target.value)}
|
|
244
|
+
placeholder="Files to include (e.g. *.ts, src/**)"
|
|
245
|
+
disabled={!activeProject}
|
|
246
|
+
className="w-full pl-2 pr-6 py-1 text-xs bg-input border border-border rounded focus:outline-none focus:ring-1 focus:ring-primary/50 disabled:opacity-50"
|
|
247
|
+
/>
|
|
248
|
+
{filesFilter && (
|
|
249
|
+
<button onClick={() => setFilesFilter("")} className="absolute right-1.5 top-1/2 -translate-y-1/2 text-text-subtle hover:text-foreground">
|
|
250
|
+
<X className="size-3" />
|
|
251
|
+
</button>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Status line */}
|
|
256
|
+
<div className="text-[10px] text-text-subtle h-3">
|
|
257
|
+
{(loading || replacing) && (
|
|
258
|
+
<span className="flex items-center gap-1">
|
|
259
|
+
<Loader2 className="size-2.5 animate-spin" />
|
|
260
|
+
{replacing ? "Replacing…" : "Searching…"}
|
|
261
|
+
</span>
|
|
262
|
+
)}
|
|
263
|
+
{!loading && !replacing && replaceCount !== null && (
|
|
264
|
+
<span className="text-green-500">{replaceCount} replacement{replaceCount !== 1 ? "s" : ""} made</span>
|
|
265
|
+
)}
|
|
266
|
+
{!loading && !replacing && replaceCount === null && !regexError && query.length >= 2 && results.length === 0 && <span>No results</span>}
|
|
267
|
+
{!loading && !replacing && replaceCount === null && total > 0 && (
|
|
268
|
+
<span>{total} result{total !== 1 ? "s" : ""} in {results.length} file{results.length !== 1 ? "s" : ""}</span>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Results */}
|
|
274
|
+
<div className="flex-1 overflow-y-auto min-h-0">
|
|
275
|
+
{results.map((r) => {
|
|
276
|
+
const isCollapsed = collapsed.has(r.file);
|
|
277
|
+
const fileName = r.file.split("/").pop() ?? r.file;
|
|
278
|
+
const dirPath = r.file.includes("/") ? r.file.slice(0, r.file.lastIndexOf("/")) : "";
|
|
279
|
+
return (
|
|
280
|
+
<div key={r.file}>
|
|
281
|
+
<button
|
|
282
|
+
onClick={() => toggleCollapse(r.file)}
|
|
283
|
+
className="w-full flex items-center gap-1 px-2 py-1 hover:bg-muted/50 text-left"
|
|
284
|
+
>
|
|
285
|
+
{isCollapsed ? <ChevronRight className="size-3 shrink-0 text-text-subtle" /> : <ChevronDown className="size-3 shrink-0 text-text-subtle" />}
|
|
286
|
+
<FileText className="size-3 shrink-0 text-text-subtle" />
|
|
287
|
+
<span className="text-xs font-medium text-foreground truncate">{fileName}</span>
|
|
288
|
+
<span className="text-[10px] text-text-subtle truncate flex-1 min-w-0 ml-1">{dirPath}</span>
|
|
289
|
+
<span className="text-[10px] text-text-subtle shrink-0 ml-1 bg-muted px-1 rounded">{r.matches.length}</span>
|
|
290
|
+
</button>
|
|
291
|
+
|
|
292
|
+
{!isCollapsed && r.matches.map((m) => (
|
|
293
|
+
<button
|
|
294
|
+
key={`${r.file}-${m.lineNum}`}
|
|
295
|
+
onClick={() => openFile(r.file, m.lineNum)}
|
|
296
|
+
className="w-full flex items-start gap-2 pl-7 pr-2 py-0.5 hover:bg-primary/10 text-left"
|
|
297
|
+
>
|
|
298
|
+
<span className="text-[10px] text-text-subtle shrink-0 w-7 text-right pt-px">{m.lineNum}</span>
|
|
299
|
+
<span className="text-xs text-text-secondary truncate font-mono leading-4">
|
|
300
|
+
<HighlightMatch text={m.content.trimStart()} re={highlightRe} />
|
|
301
|
+
</span>
|
|
302
|
+
</button>
|
|
303
|
+
))}
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { useCallback, useRef } from "react";
|
|
2
|
-
import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database } from "lucide-react";
|
|
2
|
+
import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search } from "lucide-react";
|
|
3
3
|
import { useProjectStore } from "@/stores/project-store";
|
|
4
4
|
import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
|
|
5
5
|
import { FileTree } from "@/components/explorer/file-tree";
|
|
6
6
|
import { GitStatusPanel } from "@/components/git/git-status-panel";
|
|
7
7
|
import { SettingsTab } from "@/components/settings/settings-tab";
|
|
8
8
|
import { DatabaseSidebar } from "@/components/database/database-sidebar";
|
|
9
|
+
import { SearchPanel } from "@/components/explorer/search-panel";
|
|
9
10
|
import { cn } from "@/lib/utils";
|
|
10
11
|
|
|
11
12
|
const TABS: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [
|
|
12
13
|
{ id: "explorer", label: "Explorer", icon: FolderOpen },
|
|
14
|
+
{ id: "search", label: "Search", icon: Search },
|
|
13
15
|
{ id: "git", label: "Git", icon: GitBranch },
|
|
14
16
|
{ id: "database", label: "Database", icon: Database },
|
|
15
17
|
{ id: "settings", label: "Settings", icon: Settings },
|
|
@@ -124,6 +126,9 @@ export function Sidebar() {
|
|
|
124
126
|
{sidebarActiveTab === "git" && (
|
|
125
127
|
<GitStatusPanel metadata={{ projectName: activeProject?.name }} />
|
|
126
128
|
)}
|
|
129
|
+
{sidebarActiveTab === "search" && (
|
|
130
|
+
<SearchPanel />
|
|
131
|
+
)}
|
|
127
132
|
{sidebarActiveTab === "database" && (
|
|
128
133
|
<DatabaseSidebar />
|
|
129
134
|
)}
|
|
@@ -116,6 +116,15 @@ export function useGlobalKeybindings() {
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Open search (sidebar)
|
|
120
|
+
if (match(e, "open-search")) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
const settings = useSettingsStore.getState();
|
|
123
|
+
if (settings.sidebarCollapsed) settings.toggleSidebar();
|
|
124
|
+
settings.setSidebarActiveTab("search");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
119
128
|
// Switch project 1-9
|
|
120
129
|
for (let i = 1; i <= 9; i++) {
|
|
121
130
|
if (match(e, `switch-project-${i}`)) {
|