base.html 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790
  1. {% load i18n static tz core_tags %}
  2. {% get_current_language as LANGUAGE_CODE %}
  3. {% get_current_language_bidi as LANGUAGE_BIDI %}
  4. <!DOCTYPE html>
  5. <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
  6. <head>
  7. <title>{% block title %}Home{% endblock %} | ArchiveBox</title>
  8. {% block blockbots %}
  9. <meta name="robots" content="NONE,NOARCHIVE">
  10. {% endblock %}
  11. <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
  12. {% api_token as api_token %}
  13. <script>
  14. window.ARCHIVEBOX_API_KEY = "{{ api_token|escapejs }}";
  15. </script>
  16. {% block extrastyle %}
  17. <style>
  18. #upgrade-banner {
  19. position: fixed;
  20. right: 20px;
  21. bottom: 20px;
  22. background-color: #f8f8f8;
  23. color: #333333;
  24. border: 2px solid #772948;
  25. padding: 10px 20px;
  26. z-index: 1000;
  27. text-align: center;
  28. }
  29. #dismiss-btn {
  30. background: #aa1e55;
  31. color: white;
  32. cursor: pointer;
  33. }
  34. /* ============================================
  35. Modern card-based admin UI (shadcn-inspired)
  36. ============================================ */
  37. /* Base font improvements */
  38. body, html {
  39. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  40. -webkit-font-smoothing: antialiased;
  41. -moz-osx-font-smoothing: grayscale;
  42. font-size: 15px;
  43. line-height: 1.6;
  44. color: #0f172a;
  45. background: #f8fafc;
  46. }
  47. #container {
  48. background: #f8fafc;
  49. }
  50. #content {
  51. padding: 24px;
  52. }
  53. /* Main form container - flexbox grid */
  54. body:not(.change-list) #content-main form > div,
  55. body:not(.change-list) #content form > div {
  56. display: flex;
  57. flex-wrap: wrap;
  58. gap: 20px;
  59. align-items: stretch;
  60. }
  61. /* Each fieldset becomes a card */
  62. #content-main form fieldset,
  63. #content form fieldset,
  64. #content-main form .module:not(.inline-group),
  65. #content form .module:not(.inline-group) {
  66. background: #fff !important;
  67. border: 1px solid #e2e8f0 !important;
  68. border-top: 1px solid #e2e8f0 !important;
  69. border-left: 1px solid #e2e8f0 !important;
  70. border-right: 1px solid #e2e8f0 !important;
  71. border-bottom: 1px solid #e2e8f0 !important;
  72. border-radius: 12px !important;
  73. padding: 0 !important;
  74. margin: 0 !important;
  75. box-shadow: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.06);
  76. flex: 1 1 340px;
  77. min-width: 320px;
  78. max-width: calc(33.33% - 14px);
  79. box-sizing: border-box;
  80. display: flex;
  81. flex-direction: column;
  82. transition: box-shadow 0.2s ease, border-color 0.2s ease;
  83. overflow: hidden;
  84. }
  85. /* Wide fieldsets MUST override card max-width - placed after card rules for specificity */
  86. #content-main form fieldset.wide,
  87. #content form fieldset.wide,
  88. #content-main form fieldset:has(.field-archiveresults_list),
  89. #content form fieldset:has(.field-archiveresults_list),
  90. #content-main form fieldset:has(.field-snapshots),
  91. #content form fieldset:has(.field-snapshots) {
  92. flex: 1 1 100% !important;
  93. max-width: 100% !important;
  94. min-width: 100% !important;
  95. width: 100% !important;
  96. flex-basis: 100% !important;
  97. }
  98. /* Inline groups should NOT have card constraints */
  99. #content-main form .inline-group,
  100. #content form .inline-group,
  101. .inline-group fieldset,
  102. .inline-group .module {
  103. flex: 1 1 100% !important;
  104. max-width: 100% !important;
  105. min-width: 100% !important;
  106. width: 100% !important;
  107. }
  108. #content-main form fieldset:hover,
  109. #content form fieldset:hover {
  110. box-shadow: 0 4px 6px rgba(0,0,0,0.05), 0 2px 4px rgba(0,0,0,0.06);
  111. border-color: #cbd5e1;
  112. }
  113. /* Archive results list content should take full width */
  114. .field-archiveresults_list,
  115. .field-archiveresults_list .readonly,
  116. .field-snapshots,
  117. .field-snapshots .readonly {
  118. width: 100% !important;
  119. max-width: 100% !important;
  120. background: transparent !important;
  121. border: none !important;
  122. padding: 0 !important;
  123. }
  124. /* Card headers - no borders, just background */
  125. #content-main form fieldset h2,
  126. #content form fieldset h2,
  127. #content-main form .module h2,
  128. #content form .module h2 {
  129. margin: 0 !important;
  130. padding: 8px 16px !important;
  131. background: #f1f5f9 !important;
  132. color: #334155 !important;
  133. font-size: 12px !important;
  134. font-weight: 600 !important;
  135. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
  136. border: none !important;
  137. border-top: none !important;
  138. border-left: none !important;
  139. border-right: none !important;
  140. border-bottom: none !important;
  141. border-radius: 0 !important;
  142. text-transform: uppercase;
  143. letter-spacing: 0.5px;
  144. flex-shrink: 0;
  145. -webkit-font-smoothing: antialiased;
  146. box-shadow: none !important;
  147. outline: none !important;
  148. }
  149. /* Collapse toggle styling */
  150. #content-main form fieldset h2 a.collapse-toggle,
  151. #content form fieldset h2 a.collapse-toggle {
  152. color: #64748b;
  153. }
  154. /* Card content area */
  155. #content-main form fieldset > div,
  156. #content form fieldset > div {
  157. padding: 20px;
  158. flex: 1;
  159. overflow-x: hidden;
  160. overflow-y: visible;
  161. min-width: 0;
  162. }
  163. /* Form rows inside cards */
  164. #content-main form fieldset .form-row,
  165. #content form fieldset .form-row {
  166. padding: 8px 0;
  167. border-bottom: 1px solid #f1f5f9;
  168. min-width: 0;
  169. min-height: auto;
  170. }
  171. #content-main form fieldset .form-row:first-child,
  172. #content form fieldset .form-row:first-child {
  173. padding-top: 0;
  174. }
  175. #content-main form fieldset .form-row:last-child,
  176. #content form fieldset .form-row:last-child {
  177. border-bottom: none;
  178. padding-bottom: 0;
  179. }
  180. /* Remove borders from nested fieldsets and flex-containers inside cards */
  181. #content-main form fieldset fieldset,
  182. #content form fieldset fieldset,
  183. #content-main form fieldset .flex-container,
  184. #content form fieldset .flex-container,
  185. #content-main form .module fieldset,
  186. #content form .module fieldset {
  187. background: transparent !important;
  188. border: none !important;
  189. border-radius: 0 !important;
  190. box-shadow: none !important;
  191. padding: 0 !important;
  192. margin: 0 !important;
  193. min-width: 0 !important;
  194. max-width: 94% !important;
  195. flex: none !important;
  196. display: block !important;
  197. }
  198. /* Nested fieldset headers should be invisible */
  199. #content-main form fieldset fieldset h2,
  200. #content form fieldset fieldset h2,
  201. #content-main form fieldset .flex-container legend,
  202. #content form fieldset .flex-container legend {
  203. background: transparent !important;
  204. padding: 0 0 4px 0 !important;
  205. font-size: 13px !important;
  206. color: #374151 !important;
  207. text-transform: none !important;
  208. letter-spacing: normal !important;
  209. }
  210. /* Ensure form elements inside cards don't overflow */
  211. #content-main form fieldset input,
  212. #content-main form fieldset select,
  213. #content-main form fieldset textarea,
  214. #content form fieldset input,
  215. #content form fieldset select,
  216. #content form fieldset textarea {
  217. max-width: 100%;
  218. box-sizing: border-box;
  219. }
  220. /* Related widget wrapper should fit within card */
  221. #content-main form fieldset .related-widget-wrapper,
  222. #content form fieldset .related-widget-wrapper {
  223. max-width: 100%;
  224. }
  225. #content-main form fieldset .related-widget-wrapper select,
  226. #content form fieldset .related-widget-wrapper select {
  227. min-width: 0;
  228. flex: 1;
  229. }
  230. /* Labels inside cards */
  231. #content-main form fieldset .form-row > label,
  232. #content form fieldset .form-row > label,
  233. #content-main form fieldset .form-row > .flex-container > label,
  234. #content form fieldset .form-row > .flex-container > label,
  235. #content-main form label,
  236. #content form label,
  237. .aligned label,
  238. legend {
  239. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  240. font-weight: 500;
  241. color: #374151;
  242. display: block;
  243. margin-bottom: 8px;
  244. float: none !important;
  245. width: auto !important;
  246. padding: 0 !important;
  247. font-size: 13px;
  248. letter-spacing: -0.01em;
  249. -webkit-font-smoothing: antialiased;
  250. -moz-osx-font-smoothing: grayscale;
  251. }
  252. /* Readonly fields styling */
  253. #content-main form fieldset .readonly,
  254. #content form fieldset .readonly {
  255. background: #f8fafc;
  256. padding: 12px 14px;
  257. border-radius: 8px;
  258. font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
  259. font-size: 13px;
  260. word-break: break-word;
  261. line-height: 1.6;
  262. border: 1px solid #e2e8f0;
  263. color: #475569;
  264. }
  265. /* Long content in readonly */
  266. #content-main form fieldset .readonly pre,
  267. #content form fieldset .readonly pre {
  268. margin: 0;
  269. white-space: pre-wrap;
  270. word-break: break-word;
  271. font-family: inherit;
  272. }
  273. /* Input styling */
  274. #content-main form input[type="text"],
  275. #content-main form input[type="number"],
  276. #content-main form input[type="url"],
  277. #content-main form input[type="email"],
  278. #content-main form input[type="password"],
  279. #content form input[type="text"],
  280. #content form input[type="number"],
  281. #content form input[type="url"],
  282. #content form input[type="email"],
  283. #content form input[type="password"] {
  284. width: 100%;
  285. padding: 10px 14px;
  286. border: 1px solid #d1d5db;
  287. border-radius: 8px;
  288. font-size: 14px;
  289. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  290. box-sizing: border-box;
  291. background: #fff;
  292. color: #1e293b;
  293. transition: border-color 0.15s ease, box-shadow 0.15s ease;
  294. -webkit-font-smoothing: antialiased;
  295. }
  296. #content-main form select,
  297. #content form select {
  298. width: 100%;
  299. border: 1px solid #d1d5db;
  300. border-radius: 8px;
  301. font-size: 14px;
  302. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  303. box-sizing: border-box;
  304. background: #fff;
  305. color: #1e293b;
  306. transition: border-color 0.15s ease, box-shadow 0.15s ease;
  307. -webkit-font-smoothing: antialiased;
  308. }
  309. #content-main form input::placeholder,
  310. #content form input::placeholder {
  311. color: #94a3b8;
  312. }
  313. /* Focus states */
  314. #content-main form input:focus,
  315. #content-main form select:focus,
  316. #content-main form textarea:focus,
  317. #content form input:focus,
  318. #content form select:focus,
  319. #content form textarea:focus {
  320. border-color: #3b82f6;
  321. outline: none;
  322. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
  323. }
  324. /* Textarea styling */
  325. #content-main form textarea,
  326. #content form textarea {
  327. width: 100%;
  328. box-sizing: border-box;
  329. border: 1px solid #d1d5db;
  330. border-radius: 8px;
  331. padding: 12px 14px;
  332. font-size: 14px;
  333. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  334. line-height: 1.6;
  335. resize: vertical;
  336. min-height: 80px;
  337. color: #1e293b;
  338. transition: border-color 0.15s ease, box-shadow 0.15s ease;
  339. -webkit-font-smoothing: antialiased;
  340. }
  341. /* Fix vTextField width */
  342. .vTextField {
  343. width: 100% !important;
  344. }
  345. /* ============================================
  346. Button styling (shadcn-inspired)
  347. ============================================ */
  348. /* Base button styles */
  349. input[type="submit"],
  350. button,
  351. .button,
  352. .btn,
  353. a.button,
  354. .submit-row input,
  355. .submit-row a.button {
  356. display: inline-flex;
  357. align-items: center;
  358. justify-content: center;
  359. gap: 8px;
  360. padding: 10px 18px;
  361. font-size: 14px;
  362. font-weight: 500;
  363. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  364. line-height: 1.4;
  365. border-radius: 8px;
  366. border: 1px solid transparent;
  367. cursor: pointer;
  368. transition: all 0.15s ease;
  369. text-decoration: none;
  370. white-space: nowrap;
  371. -webkit-font-smoothing: antialiased;
  372. }
  373. /* Primary button (default) */
  374. input[type="submit"],
  375. button[type="submit"],
  376. .button.default,
  377. .submit-row input[type="submit"] {
  378. background: #0f172a;
  379. color: #fff;
  380. border-color: #0f172a;
  381. }
  382. input[type="submit"]:hover,
  383. button[type="submit"]:hover,
  384. .button.default:hover,
  385. .submit-row input[type="submit"]:hover {
  386. background: #1e293b;
  387. border-color: #1e293b;
  388. }
  389. input[type="submit"]:active,
  390. button[type="submit"]:active {
  391. background: #334155;
  392. transform: translateY(1px);
  393. }
  394. /* Secondary/outline buttons */
  395. button:not([type="submit"]),
  396. .button:not(.default),
  397. a.button {
  398. background: #fff;
  399. color: #374151;
  400. border-color: #d1d5db;
  401. }
  402. button:not([type="submit"]):hover,
  403. .button:not(.default):hover,
  404. a.button:hover {
  405. background: #f9fafb;
  406. border-color: #9ca3af;
  407. color: #1f2937;
  408. }
  409. /* Danger button */
  410. .deletelink,
  411. a.deletelink,
  412. button.deletelink,
  413. input[name="delete"],
  414. .button.delete {
  415. background: #fff;
  416. color: #dc2626;
  417. border-color: #fecaca;
  418. }
  419. .deletelink:hover,
  420. a.deletelink:hover,
  421. button.deletelink:hover,
  422. input[name="delete"]:hover,
  423. .button.delete:hover {
  424. background: #fef2f2;
  425. border-color: #f87171;
  426. color: #b91c1c;
  427. }
  428. /* Small buttons */
  429. .btn-sm,
  430. .object-tools a,
  431. .datetimeshortcuts a {
  432. padding: 6px 12px;
  433. font-size: 13px;
  434. border-radius: 6px;
  435. }
  436. /* Object tools (top action buttons) */
  437. .object-tools {
  438. margin-bottom: 20px;
  439. }
  440. .object-tools li {
  441. margin-left: 10px;
  442. }
  443. .object-tools a {
  444. background: #fff;
  445. color: #374151;
  446. border: 1px solid #d1d5db;
  447. text-decoration: none;
  448. display: inline-flex;
  449. align-items: center;
  450. }
  451. .object-tools a:hover {
  452. background: #f9fafb;
  453. border-color: #9ca3af;
  454. }
  455. /* Submit row styling */
  456. .submit-row {
  457. margin-top: 24px;
  458. padding: 20px;
  459. background: #fff;
  460. border-radius: 12px;
  461. border: 1px solid #e2e8f0;
  462. box-shadow: 0 1px 3px rgba(0,0,0,0.04);
  463. clear: both;
  464. flex: 1 1 100%;
  465. display: flex;
  466. gap: 12px;
  467. flex-wrap: wrap;
  468. align-items: center;
  469. }
  470. .submit-row p {
  471. margin: 0;
  472. }
  473. .submit-row .deletelink-box {
  474. margin-left: auto;
  475. }
  476. /* Responsive: 2 columns on medium screens */
  477. @media (max-width: 1400px) {
  478. #content-main form fieldset,
  479. #content form fieldset {
  480. max-width: calc(50% - 10px);
  481. flex: 1 1 320px;
  482. }
  483. }
  484. /* Responsive: stack on smaller screens */
  485. @media (max-width: 900px) {
  486. #content-main form fieldset,
  487. #content form fieldset {
  488. flex: 1 1 100%;
  489. max-width: 100%;
  490. min-width: auto;
  491. }
  492. #content {
  493. padding: 16px;
  494. }
  495. }
  496. /* Module content padding */
  497. #content-main form .module > div,
  498. #content form .module > div {
  499. padding: 12px;
  500. }
  501. /* Fix for JSON/config editor */
  502. .field-config .readonly,
  503. .field-config textarea {
  504. width: 100%;
  505. min-height: 120px;
  506. max-height: none;
  507. }
  508. /* Related widget styling */
  509. .related-widget-wrapper {
  510. display: flex;
  511. align-items: center;
  512. gap: 8px;
  513. flex-wrap: wrap;
  514. }
  515. .related-widget-wrapper select {
  516. flex: 1;
  517. min-width: 150px;
  518. }
  519. .related-widget-wrapper a {
  520. flex-shrink: 0;
  521. padding: 8px;
  522. border-radius: 6px;
  523. color: #64748b;
  524. transition: color 0.15s ease, background 0.15s ease;
  525. }
  526. .related-widget-wrapper a:hover {
  527. color: #1e293b;
  528. background: #f1f5f9;
  529. }
  530. /* Help text styling */
  531. .help {
  532. font-size: 13px;
  533. color: #64748b;
  534. margin-top: 6px;
  535. line-height: 1.5;
  536. }
  537. /* Error styling */
  538. .errorlist {
  539. color: #dc2626;
  540. font-size: 13px;
  541. margin: 6px 0;
  542. padding: 0;
  543. list-style: none;
  544. }
  545. .errorlist li {
  546. background: #fef2f2;
  547. padding: 8px 12px;
  548. border-radius: 6px;
  549. border: 1px solid #fecaca;
  550. }
  551. /* Inline related objects - force full width */
  552. .inline-group,
  553. #archiveresult_set-group,
  554. #content-main form .inline-group,
  555. #content-main form > div > .inline-group,
  556. #content form > div > .inline-group,
  557. .change-form .inline-group,
  558. div.inline-group {
  559. flex: 1 1 100% !important;
  560. max-width: 100% !important;
  561. min-width: 100% !important;
  562. width: 100% !important;
  563. margin-top: 20px;
  564. flex-basis: 100% !important;
  565. }
  566. /* Ensure inline-group breaks out of card grid */
  567. #content-main form > div,
  568. #content form > div {
  569. flex-wrap: wrap;
  570. }
  571. /* TabularInline table full width */
  572. .inline-group .tabular,
  573. .inline-group table {
  574. width: 100% !important;
  575. }
  576. .inline-related {
  577. margin: 12px 0;
  578. padding: 16px;
  579. background: #fff;
  580. border-radius: 10px;
  581. border: 1px solid #e2e8f0;
  582. }
  583. .inline-related h3 {
  584. margin: -16px -16px 16px -16px;
  585. padding: 12px 16px;
  586. background: #f8fafc;
  587. border-radius: 9px 9px 0 0;
  588. border-bottom: 1px solid #e2e8f0;
  589. font-size: 13px;
  590. font-weight: 600;
  591. color: #374151;
  592. }
  593. /* Tabular inline styling */
  594. .tabular {
  595. border-radius: 8px;
  596. overflow: hidden;
  597. border: 1px solid #e2e8f0;
  598. }
  599. .tabular td, .tabular th {
  600. padding: 12px 14px;
  601. font-size: 13px;
  602. border-bottom: 1px solid #f1f5f9;
  603. }
  604. .tabular th {
  605. background: #f8fafc;
  606. font-weight: 600;
  607. color: #374151;
  608. text-align: left;
  609. }
  610. .tabular tr:last-child td {
  611. border-bottom: none;
  612. }
  613. /* Delete checkbox */
  614. .inline-deletelink {
  615. color: #dc2626;
  616. font-size: 13px;
  617. }
  618. /* Datetime widgets */
  619. .datetimeshortcuts {
  620. margin-left: 10px;
  621. }
  622. .datetimeshortcuts a {
  623. background: #f1f5f9;
  624. color: #475569;
  625. border: none;
  626. padding: 4px 10px;
  627. }
  628. .datetimeshortcuts a:hover {
  629. background: #e2e8f0;
  630. color: #1e293b;
  631. }
  632. /* Aligned forms - fix label positioning */
  633. .aligned .form-row > div {
  634. margin-left: 0 !important;
  635. }
  636. /* Checkbox styling */
  637. input[type="checkbox"] {
  638. width: 18px;
  639. height: 18px;
  640. border-radius: 4px;
  641. border: 1px solid #d1d5db;
  642. cursor: pointer;
  643. accent-color: #3b82f6;
  644. }
  645. /* Links styling */
  646. a {
  647. color: #2563eb;
  648. text-decoration: none;
  649. transition: color 0.15s ease;
  650. }
  651. a:hover {
  652. color: #1d4ed8;
  653. }
  654. /* Messages/alerts */
  655. .messagelist {
  656. padding: 0;
  657. margin: 0 0 20px 0;
  658. }
  659. .messagelist li {
  660. padding: 14px 18px;
  661. border-radius: 10px;
  662. font-size: 14px;
  663. margin-bottom: 10px;
  664. display: flex;
  665. align-items: center;
  666. gap: 10px;
  667. }
  668. ul.messagelist li.success {
  669. background: #f0fdf4 !important;
  670. background-image: none !important;
  671. border: 1px solid #bbf7d0;
  672. color: #166534;
  673. }
  674. .messagelist li.warning {
  675. background: #fffbeb !important;
  676. background-image: none !important;
  677. border: 1px solid #fde68a;
  678. color: #92400e;
  679. }
  680. .messagelist li.error {
  681. background: #fef2f2 !important;
  682. background-image: none !important;
  683. border: 1px solid #fecaca;
  684. color: #991b1b;
  685. }
  686. /* Breadcrumbs */
  687. .breadcrumbs {
  688. background: transparent;
  689. padding: 12px 24px;
  690. font-size: 13px;
  691. color: #64748b;
  692. }
  693. .breadcrumbs a {
  694. color: #64748b;
  695. }
  696. .breadcrumbs a:hover {
  697. color: #1e293b;
  698. }
  699. /* Action buttons in cards */
  700. .card .btn,
  701. .card button {
  702. margin-top: 10px;
  703. }
  704. /* Select2 overrides */
  705. .select2-container--default .select2-selection--single,
  706. .select2-container--default .select2-selection--multiple {
  707. border: 1px solid #d1d5db;
  708. border-radius: 8px;
  709. min-height: 42px;
  710. }
  711. .select2-container--default .select2-selection--single:focus,
  712. .select2-container--default .select2-selection--multiple:focus {
  713. border-color: #3b82f6;
  714. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
  715. }
  716. /* ============================================
  717. Admin List/Changelist Page Styling
  718. ============================================ */
  719. /* Results table container */
  720. #changelist {
  721. background: #fff;
  722. border-radius: 12px;
  723. border: 1px solid #e2e8f0;
  724. box-shadow: 0 1px 3px rgba(0,0,0,0.04);
  725. overflow: hidden;
  726. }
  727. /* Table styling */
  728. #result_list {
  729. width: 100%;
  730. border-collapse: collapse;
  731. font-size: 14px;
  732. }
  733. #result_list thead th {
  734. background: #f8fafc;
  735. border-bottom: 2px solid #e2e8f0;
  736. padding: 12px 16px;
  737. font-weight: 600;
  738. font-size: 13px;
  739. color: #475569;
  740. text-align: left;
  741. text-transform: uppercase;
  742. letter-spacing: 0.025em;
  743. white-space: nowrap;
  744. }
  745. #result_list thead th a {
  746. color: #475569;
  747. text-decoration: none;
  748. }
  749. #result_list thead th a:hover {
  750. color: #1e293b;
  751. }
  752. #result_list thead th.sorted {
  753. background: #f1f5f9;
  754. }
  755. #result_list thead th .text span {
  756. padding-right: 5px;
  757. }
  758. #result_list tbody tr {
  759. border-bottom: 1px solid #f1f5f9;
  760. transition: background-color 0.15s ease;
  761. }
  762. #result_list tbody tr:hover {
  763. background-color: #f8fafc;
  764. }
  765. #result_list tbody tr.selected {
  766. background-color: #eff6ff;
  767. }
  768. #result_list tbody td {
  769. padding: 12px 16px;
  770. color: #334155;
  771. vertical-align: middle;
  772. }
  773. #result_list tbody td a {
  774. color: #2563eb;
  775. font-weight: 500;
  776. }
  777. #result_list tbody td a:hover {
  778. color: #1d4ed8;
  779. text-decoration: underline;
  780. }
  781. /* Checkbox column */
  782. #result_list .action-checkbox,
  783. #result_list th.action-checkbox-column {
  784. width: 40px;
  785. text-align: center;
  786. padding: 12px 8px;
  787. }
  788. /* Pagination */
  789. .paginator {
  790. background: #f8fafc;
  791. padding: 12px 16px;
  792. border-top: 1px solid #e2e8f0;
  793. font-size: 14px;
  794. color: #64748b;
  795. }
  796. .paginator a {
  797. color: #2563eb;
  798. padding: 6px 12px;
  799. border-radius: 6px;
  800. margin: 0 2px;
  801. text-decoration: none;
  802. }
  803. .paginator a:hover {
  804. background: #e2e8f0;
  805. }
  806. /* Toolbar / search bar */
  807. #changelist #toolbar {
  808. padding: 12px 16px;
  809. background: #fff;
  810. border-bottom: 1px solid #e2e8f0;
  811. display: flex;
  812. align-items: center;
  813. gap: 12px;
  814. }
  815. #toolbar form,
  816. #changelist-search {
  817. display: flex;
  818. align-items: center;
  819. gap: 8px;
  820. flex: 0 1 auto;
  821. max-width: 500px;
  822. }
  823. body.change-list #toolbar form > div {
  824. display: flex !important;
  825. align-items: center;
  826. gap: 8px;
  827. flex-wrap: nowrap !important;
  828. white-space: nowrap;
  829. }
  830. body.change-list #toolbar label {
  831. margin: 0;
  832. display: inline-flex;
  833. align-items: center;
  834. }
  835. body.change-list #toolbar input[type="submit"] {
  836. margin: 0;
  837. }
  838. #searchbar {
  839. flex: 1;
  840. max-width: 400px;
  841. padding: 10px 14px;
  842. border: 1px solid #d1d5db;
  843. border-radius: 8px;
  844. font-size: 14px;
  845. }
  846. #searchbar:focus {
  847. border-color: #3b82f6;
  848. outline: none;
  849. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
  850. }
  851. /* Filter sidebar */
  852. #changelist-filter {
  853. background: #fff;
  854. border: 1px solid #e2e8f0;
  855. border-radius: 12px;
  856. box-shadow: 0 1px 3px rgba(0,0,0,0.04);
  857. overflow: hidden;
  858. }
  859. #changelist-filter h2 {
  860. background: #f8fafc;
  861. padding: 12px 16px;
  862. font-size: 13px;
  863. font-weight: 600;
  864. color: #475569;
  865. text-transform: uppercase;
  866. letter-spacing: 0.025em;
  867. margin: 0;
  868. border-bottom: 1px solid #e2e8f0;
  869. display: flex;
  870. align-items: center;
  871. justify-content: space-between;
  872. gap: 8px;
  873. }
  874. #changelist-filter .filter-toggle {
  875. border: 1px solid #e2e8f0;
  876. background: #ffffff;
  877. color: #64748b;
  878. font-size: 11px;
  879. padding: 4px 8px;
  880. border-radius: 999px;
  881. cursor: pointer;
  882. text-transform: none;
  883. letter-spacing: normal;
  884. }
  885. #changelist-filter .filter-toggle:hover {
  886. background: #f1f5f9;
  887. color: #334155;
  888. }
  889. .filter-toggle-floating {
  890. position: static;
  891. box-shadow: none;
  892. padding: 2px 6px;
  893. font-size: 11px;
  894. line-height: 1.2;
  895. height: 20px;
  896. }
  897. #changelist-filter h3 {
  898. padding: 12px 16px 8px;
  899. font-size: 12px;
  900. font-weight: 600;
  901. color: #64748b;
  902. text-transform: uppercase;
  903. letter-spacing: 0.05em;
  904. margin: 0;
  905. }
  906. #changelist-filter ul {
  907. padding: 0 8px 12px;
  908. margin: 0;
  909. list-style: none;
  910. }
  911. #changelist-filter li {
  912. margin: 0;
  913. }
  914. #changelist-filter li a {
  915. display: block;
  916. padding: 8px 12px;
  917. color: #475569;
  918. text-decoration: none;
  919. border-radius: 6px;
  920. font-size: 14px;
  921. transition: background-color 0.15s ease;
  922. }
  923. #changelist-filter li a:hover {
  924. background: #f1f5f9;
  925. color: #1e293b;
  926. }
  927. #changelist-filter li.selected a {
  928. background: #eff6ff;
  929. color: #2563eb;
  930. font-weight: 500;
  931. }
  932. body.filters-collapsed #changelist-filter {
  933. display: none !important;
  934. }
  935. body.filters-collapsed.change-list .results,
  936. body.filters-collapsed.change-list .paginator,
  937. body.filters-collapsed.change-list #toolbar,
  938. body.filters-collapsed.change-list div.xfull,
  939. body.filters-collapsed.change-list #changelist .changelist-form-container,
  940. body.filters-collapsed.change-list #changelist-form,
  941. body.filters-collapsed.change-list #result_list {
  942. margin-right: 0 !important;
  943. width: 100% !important;
  944. }
  945. body.filters-collapsed.change-list #changelist .changelist-form-container > div {
  946. max-width: 100% !important;
  947. }
  948. /* Actions bar */
  949. body.change-list #changelist .actions {
  950. padding: 12px 16px;
  951. background: #f8fafc;
  952. border-bottom: 0;
  953. display: flex !important;
  954. align-items: center;
  955. gap: 8px;
  956. flex-wrap: nowrap !important;
  957. overflow-x: auto;
  958. }
  959. body.change-list #changelist {
  960. border: 0 !important;
  961. }
  962. body.change-list #changelist .actions .button,
  963. body.change-list #changelist .actions select,
  964. body.change-list #changelist .actions label {
  965. line-height: 1.5rem;
  966. height: 1.5rem;
  967. display: inline-flex;
  968. align-items: center;
  969. }
  970. body.change-list #changelist .actions-left {
  971. display: flex;
  972. align-items: center;
  973. gap: 8px;
  974. flex-wrap: nowrap !important;
  975. flex: 1 1 auto;
  976. min-width: 0;
  977. white-space: nowrap;
  978. }
  979. body.change-list #changelist .actions-right {
  980. display: flex;
  981. align-items: center;
  982. gap: 8px;
  983. margin-left: auto;
  984. flex: 0 0 auto;
  985. }
  986. .actions label {
  987. font-size: 14px;
  988. color: #475569;
  989. }
  990. .actions select {
  991. padding: 8px 12px;
  992. border: 1px solid #d1d5db;
  993. border-radius: 6px;
  994. font-size: 14px;
  995. background: #fff;
  996. }
  997. .actions .button {
  998. padding: 8px 16px;
  999. font-size: 14px;
  1000. }
  1001. /* Object count */
  1002. .actions .action-counter {
  1003. color: #64748b;
  1004. font-size: 14px;
  1005. }
  1006. /* Empty results */
  1007. #changelist-form .results + p,
  1008. .paginator + p {
  1009. padding: 40px;
  1010. text-align: center;
  1011. color: #64748b;
  1012. font-size: 15px;
  1013. }
  1014. /* Date hierarchy */
  1015. .xfull {
  1016. padding: 12px 16px;
  1017. background: #f8fafc;
  1018. border-bottom: 1px solid #e2e8f0;
  1019. }
  1020. .xfull a {
  1021. color: #2563eb;
  1022. margin-right: 8px;
  1023. }
  1024. /* ============================================
  1025. Tag Editor Widget Styles
  1026. ============================================ */
  1027. /* Main container - acts as input field */
  1028. .tag-editor-container {
  1029. display: flex;
  1030. flex-wrap: wrap;
  1031. align-items: center;
  1032. gap: 6px;
  1033. padding: 8px 12px;
  1034. min-height: 42px;
  1035. background: #fff;
  1036. border: 1px solid #d1d5db;
  1037. border-radius: 8px;
  1038. cursor: text;
  1039. transition: border-color 0.15s ease, box-shadow 0.15s ease;
  1040. }
  1041. .tag-editor-container:focus-within {
  1042. border-color: #3b82f6;
  1043. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
  1044. }
  1045. /* Pills container */
  1046. .tag-pills {
  1047. display: flex;
  1048. flex-wrap: wrap;
  1049. gap: 6px;
  1050. align-items: center;
  1051. }
  1052. /* Individual tag pill */
  1053. .tag-pill {
  1054. display: inline-flex;
  1055. align-items: center;
  1056. gap: 4px;
  1057. padding: 4px 8px 4px 10px;
  1058. background: var(--tag-bg, #e2e8f0);
  1059. color: var(--tag-fg, #1e293b);
  1060. font-size: 13px;
  1061. font-weight: 500;
  1062. border-radius: 16px;
  1063. white-space: nowrap;
  1064. transition: all 0.15s ease;
  1065. -webkit-font-smoothing: antialiased;
  1066. border: 1px solid var(--tag-border, #cbd5e1);
  1067. }
  1068. .tag-pill:hover {
  1069. filter: brightness(0.98);
  1070. }
  1071. .tag-pill a.tag-link {
  1072. color: inherit;
  1073. text-decoration: none;
  1074. }
  1075. .tag-pill a.tag-link:hover {
  1076. text-decoration: underline;
  1077. }
  1078. /* Remove button on pills */
  1079. .tag-remove-btn {
  1080. display: inline-flex;
  1081. align-items: center;
  1082. justify-content: center;
  1083. width: 16px;
  1084. height: 16px;
  1085. padding: 0;
  1086. margin: 0;
  1087. background: rgba(15, 23, 42, 0.08);
  1088. border: 1px solid rgba(15, 23, 42, 0.12);
  1089. border-radius: 50%;
  1090. color: inherit;
  1091. font-size: 14px;
  1092. font-weight: 600;
  1093. line-height: 1;
  1094. cursor: pointer;
  1095. opacity: 0.7;
  1096. transition: all 0.15s ease;
  1097. }
  1098. .tag-remove-btn:hover {
  1099. background: rgba(15, 23, 42, 0.18);
  1100. opacity: 1;
  1101. }
  1102. /* Inline input for adding tags */
  1103. .tag-inline-input {
  1104. flex: 1;
  1105. min-width: 120px;
  1106. padding: 4px 0;
  1107. border: none;
  1108. outline: none;
  1109. font-size: 14px;
  1110. font-family: inherit;
  1111. background: transparent;
  1112. color: #1e293b;
  1113. }
  1114. .tag-inline-input::placeholder {
  1115. color: #94a3b8;
  1116. }
  1117. /* Inline editor for list view - more compact */
  1118. .tag-editor-inline {
  1119. display: inline-flex;
  1120. flex-wrap: wrap;
  1121. align-items: center;
  1122. gap: 4px;
  1123. padding: 2px 4px;
  1124. background: transparent;
  1125. border-radius: 4px;
  1126. cursor: text;
  1127. vertical-align: middle;
  1128. }
  1129. .tag-pills-inline {
  1130. display: inline-flex;
  1131. flex-wrap: wrap;
  1132. gap: 4px;
  1133. align-items: center;
  1134. }
  1135. .tag-editor-inline .tag-pill {
  1136. padding: 2px 6px 2px 8px;
  1137. font-size: 11px;
  1138. border-radius: 12px;
  1139. }
  1140. .tag-editor-inline .tag-remove-btn {
  1141. width: 14px;
  1142. height: 14px;
  1143. font-size: 12px;
  1144. }
  1145. #content .tag-editor-inline input.tag-inline-input-sm {
  1146. width: 22px;
  1147. min-width: 22px;
  1148. max-width: 140px;
  1149. height: 22px;
  1150. padding: 0 6px;
  1151. border: 1px solid #e2e8f0;
  1152. outline: none;
  1153. font-size: 12px;
  1154. font-family: inherit;
  1155. background: #f1f5f9;
  1156. color: #94a3b8;
  1157. border-radius: 999px;
  1158. text-align: center;
  1159. cursor: text;
  1160. transition: width 0.15s ease, color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  1161. }
  1162. #content .tag-editor-inline input.tag-inline-input-sm:focus {
  1163. width: 120px;
  1164. color: #1e293b;
  1165. border-color: #94a3b8;
  1166. background: #ffffff;
  1167. text-align: left;
  1168. }
  1169. #content .tag-editor-inline input.tag-inline-input-sm::placeholder {
  1170. color: #94a3b8;
  1171. }
  1172. /* Actions bar tag editor (compact to avoid crowding buttons) */
  1173. body.change-list #changelist .actions .tag-editor-container {
  1174. padding: 2px 6px;
  1175. min-height: 24px;
  1176. height: 24px;
  1177. width: 160px;
  1178. max-width: 160px;
  1179. flex: 0 0 160px;
  1180. flex-wrap: nowrap;
  1181. overflow-x: auto;
  1182. overflow-y: hidden;
  1183. gap: 4px;
  1184. }
  1185. body.change-list #changelist .actions-tags {
  1186. display: none;
  1187. align-items: center;
  1188. }
  1189. /* Ensure changelist filter sidebar is visible */
  1190. body.change-list #changelist .changelist-form-container {
  1191. display: flex;
  1192. align-items: flex-start;
  1193. width: 100%;
  1194. gap: 20px;
  1195. flex-wrap: nowrap;
  1196. }
  1197. body.change-list #changelist-filter {
  1198. flex: 0 0 260px;
  1199. max-width: 260px;
  1200. display: block;
  1201. margin: 0;
  1202. order: 2;
  1203. align-self: flex-start;
  1204. }
  1205. body.change-list #changelist .changelist-form-container > div {
  1206. flex: 1 1 auto;
  1207. min-width: 0;
  1208. order: 1;
  1209. max-width: calc(100% - 280px);
  1210. }
  1211. .actions .tag-pills {
  1212. gap: 4px;
  1213. flex-wrap: nowrap;
  1214. }
  1215. .actions .tag-pill {
  1216. padding: 1px 6px 1px 8px;
  1217. font-size: 10px;
  1218. }
  1219. .actions .tag-inline-input {
  1220. min-width: 40px;
  1221. padding: 0;
  1222. font-size: 11px;
  1223. }
  1224. /* Container in list view title column */
  1225. .tags-inline-editor {
  1226. display: inline;
  1227. margin-left: 8px;
  1228. }
  1229. /* Existing tag styles (keep for backwards compat) */
  1230. .tags .tag {
  1231. display: inline-block;
  1232. padding: 2px 8px;
  1233. margin: 1px 2px;
  1234. background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  1235. color: #fff;
  1236. font-size: 11px;
  1237. font-weight: 500;
  1238. border-radius: 12px;
  1239. text-decoration: none;
  1240. transition: all 0.15s ease;
  1241. }
  1242. .tags .tag:hover {
  1243. background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
  1244. }
  1245. </style>
  1246. {% endblock %}
  1247. {% if LANGUAGE_BIDI %}
  1248. <link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">
  1249. {% endif %}
  1250. {% block responsive %}
  1251. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
  1252. <link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive.css" %}">
  1253. {% if LANGUAGE_BIDI %}
  1254. <link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive_rtl.css" %}">
  1255. {% endif %}
  1256. {% endblock %}
  1257. <script
  1258. src="{% static 'jquery-3.7.1.slim.min.js' %}"
  1259. integrity="sha256-kmHvs0B+OpCW5GVHUNjv9rOmY0IvSIRcf7zGUDTDQM8="
  1260. crossorigin="anonymous"></script>
  1261. <link href="{% static 'select2.min.css' %}" rel="stylesheet"/>
  1262. <script src="{% static 'select2.min.js' %}"></script>
  1263. <link rel="stylesheet" type="text/css" href="{% static "admin.css" %}">
  1264. <script>
  1265. function selectSnapshotListView(e) {
  1266. e && e.stopPropagation()
  1267. e && e.preventDefault()
  1268. console.log('Switching to Snapshot list view...')
  1269. localStorage.setItem('preferred_snapshot_view_mode', 'list')
  1270. window.location = "{% url 'admin:core_snapshot_changelist' %}" + document.location.search
  1271. return false
  1272. }
  1273. function selectSnapshotGridView(e) {
  1274. e && e.stopPropagation()
  1275. e && e.preventDefault()
  1276. console.log('Switching to Snapshot grid view...')
  1277. localStorage.setItem('preferred_snapshot_view_mode', 'grid')
  1278. window.location = "{% url 'admin:grid' %}" + document.location.search
  1279. return false
  1280. }
  1281. const preferred_view = localStorage.getItem('preferred_snapshot_view_mode') || 'unset'
  1282. const current_view = (
  1283. window.location.pathname === "{% url 'admin:core_snapshot_changelist' %}"
  1284. ? 'list'
  1285. : 'grid')
  1286. console.log('Preferred snapshot view is:', preferred_view, 'Current view mode is:', current_view)
  1287. if (preferred_view === 'grid' && current_view !== 'grid') {
  1288. selectSnapshotGridView()
  1289. }
  1290. </script>
  1291. {% block extrahead %}{% endblock %}
  1292. </head>
  1293. <body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}">
  1294. {% include 'progressbar.html' %}
  1295. <div id="container">
  1296. {% if not is_popup %}
  1297. <div id="header">
  1298. <div id="branding">
  1299. <h1 id="site-name">
  1300. <a href="{% url 'Home' %}">
  1301. <img src="{% static 'archive.png' %}" id="logo">
  1302. ArchiveBox
  1303. </a>
  1304. </h1>
  1305. </div>
  1306. {% block usertools %}
  1307. {% if has_permission %}
  1308. {% include 'navigation.html' %}
  1309. {% endif %}
  1310. {% endblock %}
  1311. {% block nav-global %}{% endblock %}
  1312. </div>
  1313. {% if has_permission %}
  1314. {% include 'admin/progress_monitor.html' %}
  1315. {% endif %}
  1316. {% block breadcrumbs %}
  1317. <div class="breadcrumbs">
  1318. <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  1319. {% if title %} &rsaquo; {{ title }}{% endif %}
  1320. </div>
  1321. {% endblock %}
  1322. {% endif %}
  1323. {% block messages %}
  1324. {% if messages %}
  1325. <ul class="messagelist">
  1326. {% for message in messages %}
  1327. <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message|capfirst }}</li>
  1328. {% endfor %}
  1329. </ul>
  1330. {% endif %}
  1331. {% endblock messages %}
  1332. <div id="content" class="{% block coltype %}colM{% endblock %}">
  1333. {% if opts.model_name == 'snapshot' and cl %}
  1334. <div id="snapshot-view-mode">
  1335. <a href="#list" title="List view" id="snapshot-view-list">
  1336. <span class="view-icon">☰</span>
  1337. <span class="view-label">List</span>
  1338. </a>
  1339. <a href="#grid" title="Grid view" id="snapshot-view-grid">
  1340. <span class="view-icon">⊞</span>
  1341. <span class="view-label">Grid</span>
  1342. </a>
  1343. </div>
  1344. {% endif %}
  1345. {% block pretitle %}{% endblock %}
  1346. {% block content_title %}{# {% if title %}<h1>{{ title }}</h1>{% endif %} #}{% endblock %}
  1347. {% block content %}
  1348. {% block object-tools %}{% endblock %}
  1349. {{ content }}
  1350. {% endblock %}
  1351. {% block sidebar %}{% endblock %}
  1352. <br class="clear">
  1353. </div>
  1354. {% block footer %}<div id="footer"></div>{% endblock %}
  1355. </div>
  1356. {% comment %}
  1357. {% if user.is_authenticated and user.is_superuser and CAN_UPGRADE %}
  1358. <script>
  1359. if (!localStorage.getItem("bannerDismissed")) {
  1360. const upgradeVersionTag = "{{VERSIONS_AVAILABLE.recommended_version.tag_name}}"
  1361. const upgradeVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
  1362. const currentVersionTag = "{{VERSION}}"
  1363. const currentVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
  1364. createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL)
  1365. }
  1366. function createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL) {
  1367. const banner = document.createElement('div')
  1368. banner.setAttribute('id', 'upgrade-banner');
  1369. banner.innerHTML = `
  1370. <p>There's a new version of ArchiveBox available!</p>
  1371. Your version: <a href=${currentVersionURL}>${currentVersionTag}</a> | New version: <a href=${upgradeVersionURL}>${upgradeVersionTag}</a>
  1372. <p>
  1373. <a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives>Upgrade Instructions</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/releases>Changelog</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap>Roadmap</a>
  1374. </p>
  1375. <button id="dismiss-btn">Dismiss</button>
  1376. `
  1377. document.body.appendChild(banner);
  1378. const dismissButton = document.querySelector("#dismiss-btn")
  1379. if (dismissButton) {
  1380. dismissButton.addEventListener("click", dismissBanner)
  1381. }
  1382. }
  1383. function dismissBanner() {
  1384. const banner = document.getElementById("upgrade-banner")
  1385. banner.style.display = "none"
  1386. localStorage.setItem("bannerDismissed", "true")
  1387. }
  1388. </script>
  1389. {% endif %}
  1390. {% endcomment %}
  1391. <script>
  1392. $ = django.jQuery;
  1393. $.fn.reverse = [].reverse;
  1394. // hide images that fail to load
  1395. document.querySelector('body').addEventListener('error', function (e) {
  1396. e.target.style.opacity = 0;
  1397. }, true)
  1398. // setup timezone
  1399. {% get_current_timezone as TIME_ZONE %}
  1400. window.TIME_ZONE = '{{TIME_ZONE}}'
  1401. window.setCookie = function(name, value, days) {
  1402. let expires = ""
  1403. if (days) {
  1404. const date = new Date()
  1405. date.setTime(date.getTime() + (days*24*60*60*1000))
  1406. expires = "; expires=" + date.toUTCString()
  1407. }
  1408. document.cookie = name + "=" + (value || "") + expires + "; path=/"
  1409. }
  1410. function setTimeOffset() {
  1411. if (window.GMT_OFFSET) return
  1412. window.GMT_OFFSET = -(new Date).getTimezoneOffset()
  1413. window.setCookie('GMT_OFFSET', window.GMT_OFFSET, 365)
  1414. }
  1415. // change the admin actions button from a dropdown to buttons across
  1416. function fix_actions() {
  1417. const container = $('div.actions')
  1418. // too many actions to turn into buttons
  1419. if (container.find('select[name=action] option').length >= 11) return
  1420. // hide the empty default option thats just a placeholder with no value
  1421. container.find('label:nth-child(1), button[value=0]').hide()
  1422. const buttons = $('<div></div>')
  1423. .insertAfter('div.actions button[type=submit]')
  1424. .css('display', 'inline')
  1425. .addClass('class', 'action-buttons');
  1426. // for each action in the dropdown, turn it into a button instead
  1427. container.find('select[name=action] option:gt(0)').each(function () {
  1428. const action_type = this.value
  1429. $('<button>')
  1430. .attr('type', 'button')
  1431. .attr('name', action_type)
  1432. .addClass('button')
  1433. .text(this.text)
  1434. .click(function (e) {
  1435. e.preventDefault()
  1436. e.stopPropagation()
  1437. const num_selected = document.querySelector('.action-counter').innerText.split(' ')[0]
  1438. if (action_type === 'overwrite_snapshots') {
  1439. const message = (
  1440. 'Are you sure you want to re-archive (overwrite) ' + num_selected + ' Snapshots?\n\n' +
  1441. 'This will delete all previously saved files from these Snapshots and re-archive them from scratch.\n\n'
  1442. )
  1443. if (!window.confirm(message)) return false
  1444. }
  1445. if (action_type === 'delete_snapshots') {
  1446. const message = (
  1447. 'Are you sure you want to permanently delete ' + num_selected + ' Snapshots?\n\n' +
  1448. 'They will be removed from your index, and all their Snapshot content on disk will be permanently deleted.'
  1449. )
  1450. if (!window.confirm(message)) return false
  1451. }
  1452. // select the action button from the dropdown
  1453. container.find('select[name=action]')
  1454. .find('[selected]').removeAttr('selected').end()
  1455. .find('[value=' + action_type + ']').attr('selected', 'selected').click()
  1456. // click submit & replace the archivebox logo with a spinner
  1457. $('#changelist-form button[name="index"]').click()
  1458. document.querySelector('#logo').outerHTML = '<div class="loader"></div>'
  1459. return false
  1460. })
  1461. .appendTo(buttons)
  1462. })
  1463. console.log('Converted', buttons.children().length, 'admin actions from dropdown to buttons')
  1464. jQuery('select[multiple]').select2();
  1465. }
  1466. function updateTagWidgetVisibility() {
  1467. const tagContainer = document.querySelector('.actions-tags');
  1468. if (!tagContainer) return;
  1469. const checked = document.querySelectorAll('#changelist-form input.action-select:checked').length;
  1470. tagContainer.style.display = checked > 0 ? 'inline-flex' : 'none';
  1471. }
  1472. function fixInlineAddRow() {
  1473. $('#id_snapshottag-MAX_NUM_FORMS').val('1000')
  1474. $('.add-row').show()
  1475. }
  1476. function setupSnapshotGridListToggle() {
  1477. $("#snapshot-view-list").click(selectSnapshotListView)
  1478. $("#snapshot-view-grid").click(selectSnapshotGridView)
  1479. // Set active class based on current view
  1480. const isGridView = window.location.pathname === "{% url 'admin:grid' %}"
  1481. if (isGridView) {
  1482. $("#snapshot-view-grid").addClass('active')
  1483. $("#snapshot-view-list").removeClass('active')
  1484. } else {
  1485. $("#snapshot-view-list").addClass('active')
  1486. $("#snapshot-view-grid").removeClass('active')
  1487. }
  1488. $('#changelist-form .card input:checkbox').change(function() {
  1489. if ($(this).is(':checked'))
  1490. $(this).parents('.card').addClass('selected-card')
  1491. else
  1492. $(this).parents('.card').removeClass('selected-card')
  1493. })
  1494. };
  1495. function selectSnapshotIfHotlinked() {
  1496. // if we arrive at the index with a url like ??id__startswith=...
  1497. // we were hotlinked here with the intention of making it easy for the user to perform some
  1498. // actions on the given snapshot. therefore we should preselect the snapshot to save them a click
  1499. if (window.location.search.startsWith('?')) {
  1500. const result_checkboxes = [...document.querySelectorAll('#result_list .action-checkbox input[type=checkbox]')]
  1501. if (result_checkboxes.length === 1) {
  1502. result_checkboxes[0].click()
  1503. }
  1504. }
  1505. }
  1506. $(document).ready(function() {
  1507. fix_actions()
  1508. updateTagWidgetVisibility()
  1509. const form = document.querySelector('#changelist-form')
  1510. if (form) {
  1511. form.addEventListener('change', updateTagWidgetVisibility)
  1512. }
  1513. fixInlineAddRow()
  1514. setupSnapshotGridListToggle()
  1515. setTimeOffset()
  1516. selectSnapshotIfHotlinked()
  1517. })
  1518. </script>
  1519. <script>
  1520. (function() {
  1521. if (!document.body.classList.contains('change-list')) return;
  1522. var filter = document.getElementById('changelist-filter');
  1523. if (!filter) return;
  1524. var header = filter.querySelector('h2');
  1525. if (!header) return;
  1526. var toggle = document.getElementById('changelist-filter-toggle');
  1527. if (!toggle) {
  1528. toggle = document.createElement('button');
  1529. toggle.type = 'button';
  1530. toggle.id = 'changelist-filter-toggle';
  1531. toggle.className = 'filter-toggle';
  1532. toggle.setAttribute('aria-expanded', 'true');
  1533. toggle.dataset.showLabel = '{% translate "Filters" %}';
  1534. toggle.dataset.hideLabel = '{% translate "Hide" %}';
  1535. toggle.textContent = toggle.dataset.hideLabel;
  1536. header.appendChild(toggle);
  1537. }
  1538. var storageKey = 'admin-filters-collapsed';
  1539. var changelist = document.getElementById('changelist');
  1540. var hadFiltered = changelist && changelist.classList.contains('filtered');
  1541. var floating = document.getElementById('changelist-filter-float-toggle');
  1542. if (!floating) {
  1543. floating = document.createElement('button');
  1544. floating.type = 'button';
  1545. floating.id = 'changelist-filter-float-toggle';
  1546. floating.className = 'filter-toggle filter-toggle-floating';
  1547. floating.textContent = toggle.dataset.showLabel;
  1548. }
  1549. var actionsRight = document.querySelector('#changelist .actions .actions-right');
  1550. var actionsBar = document.querySelector('#changelist .actions');
  1551. if (actionsRight) {
  1552. actionsRight.appendChild(floating);
  1553. } else if (actionsBar) {
  1554. actionsBar.appendChild(floating);
  1555. }
  1556. function applyState() {
  1557. var collapsed = localStorage.getItem(storageKey) === 'true';
  1558. document.body.classList.toggle('filters-collapsed', collapsed);
  1559. filter.style.display = collapsed ? 'none' : '';
  1560. toggle.textContent = collapsed ? toggle.dataset.showLabel : toggle.dataset.hideLabel;
  1561. toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
  1562. floating.style.display = collapsed ? 'inline-flex' : 'none';
  1563. if (changelist) {
  1564. if (collapsed) {
  1565. changelist.classList.remove('filtered');
  1566. } else if (hadFiltered) {
  1567. changelist.classList.add('filtered');
  1568. }
  1569. }
  1570. }
  1571. function toggleFilters() {
  1572. var collapsed = !document.body.classList.contains('filters-collapsed');
  1573. localStorage.setItem(storageKey, collapsed ? 'true' : 'false');
  1574. applyState();
  1575. }
  1576. toggle.addEventListener('click', toggleFilters);
  1577. floating.addEventListener('click', toggleFilters);
  1578. applyState();
  1579. })();
  1580. </script>
  1581. <script src="{% static 'admin-inline-tags.js' %}"></script>
  1582. </body>
  1583. </html>