[{"data":1,"prerenderedAt":911},["ShallowReactive",2],{"blog-\u002Fpl\u002Fblog\u002F2026\u002Fjeden-modal-kilkanascie-kopii":3,"blog-project-\u002Fpl\u002Fblog\u002F2026\u002Fjeden-modal-kilkanascie-kopii":900},{"id":4,"title":5,"art":6,"body":7,"date":896,"description":897,"draft":898,"extension":899,"image":900,"kind":901,"meta":902,"navigation":90,"ogImage":903,"path":904,"project":900,"seo":905,"slug":900,"stem":906,"tags":907,"__hash__":910},"blog_pl\u002Fpl\u002Fblog\u002F2026\u002Fjeden-modal-kilkanascie-kopii.md","Jeden modal, kilkanaście kopii","\u002Fart\u002Fblog\u002Fjeden-modal.svg",{"type":8,"value":9,"toc":889},"minimark",[10,14,19,22,147,225,228,232,235,248,252,255,440,443,483,493,497,500,531,709,853,870,874,882,885],[11,12,13],"p",{},"Są fragmenty kodu, które nie wyglądają na problem, bo mają trzy linijki. Problem zaczyna się wtedy, gdy te same trzy linijki są w kilkunastu plikach. Trafiłem na taką sytuację w projekcie, przy którym pracowałem. Sytuacja jest prawdziwa, ale kod poniżej to przykład – nazwy i dane zmieniłem na panel sklepu, w którym kliknięcie w zdjęcie produktu w dowolnej tabeli otwiera okno z jego szczegółami.",[15,16,18],"h2",{"id":17},"jak-to-wyglądało","Jak to wyglądało",[11,20,21],{},"Tabel z produktami jest dużo: zamówienia, zwroty, magazyn, koszyki, historia. W każdym z tych widoków był ten sam kod:",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-js shiki shiki-themes github-light github-dark","const state = reactive({\n  showProductModal: false,\n  selectedProduct: null,\n})\n\nconst openProductModal = (product) => {\n  state.selectedProduct = product\n  state.showProductModal = true\n}\n","js","",[30,31,32,56,68,79,85,92,118,130,141],"code",{"__ignoreMap":28},[33,34,37,41,45,48,52],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","const",[33,42,44],{"class":43},"sj4cs"," state",[33,46,47],{"class":39}," =",[33,49,51],{"class":50},"sScJk"," reactive",[33,53,55],{"class":54},"sVt8B","({\n",[33,57,59,62,65],{"class":35,"line":58},2,[33,60,61],{"class":54},"  showProductModal: ",[33,63,64],{"class":43},"false",[33,66,67],{"class":54},",\n",[33,69,71,74,77],{"class":35,"line":70},3,[33,72,73],{"class":54},"  selectedProduct: ",[33,75,76],{"class":43},"null",[33,78,67],{"class":54},[33,80,82],{"class":35,"line":81},4,[33,83,84],{"class":54},"})\n",[33,86,88],{"class":35,"line":87},5,[33,89,91],{"emptyLinePlaceholder":90},true,"\n",[33,93,95,97,100,102,105,109,112,115],{"class":35,"line":94},6,[33,96,40],{"class":39},[33,98,99],{"class":50}," openProductModal",[33,101,47],{"class":39},[33,103,104],{"class":54}," (",[33,106,108],{"class":107},"s4XuR","product",[33,110,111],{"class":54},") ",[33,113,114],{"class":39},"=>",[33,116,117],{"class":54}," {\n",[33,119,121,124,127],{"class":35,"line":120},7,[33,122,123],{"class":54},"  state.selectedProduct ",[33,125,126],{"class":39},"=",[33,128,129],{"class":54}," product\n",[33,131,133,136,138],{"class":35,"line":132},8,[33,134,135],{"class":54},"  state.showProductModal ",[33,137,126],{"class":39},[33,139,140],{"class":43}," true\n",[33,142,144],{"class":35,"line":143},9,[33,145,146],{"class":54},"}\n",[23,148,152],{"className":149,"code":150,"language":151,"meta":28,"style":28},"language-vue shiki shiki-themes github-light github-dark","\u003CProductModal\n  :show=\"state.showProductModal\"\n  :product=\"state.selectedProduct\"\n  @close=\"state.showProductModal = false\"\n\u002F>\n","vue",[30,153,154,163,183,198,220],{"__ignoreMap":28},[33,155,156,159],{"class":35,"line":36},[33,157,158],{"class":54},"\u003C",[33,160,162],{"class":161},"s9eBZ","ProductModal\n",[33,164,165,168,171,173,177,180],{"class":35,"line":58},[33,166,167],{"class":54},"  :",[33,169,170],{"class":50},"show",[33,172,126],{"class":54},[33,174,176],{"class":175},"sZZnC","\"",[33,178,179],{"class":54},"state.showProductModal",[33,181,182],{"class":175},"\"\n",[33,184,185,187,189,191,193,196],{"class":35,"line":70},[33,186,167],{"class":54},[33,188,108],{"class":50},[33,190,126],{"class":54},[33,192,176],{"class":175},[33,194,195],{"class":54},"state.selectedProduct",[33,197,182],{"class":175},[33,199,200,203,206,208,210,213,215,218],{"class":35,"line":81},[33,201,202],{"class":54},"  @",[33,204,205],{"class":50},"close",[33,207,126],{"class":54},[33,209,176],{"class":175},[33,211,212],{"class":54},"state.showProductModal ",[33,214,126],{"class":39},[33,216,217],{"class":43}," false",[33,219,182],{"class":175},[33,221,222],{"class":35,"line":87},[33,223,224],{"class":54},"\u002F>\n",[11,226,227],{},"Nikt tego nie zaplanował. Pierwszy widok dostał okno, drugi był „taki sam, tylko z inną tabelą”, więc kod przeszedł kopiuj–wklej. Przy kolejnych widokach kopiowanie było już po prostu najszybszą drogą.",[15,229,231],{"id":230},"dlaczego-to-przeszkadza","Dlaczego to przeszkadza",[11,233,234],{},"Sam kod jest poprawny. Kłopot pojawia się przy pierwszej zmianie:",[236,237,238,242,245],"ul",{},[239,240,241],"li",{},"chcesz, żeby okno pobierało dodatkowe dane albo zamykało się po zmianie strony – zmieniasz kilkanaście plików,",[239,243,244],{},"jeden plik zawsze się pominie i w jednym widoku okno działa trochę inaczej niż w reszcie,",[239,246,247],{},"przy przeglądzie kodu nie widać, czy kopie są jeszcze takie same, czy już się rozjechały.",[15,249,251],{"id":250},"krok-1-composable","Krok 1: composable",[11,253,254],{},"Najpierw logikę otwierania i zamykania zbieramy w jednym miejscu:",[23,256,258],{"className":25,"code":257,"language":27,"meta":28,"style":28},"\u002F\u002F composables\u002FuseProductModal.js\nimport { ref } from 'vue'\n\nexport function useProductModal() {\n  const selectedProduct = ref(null)\n  const isOpen = ref(false)\n\n  const open = (product) => {\n    selectedProduct.value = product\n    isOpen.value = true\n  }\n\n  const close = () => {\n    isOpen.value = false\n  }\n\n  return { selectedProduct, isOpen, open, close }\n}\n",[30,259,260,266,280,284,298,319,336,340,359,368,378,384,389,406,416,421,426,435],{"__ignoreMap":28},[33,261,262],{"class":35,"line":36},[33,263,265],{"class":264},"sJ8bj","\u002F\u002F composables\u002FuseProductModal.js\n",[33,267,268,271,274,277],{"class":35,"line":58},[33,269,270],{"class":39},"import",[33,272,273],{"class":54}," { ref } ",[33,275,276],{"class":39},"from",[33,278,279],{"class":175}," 'vue'\n",[33,281,282],{"class":35,"line":70},[33,283,91],{"emptyLinePlaceholder":90},[33,285,286,289,292,295],{"class":35,"line":81},[33,287,288],{"class":39},"export",[33,290,291],{"class":39}," function",[33,293,294],{"class":50}," useProductModal",[33,296,297],{"class":54},"() {\n",[33,299,300,303,306,308,311,314,316],{"class":35,"line":87},[33,301,302],{"class":39},"  const",[33,304,305],{"class":43}," selectedProduct",[33,307,47],{"class":39},[33,309,310],{"class":50}," ref",[33,312,313],{"class":54},"(",[33,315,76],{"class":43},[33,317,318],{"class":54},")\n",[33,320,321,323,326,328,330,332,334],{"class":35,"line":94},[33,322,302],{"class":39},[33,324,325],{"class":43}," isOpen",[33,327,47],{"class":39},[33,329,310],{"class":50},[33,331,313],{"class":54},[33,333,64],{"class":43},[33,335,318],{"class":54},[33,337,338],{"class":35,"line":120},[33,339,91],{"emptyLinePlaceholder":90},[33,341,342,344,347,349,351,353,355,357],{"class":35,"line":132},[33,343,302],{"class":39},[33,345,346],{"class":50}," open",[33,348,47],{"class":39},[33,350,104],{"class":54},[33,352,108],{"class":107},[33,354,111],{"class":54},[33,356,114],{"class":39},[33,358,117],{"class":54},[33,360,361,364,366],{"class":35,"line":143},[33,362,363],{"class":54},"    selectedProduct.value ",[33,365,126],{"class":39},[33,367,129],{"class":54},[33,369,371,374,376],{"class":35,"line":370},10,[33,372,373],{"class":54},"    isOpen.value ",[33,375,126],{"class":39},[33,377,140],{"class":43},[33,379,381],{"class":35,"line":380},11,[33,382,383],{"class":54},"  }\n",[33,385,387],{"class":35,"line":386},12,[33,388,91],{"emptyLinePlaceholder":90},[33,390,392,394,397,399,402,404],{"class":35,"line":391},13,[33,393,302],{"class":39},[33,395,396],{"class":50}," close",[33,398,47],{"class":39},[33,400,401],{"class":54}," () ",[33,403,114],{"class":39},[33,405,117],{"class":54},[33,407,409,411,413],{"class":35,"line":408},14,[33,410,373],{"class":54},[33,412,126],{"class":39},[33,414,415],{"class":43}," false\n",[33,417,419],{"class":35,"line":418},15,[33,420,383],{"class":54},[33,422,424],{"class":35,"line":423},16,[33,425,91],{"emptyLinePlaceholder":90},[33,427,429,432],{"class":35,"line":428},17,[33,430,431],{"class":39},"  return",[33,433,434],{"class":54}," { selectedProduct, isOpen, open, close }\n",[33,436,438],{"class":35,"line":437},18,[33,439,146],{"class":54},[11,441,442],{},"W widoku zostaje jedna linijka zamiast stanu i funkcji:",[23,444,446],{"className":25,"code":445,"language":27,"meta":28,"style":28},"const { selectedProduct, isOpen, open, close } = useProductModal()\n",[30,447,448],{"__ignoreMap":28},[33,449,450,452,455,458,461,464,466,469,471,473,476,478,480],{"class":35,"line":36},[33,451,40],{"class":39},[33,453,454],{"class":54}," { ",[33,456,457],{"class":43},"selectedProduct",[33,459,460],{"class":54},", ",[33,462,463],{"class":43},"isOpen",[33,465,460],{"class":54},[33,467,468],{"class":43},"open",[33,470,460],{"class":54},[33,472,205],{"class":43},[33,474,475],{"class":54}," } ",[33,477,126],{"class":39},[33,479,294],{"class":50},[33,481,482],{"class":54},"()\n",[11,484,485,486,489,490,492],{},"To już pomaga – zmiana zachowania okna to zmiana w jednym pliku. Ale w każdym widoku dalej jest ",[30,487,488],{},"\u003CProductModal>"," w szablonie i przekazywanie funkcji ",[30,491,468],{}," do tabeli.",[15,494,496],{"id":495},"krok-2-jedno-okno-i-provideinject","Krok 2: jedno okno i provide\u002Finject",[11,498,499],{},"Skoro okno wygląda wszędzie tak samo, może być jedno – w komponencie nadrzędnym, np. w layoucie panelu. Layout udostępnia funkcję otwierania, a tabele same po nią sięgają:",[23,501,503],{"className":25,"code":502,"language":27,"meta":28,"style":28},"\u002F\u002F composables\u002FuseProductModal.js\nexport const productModalKey = Symbol('productModal')\n",[30,504,505,509],{"__ignoreMap":28},[33,506,507],{"class":35,"line":36},[33,508,265],{"class":264},[33,510,511,513,516,519,521,524,526,529],{"class":35,"line":58},[33,512,288],{"class":39},[33,514,515],{"class":39}," const",[33,517,518],{"class":43}," productModalKey",[33,520,47],{"class":39},[33,522,523],{"class":50}," Symbol",[33,525,313],{"class":54},[33,527,528],{"class":175},"'productModal'",[33,530,318],{"class":54},[23,532,534],{"className":149,"code":533,"language":151,"meta":28,"style":28},"\u003C!-- AdminLayout.vue -->\n\u003Cscript setup>\nimport { provide } from 'vue'\nimport { useProductModal, productModalKey } from '@\u002Fcomposables\u002FuseProductModal'\n\nconst { selectedProduct, isOpen, open, close } = useProductModal()\nprovide(productModalKey, open)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cslot \u002F>\n  \u003CProductModal :show=\"isOpen\" :product=\"selectedProduct\" @close=\"close\" \u002F>\n\u003C\u002Ftemplate>\n",[30,535,536,541,554,565,577,581,609,617,626,630,639,653,701],{"__ignoreMap":28},[33,537,538],{"class":35,"line":36},[33,539,540],{"class":264},"\u003C!-- AdminLayout.vue -->\n",[33,542,543,545,548,551],{"class":35,"line":58},[33,544,158],{"class":54},[33,546,547],{"class":161},"script",[33,549,550],{"class":50}," setup",[33,552,553],{"class":54},">\n",[33,555,556,558,561,563],{"class":35,"line":70},[33,557,270],{"class":39},[33,559,560],{"class":54}," { provide } ",[33,562,276],{"class":39},[33,564,279],{"class":175},[33,566,567,569,572,574],{"class":35,"line":81},[33,568,270],{"class":39},[33,570,571],{"class":54}," { useProductModal, productModalKey } ",[33,573,276],{"class":39},[33,575,576],{"class":175}," '@\u002Fcomposables\u002FuseProductModal'\n",[33,578,579],{"class":35,"line":87},[33,580,91],{"emptyLinePlaceholder":90},[33,582,583,585,587,589,591,593,595,597,599,601,603,605,607],{"class":35,"line":94},[33,584,40],{"class":39},[33,586,454],{"class":54},[33,588,457],{"class":43},[33,590,460],{"class":54},[33,592,463],{"class":43},[33,594,460],{"class":54},[33,596,468],{"class":43},[33,598,460],{"class":54},[33,600,205],{"class":43},[33,602,475],{"class":54},[33,604,126],{"class":39},[33,606,294],{"class":50},[33,608,482],{"class":54},[33,610,611,614],{"class":35,"line":120},[33,612,613],{"class":50},"provide",[33,615,616],{"class":54},"(productModalKey, open)\n",[33,618,619,622,624],{"class":35,"line":132},[33,620,621],{"class":54},"\u003C\u002F",[33,623,547],{"class":161},[33,625,553],{"class":54},[33,627,628],{"class":35,"line":143},[33,629,91],{"emptyLinePlaceholder":90},[33,631,632,634,637],{"class":35,"line":370},[33,633,158],{"class":54},[33,635,636],{"class":161},"template",[33,638,553],{"class":54},[33,640,641,644,647,651],{"class":35,"line":380},[33,642,643],{"class":54},"  \u003C",[33,645,646],{"class":161},"slot",[33,648,650],{"class":649},"s7hpK"," \u002F",[33,652,553],{"class":54},[33,654,655,657,660,663,665,667,669,671,673,675,677,679,681,683,685,688,690,692,694,696,698],{"class":35,"line":386},[33,656,643],{"class":54},[33,658,659],{"class":161},"ProductModal",[33,661,662],{"class":54}," :",[33,664,170],{"class":50},[33,666,126],{"class":54},[33,668,176],{"class":175},[33,670,463],{"class":54},[33,672,176],{"class":175},[33,674,662],{"class":54},[33,676,108],{"class":50},[33,678,126],{"class":54},[33,680,176],{"class":175},[33,682,457],{"class":54},[33,684,176],{"class":175},[33,686,687],{"class":54}," @",[33,689,205],{"class":50},[33,691,126],{"class":54},[33,693,176],{"class":175},[33,695,205],{"class":54},[33,697,176],{"class":175},[33,699,700],{"class":54}," \u002F>\n",[33,702,703,705,707],{"class":35,"line":391},[33,704,621],{"class":54},[33,706,636],{"class":161},[33,708,553],{"class":54},[23,710,712],{"className":149,"code":711,"language":151,"meta":28,"style":28},"\u003C!-- ProductsTable.vue -->\n\u003Cscript setup>\nimport { inject } from 'vue'\nimport { productModalKey } from '@\u002Fcomposables\u002FuseProductModal'\n\nconst openProductModal = inject(productModalKey, null)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cimg\n    :src=\"product.image\"\n    :alt=\"product.name\"\n    :class=\"{ 'cursor-pointer': openProductModal }\"\n    @click=\"openProductModal?.(product)\"\n  \u002F>\n\u003C\u002Ftemplate>\n",[30,713,714,719,729,740,751,755,773,781,785,793,800,810,820,830,840,845],{"__ignoreMap":28},[33,715,716],{"class":35,"line":36},[33,717,718],{"class":264},"\u003C!-- ProductsTable.vue -->\n",[33,720,721,723,725,727],{"class":35,"line":58},[33,722,158],{"class":54},[33,724,547],{"class":161},[33,726,550],{"class":50},[33,728,553],{"class":54},[33,730,731,733,736,738],{"class":35,"line":70},[33,732,270],{"class":39},[33,734,735],{"class":54}," { inject } ",[33,737,276],{"class":39},[33,739,279],{"class":175},[33,741,742,744,747,749],{"class":35,"line":81},[33,743,270],{"class":39},[33,745,746],{"class":54}," { productModalKey } ",[33,748,276],{"class":39},[33,750,576],{"class":175},[33,752,753],{"class":35,"line":87},[33,754,91],{"emptyLinePlaceholder":90},[33,756,757,759,761,763,766,769,771],{"class":35,"line":94},[33,758,40],{"class":39},[33,760,99],{"class":43},[33,762,47],{"class":39},[33,764,765],{"class":50}," inject",[33,767,768],{"class":54},"(productModalKey, ",[33,770,76],{"class":43},[33,772,318],{"class":54},[33,774,775,777,779],{"class":35,"line":120},[33,776,621],{"class":54},[33,778,547],{"class":161},[33,780,553],{"class":54},[33,782,783],{"class":35,"line":132},[33,784,91],{"emptyLinePlaceholder":90},[33,786,787,789,791],{"class":35,"line":143},[33,788,158],{"class":54},[33,790,636],{"class":161},[33,792,553],{"class":54},[33,794,795,797],{"class":35,"line":370},[33,796,643],{"class":54},[33,798,799],{"class":161},"img\n",[33,801,802,805,807],{"class":35,"line":380},[33,803,804],{"class":50},"    :src",[33,806,126],{"class":54},[33,808,809],{"class":175},"\"product.image\"\n",[33,811,812,815,817],{"class":35,"line":386},[33,813,814],{"class":50},"    :alt",[33,816,126],{"class":54},[33,818,819],{"class":175},"\"product.name\"\n",[33,821,822,825,827],{"class":35,"line":391},[33,823,824],{"class":50},"    :class",[33,826,126],{"class":54},[33,828,829],{"class":175},"\"{ 'cursor-pointer': openProductModal }\"\n",[33,831,832,835,837],{"class":35,"line":408},[33,833,834],{"class":50},"    @click",[33,836,126],{"class":54},[33,838,839],{"class":175},"\"openProductModal?.(product)\"\n",[33,841,842],{"class":35,"line":418},[33,843,844],{"class":54},"  \u002F>\n",[33,846,847,849,851],{"class":35,"line":423},[33,848,621],{"class":54},[33,850,636],{"class":161},[33,852,553],{"class":54},[11,854,855,856,104,859,861,862,865,866,869],{},"Widoki nie muszą już wiedzieć o oknie. Tabela pyta tylko: „czy ktoś nade mną umie otworzyć szczegóły produktu?”. Drugi argument ",[30,857,858],{},"inject",[30,860,76],{},") i ",[30,863,864],{},"?.()"," sprawiają, że ta sama tabela użyta poza layoutem po prostu nie otwiera okna, zamiast rzucać błąd. Klucz jako ",[30,867,868],{},"Symbol"," z jednego pliku chroni przed literówką w nazwie, która przy zwykłym stringu cicho by nie zadziałała.",[15,871,873],{"id":872},"kiedy-kopiowanie-jest-w-porządku","Kiedy kopiowanie jest w porządku",[11,875,876,877,881],{},"Nie każde powtórzenie trzeba od razu wyciągać do osobnego pliku. Pomaga znana zasada trzech (",[878,879,880],"em",{},"rule of three","): dwie kopie można zostawić, a przy trzeciej warto się zatrzymać i sprawdzić, czy to na pewno ten sam kod. Czasem dwa fragmenty wyglądają tak samo, ale służą czemuś innemu i za chwilę i tak się rozjadą – wtedy wspólna funkcja tylko by przeszkadzała.",[11,883,884],{},"Najprostszy nawyk, który pomaga najbardziej: zanim napiszesz funkcję, wyszukaj jej nazwę w projekcie. Jeśli znajdziesz ją w trzech plikach, to znak, że czwarta kopia nie jest potrzebna.",[886,887,888],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":28,"searchDepth":58,"depth":58,"links":890},[891,892,893,894,895],{"id":17,"depth":58,"text":18},{"id":230,"depth":58,"text":231},{"id":250,"depth":58,"text":251},{"id":495,"depth":58,"text":496},{"id":872,"depth":58,"text":873},"2026-03-02","Ta sama funkcja do otwierania okna skopiowana do kilkunastu widoków – jak zebrać ją w jeden composable i udostępnić tabelom przez provide\u002Finject.",false,"md",null,"post",{},"\u002Fog\u002Fblog\u002Fjeden-modal-pl.jpg","\u002Fpl\u002Fblog\u002F2026\u002Fjeden-modal-kilkanascie-kopii",{"title":5,"description":897},"pl\u002Fblog\u002F2026\u002Fjeden-modal-kilkanascie-kopii",[151,908,909],"javascript","composables","Wy3CikKvwz2DVJmVoszNutrtKKlJALcl4S56sqentrA",1792087426368]