| @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12.68,19.46H9.83V13H5.61v6.47H2.75V4.54H5.61v6.12H9.83V4.54h2.85Zm8.57,0H18.37V8L14.6,9.36V6.88l6.5-2.34h.15Z"/></svg> | |||
| @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.29,19.57H8.44V13.1H4.22v6.47H1.36V4.64H4.22v6.12H8.44V4.64h2.85Zm11.35,0H12.46V17.62l4.83-5.19c.38-.41.7-.78,1-1.12a8.46,8.46,0,0,0,.63-.91,4.3,4.3,0,0,0,.35-.78,2.47,2.47,0,0,0,.1-.7A2.72,2.72,0,0,0,19.19,8a1.85,1.85,0,0,0-.41-.66A1.64,1.64,0,0,0,18.16,7a2.09,2.09,0,0,0-.81-.15,2.2,2.2,0,0,0-1.75.66A2.66,2.66,0,0,0,15,9.3H12.15a4.78,4.78,0,0,1,.38-1.88,4.66,4.66,0,0,1,1.08-1.55,4.75,4.75,0,0,1,1.67-1,5.78,5.78,0,0,1,2.17-.39,5.93,5.93,0,0,1,2,.32,4.08,4.08,0,0,1,1.47.89A3.86,3.86,0,0,1,21.82,7a4.86,4.86,0,0,1,.31,1.78,4.17,4.17,0,0,1-.23,1.41,5.68,5.68,0,0,1-.66,1.33,11.34,11.34,0,0,1-1,1.34c-.39.45-.83.93-1.32,1.43l-2.81,3h6.55Z"/></svg> | |||
| @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.28,19.46H8.43V13H4.2v6.47H1.34V4.54H4.2v6.12H8.43V4.54h2.85Zm4.46-8.71h1.55A2.31,2.31,0,0,0,19,10.16a2.12,2.12,0,0,0,.56-1.54,2.55,2.55,0,0,0-.12-.81,1.69,1.69,0,0,0-1-1,2.82,2.82,0,0,0-.92-.14,2.58,2.58,0,0,0-.8.12,2.08,2.08,0,0,0-.66.35,1.59,1.59,0,0,0-.44.56,1.52,1.52,0,0,0-.17.74H12.58A3.65,3.65,0,0,1,13,6.75a4,4,0,0,1,1-1.29,4.9,4.9,0,0,1,1.55-.83,5.93,5.93,0,0,1,1.91-.3,7.22,7.22,0,0,1,2,.27,4.38,4.38,0,0,1,1.58.81,3.66,3.66,0,0,1,1,1.34,4.41,4.41,0,0,1,.36,1.84,3,3,0,0,1-.14.94,3.63,3.63,0,0,1-.42.89,3.58,3.58,0,0,1-.69.78,4.62,4.62,0,0,1-.94.61,3.94,3.94,0,0,1,1.08.57,3.34,3.34,0,0,1,.76.79,3.77,3.77,0,0,1,.44,1,4.6,4.6,0,0,1,.14,1.13,4.18,4.18,0,0,1-.4,1.87,4.1,4.1,0,0,1-1.1,1.38,5,5,0,0,1-1.65.85,7.24,7.24,0,0,1-2.07.29,6.79,6.79,0,0,1-1.86-.26A5.2,5.2,0,0,1,14,18.62a3.93,3.93,0,0,1-1.13-1.33,4,4,0,0,1-.42-1.89h2.85a1.91,1.91,0,0,0,.16.8,1.79,1.79,0,0,0,.47.62,2,2,0,0,0,.71.41,2.57,2.57,0,0,0,.9.15,2.39,2.39,0,0,0,1.7-.57,2,2,0,0,0,.62-1.56,2.94,2.94,0,0,0-.17-1,1.82,1.82,0,0,0-.51-.7,2.09,2.09,0,0,0-.8-.41A3.76,3.76,0,0,0,17.29,13H15.74Z"/></svg> | |||
| @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220.8 24"><path d="M59.03,0H0v24h59.03V0Z" style="fill:#0058a3; stroke-width:0px;"/><path d="M1.15,12c0,5.81,11.89,10.62,28.36,10.62s28.36-4.81,28.36-10.62S45.99,1.38,29.52,1.38,1.15,6.2,1.15,12h0Z" style="fill:#ffdb00; stroke-width:0px;"/><path d="M27.84,15.41c.17.26.38.5.62.75h-6.38c0-.25-.24-.77-.52-1.18-.27-.41-1.74-2.63-1.74-2.63v3.06c0,.25,0,.5.13.75h-5.32c.13-.25.13-.5.13-.75v-7.27c0-.25,0-.5-.13-.75h5.32c-.13.25-.13.5-.13.75v3.17s1.7-2.22,2.09-2.73c.3-.4.66-.94.66-1.19h5.55c-.38.25-.81.71-1.15,1.13-.31.37-2.03,2.48-2.03,2.48,0,0,2.56,3.9,2.9,4.4h0ZM29.51,8.14v7.27c0,.25,0,.5-.13.75h10.27v-2.43c-.25.13-.5.13-.75.13h-4.32v-1.15h4.15v-1.85h-4.15v-1.15h4.32c.25,0,.5,0,.75.12v-2.43h-10.27c.13.25.13.5.13.75,0,0,0,0,0,0ZM53.89,15.41c.1.25.19.5.41.75h-5.56c.03-.25-.07-.5-.17-.75,0,0-.08-.2-.2-.5l-.05-.13h-3.21l-.05.13-.18.5c-.09.25-.18.5-.15.75h-4.4c.22-.25.31-.5.4-.75.15-.4,2.43-6.7,2.63-7.27.09-.25.18-.5.15-.75h7.42c-.06.25.07.5.17.75.22.56,2.59,6.77,2.78,7.26,0,0,0,0,0,0ZM47.61,12.92c-.4-1.05-.74-1.94-.78-2.03-.07-.19-.13-.38-.17-.58,0,0-.06.32-.15.58-.03.08-.35.97-.73,2.03,0,0,1.83,0,1.83,0ZM12.12,7.38h-5.78c.13.25.13.5.13.75v7.27c0,.25,0,.5-.13.75h5.78c-.13-.25-.13-.5-.13-.75v-7.27c0-.25,0-.5.13-.75h0ZM52.11,8.08c0-.67.48-1.15,1.15-1.15s1.15.49,1.15,1.15-.48,1.15-1.15,1.15-1.15-.48-1.15-1.15ZM52.34,8.08c0,.51.39.92.92.92.5,0,.91-.39.92-.89,0-.01,0-.02,0-.03,0-.5-.39-.91-.89-.92-.01,0-.02,0-.03,0-.54,0-.92.39-.92.92ZM53.04,8.77h-.21v-1.38h.52c.24,0,.43.21.43.45,0,.17-.1.33-.24.41l.3.53h-.23l-.27-.49h-.3v.49h0ZM53.04,8.08h.28c.14,0,.26-.1.26-.24s-.12-.24-.26-.24h-.28v.49Z" style="fill:#0058a3; stroke-width:0px;"/><path d="M118.12,19.2V4.8h8.04v1.59h-6.23v5.1h5.84v1.59h-5.84v6.11h-1.82ZM132.52,19.4c-2.86,0-4.96-2.04-4.96-5.63s1.91-5.59,5.02-5.59c2.92,0,4.98,2.02,4.98,5.59s-1.96,5.63-5.04,5.63h0ZM132.57,17.93c2.18,0,3.16-1.59,3.16-4.15s-.99-4.09-3.19-4.09-3.14,1.53-3.14,4.09.97,4.15,3.16,4.15M148.89,19.2h-1.45l-.26-1.43h-.08c-.71,1.13-2.04,1.63-3.43,1.63-2.6,0-3.93-1.19-3.93-3.93v-7.08h1.79v6.96c0,1.74.77,2.58,2.4,2.58,2.4,0,3.18-1.39,3.18-3.91v-5.63h1.77v10.81h0,0ZM161,12.16v7.04h-1.75v-6.92c0-1.73-.77-2.6-2.42-2.6-2.4,0-3.16,1.39-3.16,3.91v5.61h-1.77v-10.81h1.43l.26,1.47h.1c.71-1.13,2.04-1.67,3.41-1.67,2.58,0,3.91,1.19,3.91,3.98M163.57,13.82c0-3.71,1.77-5.62,4.46-5.62,1.67,0,2.68.7,3.35,1.59h.12c-.04-.34-.12-1.19-.12-1.59V3.87h1.77v15.33h-1.43l-.26-1.45h-.08c-.64.93-1.67,1.66-3.37,1.66-2.68,0-4.43-1.88-4.43-5.59h0,0ZM171.39,14.16v-.32c0-2.68-.72-4.18-3.13-4.18-1.91,0-2.86,1.63-2.86,4.2s.95,4.07,2.88,4.07c2.28,0,3.1-1.25,3.1-3.77M184.36,11.84v7.36h-1.29l-.34-1.53h-.08c-.95,1.19-1.82,1.74-3.63,1.74-1.96,0-3.41-1.01-3.41-3.21s1.65-3.35,5.18-3.45l1.83-.06v-.64c0-1.8-.83-2.4-2.24-2.4-1.13,0-2.16.4-3.05.83l-.54-1.33c.95-.5,2.28-.93,3.69-.93,2.62,0,3.87,1.11,3.87,3.63,0,0,0,0,0,0ZM181.01,13.98c-2.7.1-3.57.87-3.57,2.24,0,1.21.8,1.75,1.97,1.75,1.81,0,3.18-.99,3.18-3.09v-.97l-1.59.06h0ZM192.74,17.73v1.35c-.39.18-1.17.32-1.82.32-1.69,0-3.16-.72-3.16-3.35v-6.29h-1.53v-.85l1.55-.7.71-2.3h1.05v2.48h3.13v1.37h-3.13v6.25c0,1.31.71,1.94,1.69,1.94.52,0,1.17-.1,1.51-.22h0ZM196.82,5.47c0,.77-.48,1.13-1.03,1.13-.58,0-1.05-.36-1.05-1.13s.46-1.13,1.05-1.13c.54,0,1.03.34,1.03,1.13h0ZM196.67,19.2h-1.77v-10.81h1.77v10.81ZM204.11,19.4c-2.86,0-4.96-2.04-4.96-5.63s1.91-5.59,5.02-5.59c2.92,0,4.98,2.02,4.98,5.59s-1.96,5.63-5.04,5.63h0ZM204.15,17.93c2.18,0,3.16-1.59,3.16-4.15s-.99-4.09-3.19-4.09-3.14,1.53-3.14,4.09.97,4.15,3.16,4.15M220.8,12.16v7.04h-1.75v-6.92c0-1.73-.77-2.6-2.42-2.6-2.4,0-3.16,1.39-3.16,3.91v5.61h-1.77v-10.81h1.43l.26,1.47h.1c.71-1.13,2.04-1.67,3.41-1.67,2.58,0,3.91,1.19,3.91,3.98M72.83,4.8h-1.81v14.4h1.81V4.8ZM85.14,19.2l-5.1-6.88-1.47,1.29v5.59h-1.81V4.8h1.81v7.1c.81-.91,1.65-1.82,2.48-2.74l3.89-4.36h2.11l-5.7,6.27,5.93,8.13h-2.14ZM89.24,19.2V4.8h8.04v1.59h-6.23v4.5h5.86v1.58h-5.86v5.14h6.23v1.59h-8.04ZM107.74,14.74h-5.7l-1.71,4.46h-1.83l5.62-14.46h1.63l5.6,14.46h-1.88s-1.73-4.46-1.73-4.46ZM105.58,8.77c-.25-.71-.47-1.43-.69-2.16-.18.73-.39,1.45-.62,2.16l-1.63,4.36h4.55s-1.61-4.36-1.61-4.36Z" style="fill:#FFF;"/></svg> | |||
| @ -0,0 +1 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220.8 24"><path d="M59.03,0H0v24h59.03V0Z" style="fill:#0058a3; stroke-width:0px;"/><path d="M1.15,12c0,5.81,11.89,10.62,28.36,10.62s28.36-4.81,28.36-10.62S45.99,1.38,29.52,1.38,1.15,6.2,1.15,12h0Z" style="fill:#ffdb00; stroke-width:0px;"/><path d="M27.84,15.41c.17.26.38.5.62.75h-6.38c0-.25-.24-.77-.52-1.18-.27-.41-1.74-2.63-1.74-2.63v3.06c0,.25,0,.5.13.75h-5.32c.13-.25.13-.5.13-.75v-7.27c0-.25,0-.5-.13-.75h5.32c-.13.25-.13.5-.13.75v3.17s1.7-2.22,2.09-2.73c.3-.4.66-.94.66-1.19h5.55c-.38.25-.81.71-1.15,1.13-.31.37-2.03,2.48-2.03,2.48,0,0,2.56,3.9,2.9,4.4h0ZM29.51,8.14v7.27c0,.25,0,.5-.13.75h10.27v-2.43c-.25.13-.5.13-.75.13h-4.32v-1.15h4.15v-1.85h-4.15v-1.15h4.32c.25,0,.5,0,.75.12v-2.43h-10.27c.13.25.13.5.13.75,0,0,0,0,0,0ZM53.89,15.41c.1.25.19.5.41.75h-5.56c.03-.25-.07-.5-.17-.75,0,0-.08-.2-.2-.5l-.05-.13h-3.21l-.05.13-.18.5c-.09.25-.18.5-.15.75h-4.4c.22-.25.31-.5.4-.75.15-.4,2.43-6.7,2.63-7.27.09-.25.18-.5.15-.75h7.42c-.06.25.07.5.17.75.22.56,2.59,6.77,2.78,7.26,0,0,0,0,0,0ZM47.61,12.92c-.4-1.05-.74-1.94-.78-2.03-.07-.19-.13-.38-.17-.58,0,0-.06.32-.15.58-.03.08-.35.97-.73,2.03,0,0,1.83,0,1.83,0ZM12.12,7.38h-5.78c.13.25.13.5.13.75v7.27c0,.25,0,.5-.13.75h5.78c-.13-.25-.13-.5-.13-.75v-7.27c0-.25,0-.5.13-.75h0ZM52.11,8.08c0-.67.48-1.15,1.15-1.15s1.15.49,1.15,1.15-.48,1.15-1.15,1.15-1.15-.48-1.15-1.15ZM52.34,8.08c0,.51.39.92.92.92.5,0,.91-.39.92-.89,0-.01,0-.02,0-.03,0-.5-.39-.91-.89-.92-.01,0-.02,0-.03,0-.54,0-.92.39-.92.92ZM53.04,8.77h-.21v-1.38h.52c.24,0,.43.21.43.45,0,.17-.1.33-.24.41l.3.53h-.23l-.27-.49h-.3v.49h0ZM53.04,8.08h.28c.14,0,.26-.1.26-.24s-.12-.24-.26-.24h-.28v.49Z" style="fill:#0058a3; stroke-width:0px;"/><path d="M118.12,19.2V4.8h8.04v1.59h-6.23v5.1h5.84v1.59h-5.84v6.11h-1.82ZM132.52,19.4c-2.86,0-4.96-2.04-4.96-5.63s1.91-5.59,5.02-5.59c2.92,0,4.98,2.02,4.98,5.59s-1.96,5.63-5.04,5.63h0ZM132.57,17.93c2.18,0,3.16-1.59,3.16-4.15s-.99-4.09-3.19-4.09-3.14,1.53-3.14,4.09.97,4.15,3.16,4.15M148.89,19.2h-1.45l-.26-1.43h-.08c-.71,1.13-2.04,1.63-3.43,1.63-2.6,0-3.93-1.19-3.93-3.93v-7.08h1.79v6.96c0,1.74.77,2.58,2.4,2.58,2.4,0,3.18-1.39,3.18-3.91v-5.63h1.77v10.81h0,0ZM161,12.16v7.04h-1.75v-6.92c0-1.73-.77-2.6-2.42-2.6-2.4,0-3.16,1.39-3.16,3.91v5.61h-1.77v-10.81h1.43l.26,1.47h.1c.71-1.13,2.04-1.67,3.41-1.67,2.58,0,3.91,1.19,3.91,3.98M163.57,13.82c0-3.71,1.77-5.62,4.46-5.62,1.67,0,2.68.7,3.35,1.59h.12c-.04-.34-.12-1.19-.12-1.59V3.87h1.77v15.33h-1.43l-.26-1.45h-.08c-.64.93-1.67,1.66-3.37,1.66-2.68,0-4.43-1.88-4.43-5.59h0,0ZM171.39,14.16v-.32c0-2.68-.72-4.18-3.13-4.18-1.91,0-2.86,1.63-2.86,4.2s.95,4.07,2.88,4.07c2.28,0,3.1-1.25,3.1-3.77M184.36,11.84v7.36h-1.29l-.34-1.53h-.08c-.95,1.19-1.82,1.74-3.63,1.74-1.96,0-3.41-1.01-3.41-3.21s1.65-3.35,5.18-3.45l1.83-.06v-.64c0-1.8-.83-2.4-2.24-2.4-1.13,0-2.16.4-3.05.83l-.54-1.33c.95-.5,2.28-.93,3.69-.93,2.62,0,3.87,1.11,3.87,3.63,0,0,0,0,0,0ZM181.01,13.98c-2.7.1-3.57.87-3.57,2.24,0,1.21.8,1.75,1.97,1.75,1.81,0,3.18-.99,3.18-3.09v-.97l-1.59.06h0ZM192.74,17.73v1.35c-.39.18-1.17.32-1.82.32-1.69,0-3.16-.72-3.16-3.35v-6.29h-1.53v-.85l1.55-.7.71-2.3h1.05v2.48h3.13v1.37h-3.13v6.25c0,1.31.71,1.94,1.69,1.94.52,0,1.17-.1,1.51-.22h0ZM196.82,5.47c0,.77-.48,1.13-1.03,1.13-.58,0-1.05-.36-1.05-1.13s.46-1.13,1.05-1.13c.54,0,1.03.34,1.03,1.13h0ZM196.67,19.2h-1.77v-10.81h1.77v10.81ZM204.11,19.4c-2.86,0-4.96-2.04-4.96-5.63s1.91-5.59,5.02-5.59c2.92,0,4.98,2.02,4.98,5.59s-1.96,5.63-5.04,5.63h0ZM204.15,17.93c2.18,0,3.16-1.59,3.16-4.15s-.99-4.09-3.19-4.09-3.14,1.53-3.14,4.09.97,4.15,3.16,4.15M220.8,12.16v7.04h-1.75v-6.92c0-1.73-.77-2.6-2.42-2.6-2.4,0-3.16,1.39-3.16,3.91v5.61h-1.77v-10.81h1.43l.26,1.47h.1c.71-1.13,2.04-1.67,3.41-1.67,2.58,0,3.91,1.19,3.91,3.98M72.83,4.8h-1.81v14.4h1.81V4.8ZM85.14,19.2l-5.1-6.88-1.47,1.29v5.59h-1.81V4.8h1.81v7.1c.81-.91,1.65-1.82,2.48-2.74l3.89-4.36h2.11l-5.7,6.27,5.93,8.13h-2.14ZM89.24,19.2V4.8h8.04v1.59h-6.23v4.5h5.86v1.58h-5.86v5.14h6.23v1.59h-8.04ZM107.74,14.74h-5.7l-1.71,4.46h-1.83l5.62-14.46h1.63l5.6,14.46h-1.88s-1.73-4.46-1.73-4.46ZM105.58,8.77c-.25-.71-.47-1.43-.69-2.16-.18.73-.39,1.45-.62,2.16l-1.63,4.36h4.55s-1.61-4.36-1.61-4.36Z" style="fill:#000;"/></svg> | |||
| @ -0,0 +1,274 @@ | |||
| @font-face { | |||
| font-family: 'Material Symbols Outlined'; | |||
| font-style: normal; | |||
| src: url('MaterialSymbolsRounded[FILL,GRAD,opsz,wght].woff2') format('woff2'); | |||
| } | |||
| :root { | |||
| --icon: #777; | |||
| --icon-active: #f1f1f1; | |||
| --white: #e6edf3; | |||
| --black: #2d2d2d; | |||
| --error: #b24226; | |||
| --bg: #fff; | |||
| --audit: #888; | |||
| --inactive: #888; | |||
| --secondary: #888; | |||
| --action: #3c85c6; | |||
| --call-to-action: #D65535; | |||
| --enabled: rgb(26, 134, 58); | |||
| --disabled: #b24226; | |||
| --border: #ccc; | |||
| --hover: #f6f6f6; | |||
| --popup-bg: #282a2c; | |||
| --clr-black: #2d2d2d; | |||
| --clr-grey-100: #f4f4f5; | |||
| --clr-grey-200: #e4e4e7; | |||
| --clr-grey-300: #d4d4d8; | |||
| --clr-grey-400: #a1a1aa; | |||
| --clr-grey-500: #71717a; | |||
| --clr-grey-600: #52525b; | |||
| --clr-grey-700: #3f3f46; | |||
| --clr-grey-750: #2e2f31; | |||
| --font-icons: 'Material Symbols Outlined'; | |||
| --font-base: system-ui, sans-serif; | |||
| --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; | |||
| font: 10px/1.3 var(--font-base); | |||
| } | |||
| /*.material-symbols-outlined { | |||
| font-family: 'Material Symbols Outlined'; | |||
| font-weight: normal; | |||
| font-style: normal; | |||
| display: inline-block; | |||
| line-height: 1; | |||
| text-transform: none; | |||
| letter-spacing: normal; | |||
| word-wrap: normal; | |||
| white-space: nowrap; | |||
| direction: ltr; | |||
| }*/ | |||
| @keyframes rotate_animation { | |||
| from { | |||
| transform: rotate(0deg); | |||
| } | |||
| to { | |||
| transform: rotate(360deg); | |||
| } | |||
| } | |||
| body { | |||
| margin: 0; | |||
| background: var(--bg, #fff); | |||
| color: var(--black, #000); | |||
| -webkit-font-smoothing: antialiased; | |||
| font-optical-sizing: auto; | |||
| } | |||
| .icon, .node__flags { | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| &.action { | |||
| color: var(--action); | |||
| } | |||
| &.enabled { | |||
| color: var(--enabled); | |||
| } | |||
| &.disabled { | |||
| color: var(--disabled); | |||
| } | |||
| &.size--small { | |||
| font-size: 18px; | |||
| } | |||
| &.size--medium { | |||
| font-size: 20px; | |||
| } | |||
| } | |||
| a.icon { | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-weight: 400; | |||
| display: inline-flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| text-decoration: none; | |||
| width: 36px; | |||
| height: 36px; | |||
| border-radius: 50%; | |||
| color: var(--clr-grey-600); | |||
| &:hover { | |||
| color: var(--black); | |||
| background-color: var(--clr-grey-200); | |||
| } | |||
| } | |||
| button { | |||
| cursor: pointer; | |||
| } | |||
| #navbar { | |||
| background: var(--clr-grey-750); | |||
| position: fixed; | |||
| z-index: 999; | |||
| width: 80px; | |||
| inset: 0 auto 0 0; | |||
| padding: 10px 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| gap: 20px; | |||
| &>div { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| gap: 8px; | |||
| } | |||
| } | |||
| .navbar-link { | |||
| background: none; | |||
| border: none; | |||
| color: var(--icon); | |||
| text-decoration: none; | |||
| & .icon { | |||
| font-size: 32px; | |||
| } | |||
| &.open, | |||
| &.current { | |||
| .icon { | |||
| color: var(--icon-active); | |||
| } | |||
| } | |||
| } | |||
| turbo-frame { | |||
| display: block; | |||
| } | |||
| #main { | |||
| margin: 0 0 0 80px; | |||
| padding: 30px 40px; | |||
| } | |||
| h1, | |||
| .node-path { | |||
| font-size: 2.4rem; | |||
| line-height: 2; | |||
| font-weight: 400; | |||
| margin: 0 0 0 0; | |||
| } | |||
| .list-title-link { | |||
| text-decoration: none; | |||
| color: var(--black); | |||
| padding: 0 0.6em; | |||
| border-radius: 999px; | |||
| display: inline-block; | |||
| appearance: none; | |||
| background-color: transparent; | |||
| border: none; | |||
| font-size: inherit; | |||
| line-height: inherit; | |||
| } | |||
| a.list-title-link:hover { | |||
| background-color: var(--clr-grey-200); | |||
| } | |||
| #flash { | |||
| position: fixed; | |||
| left: 80px; | |||
| right: 0; | |||
| bottom: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 0px; | |||
| font-size: 1.6rem; | |||
| z-index: 100; | |||
| } | |||
| .flash__message { | |||
| color: var(--white, #fff); | |||
| background-color: rgba(119, 119, 119, 0.8); | |||
| animation: appear-then-fade 2s both; | |||
| padding: 10px 20px; | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| & .icon { | |||
| color: #f1f1f1; | |||
| font-size: 3.2rem; | |||
| } | |||
| } | |||
| @keyframes appear-then-fade { | |||
| 0%, | |||
| 100% { | |||
| opacity: 0; | |||
| } | |||
| 2%, | |||
| 90% { | |||
| opacity: 1; | |||
| } | |||
| } | |||
| .logo { | |||
| display: block; | |||
| transform-origin: center center; | |||
| width: 40px; | |||
| aspect-ratio: 40/368; | |||
| & svg { | |||
| rotate: 90deg; | |||
| display: block; | |||
| transform-origin: 0 0; | |||
| transform: translateY(-100%); | |||
| height: 40px; | |||
| } | |||
| } | |||
| @ -0,0 +1,134 @@ | |||
| #assets { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |||
| gap: 10px; | |||
| } | |||
| .asset { | |||
| text-align: center; | |||
| } | |||
| .asset__thumbnail { | |||
| position: relative; | |||
| width: 100%; | |||
| margin-bottom: 12px; | |||
| &::after { | |||
| content: ""; | |||
| display: block; | |||
| padding-bottom: 100%; | |||
| } | |||
| & div { | |||
| position: absolute; | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| & img { | |||
| display: block; | |||
| max-width: 100%; | |||
| max-height: 100%; | |||
| box-shadow: rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 3px 1px -2px, rgba(0, 0, 0, 0.2) 0px 1px 5px 0px; | |||
| } | |||
| } | |||
| .asset__title { | |||
| font-size: 1.4rem; | |||
| margin-bottom: 2px; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| word-break: normal; | |||
| overflow: hidden; | |||
| } | |||
| .asset__mimetypes { | |||
| font-size: 1.2rem; | |||
| font-family: var(--font-mono); | |||
| color: var(--action); | |||
| } | |||
| .assets-sort { | |||
| font-size: 1.4rem; | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| gap: 1rem; | |||
| align-items: center; | |||
| .popup-menu { | |||
| min-width: auto | |||
| } | |||
| & a { | |||
| font-size: 1.4rem; | |||
| white-space: nowrap; | |||
| } | |||
| } | |||
| .asset { | |||
| position: relative; | |||
| padding: 10px 10px 40px 10px; | |||
| border: 2px solid transparent; | |||
| &:hover { | |||
| border-color: var(--clr-grey-200); | |||
| border-radius: 10px; | |||
| & .asset-ctrls { | |||
| bottom: 6px; | |||
| position: absolute; | |||
| left: 10px; | |||
| right: 10px; | |||
| display: flex; | |||
| justify-content: space-between; | |||
| } | |||
| } | |||
| } | |||
| .icon-cb-round { | |||
| position: absolute; | |||
| inset: 0 0 0 0; | |||
| cursor: pointer; | |||
| display: none; | |||
| & span { | |||
| position: absolute; | |||
| right: 8px; | |||
| top: 8px; | |||
| width: 16px; | |||
| height: 16px; | |||
| outline: 2px solid var(--border); | |||
| outline-offset: 2px; | |||
| border-radius: 50%; | |||
| } | |||
| & input { | |||
| position: absolute; | |||
| height: 0; | |||
| width: 0; | |||
| clip: rect(0,0,0,0); | |||
| &:checked~span { | |||
| outline: 2px solid var(--action); | |||
| background-color: var(--action); | |||
| } | |||
| } | |||
| } | |||
| #overlay { | |||
| & .icon-cb-round { | |||
| display: block; | |||
| } | |||
| & .asset-ctrls { | |||
| display: none; | |||
| } | |||
| } | |||
| @ -0,0 +1,142 @@ | |||
| #attachments { | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 3.2rem; | |||
| } | |||
| .attachment { | |||
| position: relative; | |||
| padding-top: 14px; | |||
| & label { | |||
| font-size: 1.4rem; | |||
| line-height: 1.2; | |||
| color: #555; | |||
| display: block; | |||
| margin-bottom: 8px; | |||
| } | |||
| & trix-editor { | |||
| min-height: 15em; | |||
| /* max-height: 320px; | |||
| overflow: auto; */ | |||
| } | |||
| .handle { | |||
| appearance: none; | |||
| position: absolute; | |||
| top: 4px; | |||
| right: 40px; | |||
| border: none; | |||
| border-radius: 50%; | |||
| display: flex; | |||
| width: 32px; | |||
| height: 32px; | |||
| justify-content: center; | |||
| align-items: center; | |||
| font-family: var(--font-icons); | |||
| background-color: transparent; | |||
| font-size: 2.2rem; | |||
| color: var(--clr-grey-400); | |||
| &:hover { | |||
| background-color: var(--clr-grey-200); | |||
| color: var(--clr-grey-700); | |||
| } | |||
| } | |||
| } | |||
| .attachment__content:has(.attachment__asset) { | |||
| display: grid; | |||
| grid-template-columns: 120px 1fr; | |||
| gap: 0 20px; | |||
| & .trix-button--icon-decrease-nesting-level, | |||
| .trix-button--icon-increase-nesting-level, | |||
| .trix-button--icon-bullet-list, | |||
| .trix-button--icon-number-list { | |||
| display: none; | |||
| } | |||
| & trix-editor { | |||
| min-height: 8em; | |||
| } | |||
| .attachment__content-two { | |||
| grid-column-start: 2; | |||
| } | |||
| .attachment__content-three { | |||
| grid-column-start: 2; | |||
| } | |||
| } | |||
| .attachment-site:not(:has(.attachment__asset)) { | |||
| & .attachment__content-three { | |||
| &>*:nth-child(3), | |||
| &>*:nth-child(4) { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| .attachment-site:has(.attachment__asset) { | |||
| & .attachment__content-three { | |||
| &>*:nth-child(1), | |||
| &>*:nth-child(2) { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| .attachment-tile { | |||
| & .attachment__content-three { | |||
| &>*:nth-child(4) { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| #newsletter-attachments .attachment__content .field:nth-child(2) { | |||
| display: none; | |||
| } | |||
| #newsletter-attachments .attachment__content:has(.attachment__asset) { | |||
| & .attachment__content-three { | |||
| display: none; | |||
| } | |||
| } | |||
| .attachment__content .field { | |||
| display: flex; | |||
| flex-direction: column; | |||
| padding-bottom: 0; | |||
| &>div:nth-child(2) { | |||
| flex-basis: 100%; | |||
| flex-grow: 1; | |||
| width: 100%; | |||
| } | |||
| } | |||
| .attachment__content-two { | |||
| display: grid; | |||
| grid-template-columns: 1fr 1fr; | |||
| gap: 20px; | |||
| } | |||
| .attachment__content-three { | |||
| display: grid; | |||
| grid-template-columns: 1fr 1fr 1fr; | |||
| gap: 0 20px; | |||
| } | |||
| .attachment__asset { | |||
| margin-top: 3em; | |||
| text-align: center; | |||
| } | |||
| .trix__field { | |||
| width: 100%; | |||
| } | |||
| @ -0,0 +1,158 @@ | |||
| .subscribe__container { | |||
| min-height: 30svh; | |||
| } | |||
| .field { | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 0.4em; | |||
| &>div:nth-child(1) { | |||
| & label { | |||
| font-size: 1.6rem; | |||
| line-height: 1.2; | |||
| color: var(--clr-grey-600); | |||
| } | |||
| } | |||
| & .field_with_errors { | |||
| --border: var(--clr-error); | |||
| } | |||
| & + .field, | |||
| & + .input__fields-two { | |||
| margin-top: 1.2em; | |||
| } | |||
| &:has(input[type=submit]) { | |||
| margin: 2em 0; | |||
| display: block; | |||
| } | |||
| } | |||
| input[type=submit] { | |||
| font-family: var(--ff-base); | |||
| font-size: 1.6rem; | |||
| background-color: var(--clr-grey-800); | |||
| color: var(--clr-white-200); | |||
| padding: 1em 1.2em; | |||
| border: none; | |||
| appearance: none; | |||
| border-radius: 2px; | |||
| cursor: pointer; | |||
| } | |||
| .material__input { | |||
| display: flex; | |||
| font-family: var(--ff-base); | |||
| border: 1px solid var(--clr-border); | |||
| font-size: 1.6rem; | |||
| letter-spacing: 0.00625em; | |||
| border: none !important; | |||
| background-color: transparent; | |||
| width: 100%; | |||
| height: 100%; | |||
| appearance: none; | |||
| padding: 0; | |||
| box-shadow: none; | |||
| &:focus { | |||
| outline: none; | |||
| } | |||
| } | |||
| textarea.material__input { | |||
| resize: none; | |||
| } | |||
| .input-box { | |||
| display: flex; | |||
| height: 46px; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| align-items: center; | |||
| padding: 0 16px; | |||
| margin: 0; | |||
| overflow: visible; | |||
| -webkit-box-align: baseline; | |||
| --outline: var(--clr-action); | |||
| border-radius: 4px; | |||
| border: 1px solid var(--clr-border); | |||
| & .field_with_errors { | |||
| display: flex; | |||
| height: 100%; | |||
| width: 100%; | |||
| } | |||
| &:has(textarea) { | |||
| padding: 0 0 0 8px; | |||
| height: auto; | |||
| & textarea { | |||
| line-height: 1.2; | |||
| margin: 1px 0; | |||
| padding: 8px 16px 8px 0; | |||
| } | |||
| } | |||
| &:hover { | |||
| border-color: var(--clr-grey-500); | |||
| } | |||
| &:focus-within { | |||
| outline: 2px solid var(--outline); | |||
| outline-offset: -2px; | |||
| } | |||
| &:has(.field_with_errors) { | |||
| --outline: var(--clr-error); | |||
| &:hover { | |||
| border-color: var(--clr-error); | |||
| } | |||
| } | |||
| } | |||
| p[role="alert"] { | |||
| margin: 4px 0 0 0; | |||
| color: var(--clr-error); | |||
| font-size: 1.2rem; | |||
| } | |||
| ul.errors { | |||
| color: var(--clr-error); | |||
| margin: 0 0 1.2em 0; | |||
| } | |||
| p[role="tooltip"] { | |||
| margin: 4px 0 0 0; | |||
| color: var(--clr-grey-500); | |||
| font-size: 1.2rem; | |||
| } | |||
| .form__errors { | |||
| margin: 0 0 1.2em 0; | |||
| color: var(--clr-error); | |||
| padding: 0; | |||
| list-style: none; | |||
| } | |||
| .input__fields-two { | |||
| display: grid; | |||
| gap: 2em; | |||
| grid-template-columns: 1fr 2fr; | |||
| & .field + .field { | |||
| margin-top: 0; | |||
| } | |||
| } | |||
| .input__fields-two + .field { | |||
| margin-top: 1.2em; | |||
| } | |||
| @ -0,0 +1,436 @@ | |||
| .entries-info { | |||
| font-size: 2.2rem; | |||
| font-weight: 500; | |||
| margin: 0; | |||
| } | |||
| .audit-date, .user__role { | |||
| color: var(--audit); | |||
| font-size: 1.4rem; | |||
| } | |||
| .date { | |||
| color: var(--audit); | |||
| font-size: 1.4rem; | |||
| } | |||
| .list-container { | |||
| padding-top: 0em; | |||
| margin-top: 2em; | |||
| } | |||
| .list-ctrls { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| } | |||
| .list { | |||
| padding: 2em 0; | |||
| } | |||
| .list-small { | |||
| padding: 0; | |||
| & .list-header, & .row, & .audit-date { | |||
| font-size: 1.2rem; | |||
| } | |||
| & .icon { | |||
| font-size: 1.8rem; | |||
| } | |||
| } | |||
| .list-header, | |||
| .row { | |||
| display: flex; | |||
| justify-content: stretch; | |||
| align-items: center; | |||
| } | |||
| .list-header { | |||
| font-size: 1.4rem; | |||
| line-height: 1.2; | |||
| color: var(--black); | |||
| min-height: 38px; | |||
| border-bottom: 1px solid var(--border); | |||
| } | |||
| .sort_link { | |||
| font-size: 1.4rem; | |||
| text-decoration: none; | |||
| color: var(--black); | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 0.4em; | |||
| border-radius: 999px; | |||
| padding: 0.4em 1em; | |||
| margin-left: -1em; | |||
| &:hover { | |||
| background-color: var(--clr-grey-200); | |||
| } | |||
| &.current { | |||
| position: relative; | |||
| color: #000; | |||
| &::after { | |||
| content: "arrow_downward"; | |||
| font: 1.6rem var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| color: var(--black); | |||
| } | |||
| &.desc::after { | |||
| content: "arrow_upward"; | |||
| } | |||
| } | |||
| } | |||
| .popup-menu .sort_link { | |||
| border-radius: 0px; | |||
| margin-left: 0; | |||
| } | |||
| .row { | |||
| border-bottom: 1px solid var(--border); | |||
| min-height: 48px; | |||
| box-sizing: border-box; | |||
| padding: 4px 0; | |||
| font-size: 1.6rem; | |||
| & .actions { | |||
| & a, | |||
| button { | |||
| display: none; | |||
| } | |||
| } | |||
| &.sortable-drag { | |||
| border-bottom: none; | |||
| background-color: var(--bg); | |||
| & .handle { | |||
| display: inline-flex; | |||
| } | |||
| } | |||
| } | |||
| .handle { | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| display: none; | |||
| font-size: 20px; | |||
| justify-content: center; | |||
| align-items: center; | |||
| text-decoration: none; | |||
| width: 36px; | |||
| height: 36px; | |||
| border-radius: 50%; | |||
| cursor: move; | |||
| &:hover { | |||
| background-color: var(--clr-grey-200); | |||
| } | |||
| } | |||
| .list:not(:has(.row.sortable-chosen)) { | |||
| & .row:hover { | |||
| background-color: var(--hover); | |||
| & .actions { | |||
| & a, | |||
| button, | |||
| .handle { | |||
| display: inline-flex; | |||
| flex-shrink: 0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .cell { | |||
| padding: 0 8px; | |||
| flex: 1 1 0px; | |||
| & ul { | |||
| margin: 8px 0; | |||
| padding: 0; | |||
| list-style: none; | |||
| & li { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 0.4em; | |||
| } | |||
| } | |||
| &.flags { | |||
| flex: 0 0 auto; | |||
| width: 40px; | |||
| } | |||
| &.with-cb { | |||
| width: 40px; | |||
| flex: 0 0 auto; | |||
| } | |||
| &.mono { | |||
| font-size: 1.2rem; | |||
| font-family: var(--font-mono); | |||
| } | |||
| &.actions { | |||
| flex: 0 0 auto; | |||
| text-align: right; | |||
| width: 72px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| } | |||
| &.actions-one { | |||
| width: 40px; | |||
| } | |||
| } | |||
| .node__flags { | |||
| font-size: 20px; | |||
| color: var(--clr-grey-400); | |||
| font-variation-settings: 'FILL' 0; | |||
| display: flex; | |||
| gap: 0; | |||
| } | |||
| .back-link { | |||
| color: var(--secondary); | |||
| text-decoration: none; | |||
| font-size: 1.6rem; | |||
| display: inline-flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| &::before { | |||
| content: "arrow_back"; | |||
| font: 1.6rem/1 var(--font-icons); | |||
| color: var(--secondary); | |||
| margin-right: 0.4em; | |||
| } | |||
| } | |||
| .pagination-container { | |||
| color: var(--inactive); | |||
| padding: 20px 0; | |||
| display: flex; | |||
| justify-content: end; | |||
| align-items: center; | |||
| gap: 20px; | |||
| font-size: 1.4rem; | |||
| } | |||
| .pagination { | |||
| font: normal 3.2rem/40px var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| display: flex; | |||
| height: 40px; | |||
| margin-right: 8px; | |||
| align-items: center; | |||
| & span { | |||
| color: var(--border); | |||
| width: 40px; | |||
| height: 40px; | |||
| } | |||
| & a { | |||
| display: block; | |||
| width: 40px; | |||
| height: 40px; | |||
| text-align: center; | |||
| color: var(--secondary); | |||
| text-decoration: none; | |||
| &:hover { | |||
| color: var(--black); | |||
| } | |||
| } | |||
| } | |||
| .category-selector { | |||
| font-size: 1.5rem; | |||
| line-height: 1.6; | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 0.4em; | |||
| & a { | |||
| color: var(--black, #000); | |||
| text-decoration: none; | |||
| padding: 0.4em 0.8em; | |||
| border-radius: 999px; | |||
| background-color: var(--clr-grey-200); | |||
| &.current { | |||
| background-color: var(--clr-grey-750, #000); | |||
| color: var(--white, #FFF); | |||
| & span::before { | |||
| content: 'done'; | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 2.4rem; | |||
| line-height: 1; | |||
| margin-right: 2px; | |||
| vertical-align: bottom; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .list-title { | |||
| display: flex; | |||
| align-items: flex-start; | |||
| justify-content: space-between; | |||
| margin-left: -0.6em; | |||
| } | |||
| .node-path { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 0.2em; | |||
| &>* { | |||
| height: 48px; | |||
| } | |||
| & .list-title-link[data-icon] { | |||
| display: inline-flex; | |||
| gap: 6px; | |||
| align-items: center; | |||
| &::before { | |||
| color: var(--data-icon-color, #717274); | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 3.2rem; | |||
| line-height: 1; | |||
| content: attr(data-icon); | |||
| } | |||
| } | |||
| & .has-popup-menu { | |||
| &::after { | |||
| color: var(--black); | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 2.8rem; | |||
| line-height: 32px; | |||
| width: 20px; | |||
| overflow: visible; | |||
| content: 'expand_more'; | |||
| } | |||
| } | |||
| } | |||
| .list-title .flex { | |||
| display: flex; | |||
| gap: 1em; | |||
| & form { | |||
| padding: 0; | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 4px; | |||
| &>div { | |||
| display: flex; | |||
| flex-shrink: 0; | |||
| flex-grow: 1; | |||
| height: 48px; | |||
| background-color: var(--clr-grey-200); | |||
| border-radius: 999px; | |||
| padding: 0 4px 0 1em; | |||
| justify-content: start; | |||
| align-items: center; | |||
| gap: 8px; | |||
| max-width: 300px; | |||
| &:focus-within { | |||
| outline: 2px solid var(--action); | |||
| } | |||
| } | |||
| & input[type="text"] { | |||
| background-color: transparent; | |||
| border: none; | |||
| flex-grow: 1; | |||
| font-size: 1.6rem; | |||
| line-height: 36px; | |||
| height: 36px; | |||
| padding: 0 0.4em; | |||
| outline: none; | |||
| } | |||
| & button, | |||
| a { | |||
| font-size: 28px; | |||
| line-height: 40px; | |||
| text-align: center; | |||
| width: 40px; | |||
| height: 40px; | |||
| padding: 0; | |||
| border: none; | |||
| background: none; | |||
| color: var(--inactive); | |||
| outline: none; | |||
| border-radius: 50%; | |||
| &:focus { | |||
| background-color: var(--clr-grey-300); | |||
| color: var(--black); | |||
| } | |||
| &:hover { | |||
| background-color: var(--clr-grey-300); | |||
| color: var(--black); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .search__collection:has(input:placeholder-shown) { | |||
| & .reset__search { | |||
| display: none; | |||
| } | |||
| } | |||
| #load-more { | |||
| height: 1em; | |||
| } | |||
| .utm { | |||
| margin-top: 8px; | |||
| font-size: 1.2rem; | |||
| font-family: var(--font-mono); | |||
| & .tag { | |||
| font-size: 1.2rem; | |||
| } | |||
| } | |||
| @ -0,0 +1,238 @@ | |||
| #drawer { | |||
| position: fixed; | |||
| top: 0; | |||
| left: 80px; | |||
| width: 320px; | |||
| bottom: 0; | |||
| padding: 16px 0; | |||
| background-color: #28292A; | |||
| color: var(--white, #FFF); | |||
| overflow: auto; | |||
| &::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| &::-webkit-scrollbar-track { | |||
| background-color: #28292A; | |||
| } | |||
| &::-webkit-scrollbar-thumb { | |||
| background-color: #BDC1C6; | |||
| outline: 1px solid #BDC1C6; | |||
| border-radius: 8px; | |||
| } | |||
| & h1 { | |||
| margin: 0; | |||
| font: 1.4rem/1 var(--font-base); | |||
| } | |||
| &:hover { | |||
| --clr-spacer: #444; | |||
| } | |||
| } | |||
| #drawer-structure { | |||
| font-size: 1.4rem; | |||
| line-height: 1.5; | |||
| margin: 0 8px; | |||
| } | |||
| .node-header { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 8px 10px; | |||
| } | |||
| .node { | |||
| & a { | |||
| color: var(--white, #fff); | |||
| text-decoration: none; | |||
| } | |||
| &.current>.node-row:nth-child(1) { | |||
| background-color: #37383A; | |||
| } | |||
| } | |||
| .node-row { | |||
| --level: 0; | |||
| --toggle-width: 1.6rem; | |||
| display: grid; | |||
| grid-template-columns: calc(var(--level) * (var(--toggle-width) / 2)) var(--toggle-width) 1fr; | |||
| grid-template-areas: "spacer toggle content"; | |||
| min-height: 3.2rem; | |||
| padding: 0 10px 0 0; | |||
| cursor: pointer; | |||
| &:hover { | |||
| background-color: #37383A; | |||
| border-radius: 4px; | |||
| } | |||
| .child { | |||
| color: var(--clr-grey-400); | |||
| font: 1.8rem/1 var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| grid-area: toggle; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .parent { | |||
| &:hover { | |||
| background-color: var(--clr-grey-600); | |||
| border-radius: 4px 0 0 4px; | |||
| } | |||
| & span:nth-child(1) { | |||
| display: inherit; | |||
| } | |||
| & span:nth-child(2) { | |||
| display: none; | |||
| } | |||
| } | |||
| &.closed { | |||
| .parent { | |||
| & span:nth-child(1) { | |||
| display: none; | |||
| } | |||
| & span:nth-child(2) { | |||
| display: inherit; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .node-title { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 4px; | |||
| grid-area: content; | |||
| letter-spacing: 0.00625em; | |||
| text-overflow: ellipsis; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| word-break: normal; | |||
| &::before { | |||
| color: var(--data-icon-color, #717274); | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 2.0rem; | |||
| content: attr(data-icon); | |||
| } | |||
| } | |||
| .node-link { | |||
| text-decoration: none; | |||
| color: var(--black); | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| &:not(:has(img))::before { | |||
| color: var(--data-icon-color, #717274); | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 2.4rem; | |||
| line-height: 1.5; | |||
| content: attr(data-icon); | |||
| } | |||
| &:has(img) { | |||
| & img { | |||
| width: 24px; | |||
| height: 36px; | |||
| object-fit: contain; | |||
| } | |||
| } | |||
| } | |||
| [data-icon=inventory_2] { | |||
| --data-icon-color: #886F65; | |||
| } | |||
| [data-icon=folder] { | |||
| --data-icon-color: #EC5F59; | |||
| } | |||
| [data-icon=book_2] { | |||
| --data-icon-color: #F8CA4F; | |||
| } | |||
| [data-icon=description] { | |||
| --data-icon-color: #94A3AD; | |||
| } | |||
| .spacer { | |||
| grid-area: spacer; | |||
| display: flex; | |||
| justify-content: stretch; | |||
| & div { | |||
| width: 100%; | |||
| height: 100%; | |||
| border-right: 1px solid var(--clr-spacer, transparent); | |||
| } | |||
| } | |||
| .closed~.node { | |||
| display: none; | |||
| } | |||
| .node-ctrls { | |||
| display: flex; | |||
| gap: 8px; | |||
| & button { | |||
| font: 1.6rem/1 var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| } | |||
| } | |||
| #tree { | |||
| margin-left: 320px; | |||
| } | |||
| .tree-title { | |||
| font-size: 1.6rem; | |||
| font-weight: 600; | |||
| display: flex; | |||
| justify-content: start; | |||
| gap: 0.2em; | |||
| & a { | |||
| color: var(--action); | |||
| text-decoration: none; | |||
| &:hover { | |||
| text-decoration: underline; | |||
| } | |||
| } | |||
| } | |||
| .tree-children { | |||
| font-size: 1.6rem; | |||
| } | |||
| @ -0,0 +1,86 @@ | |||
| .popup-menu { | |||
| position: absolute; | |||
| display: none; | |||
| background-color: var(--bg); | |||
| padding: 0; | |||
| overflow: hidden; | |||
| border-radius: 4px; | |||
| font-size: 1.6rem; | |||
| line-height: 1.2; | |||
| min-width: 240px; | |||
| &.open { | |||
| display: block; | |||
| z-index: 1000; | |||
| box-shadow: 0px 0px 1.5px rgba(0, 0, 0, 0.105), 0px 1px 3px rgba(0, 0, 0, 0.21); | |||
| } | |||
| & ul { | |||
| margin: 0; | |||
| padding: 0; | |||
| list-style: none; | |||
| & .with-icon { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| } | |||
| } | |||
| & li>* { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| } | |||
| & li>div { | |||
| padding: 2px 12px; | |||
| min-height: 40px; | |||
| } | |||
| & form.button_to { | |||
| display: block; | |||
| } | |||
| & a, button[type="submit"] { | |||
| display: flex; | |||
| appearance: none; | |||
| border: none; | |||
| font-size: 1.6rem; | |||
| justify-content: flex-start; | |||
| flex-grow: 1; | |||
| align-items: center; | |||
| color: var(--black, #000); | |||
| text-decoration: none; | |||
| gap: 0.4em; | |||
| padding: 2px 12px; | |||
| min-height: 40px; | |||
| &[data-icon]::before { | |||
| color: var(--data-icon-color); | |||
| font-family: var(--font-icons); | |||
| font-variation-settings: 'opsz' 24, 'wght' 300, 'FILL' 1, 'GRAD' 0; | |||
| font-size: 2.4rem; | |||
| line-height: 1; | |||
| content: attr(data-icon); | |||
| } | |||
| & .icon { | |||
| font-size: 2.8rem; | |||
| color: var(--clr-grey-800); | |||
| } | |||
| } | |||
| & a, button[type="submit"] { | |||
| &:hover { | |||
| background-color: var(--clr-grey-300); | |||
| } | |||
| } | |||
| & button[type="submit"] { | |||
| background-color: transparent; | |||
| width: 100%; | |||
| } | |||
| } | |||
| @ -0,0 +1,172 @@ | |||
| :root { | |||
| font: 400 10px/1.3 system-ui, sans-serif; | |||
| --red: #B24226; | |||
| --border: #ccc; | |||
| --font-base: system-ui, sans-serif; | |||
| } | |||
| body { | |||
| background: #E4E4E4; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| margin: 0; | |||
| } | |||
| html, | |||
| body { | |||
| height: 100%; | |||
| } | |||
| main { | |||
| max-width: 360px; | |||
| width: 100%; | |||
| background: white; | |||
| padding: 40px 32px 40px 32px; | |||
| font-size: 1.6rem; | |||
| box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2); | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 20px; | |||
| } | |||
| svg { | |||
| display: block; | |||
| width: 100%; | |||
| height: auto; | |||
| margin: 0 auto; | |||
| } | |||
| label { | |||
| font-size: 1.4rem; | |||
| color: #666; | |||
| order: 0; | |||
| } | |||
| input[type="text"], | |||
| input[type="password"] { | |||
| display: flex; | |||
| border: 1px solid var(--border); | |||
| font-family: var(--font-base); | |||
| font-size: 1.6rem; | |||
| letter-spacing: 0.00625em; | |||
| border: none !important; | |||
| background-color: transparent; | |||
| width: 100%; | |||
| height: 100%; | |||
| appearance: none; | |||
| padding: 0; | |||
| box-shadow: none; | |||
| &:focus { | |||
| outline: none; | |||
| } | |||
| } | |||
| .input-box { | |||
| display: flex; | |||
| height: 46px; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| align-items: baseline; | |||
| padding: 0 16px; | |||
| margin: 8px 0 0 0; | |||
| overflow: visible; | |||
| -webkit-box-align: baseline; | |||
| --outline: var(--action); | |||
| border-radius: 4px; | |||
| border: 1px solid var(--border); | |||
| & .field_with_errors { | |||
| display: flex; | |||
| height: 100%; | |||
| width: 100%; | |||
| } | |||
| &:hover { | |||
| border-color: var(--inactive); | |||
| } | |||
| &:focus-within { | |||
| outline: 2px solid var(--outline); | |||
| outline-offset: -2px; | |||
| } | |||
| &:has(.field_with_errors) { | |||
| --outline: var(--error); | |||
| &:hover { | |||
| border-color: var(--error); | |||
| } | |||
| } | |||
| } | |||
| button[type=submit] { | |||
| background: #333; | |||
| border: none; | |||
| outline: none; | |||
| font: 400 1.6rem system-ui, sans-serif; | |||
| padding: 8px 20px; | |||
| border-radius: 4px; | |||
| cursor: pointer; | |||
| color: #FFF; | |||
| &:active { | |||
| background-color: #999; | |||
| } | |||
| } | |||
| p[role=alert] { | |||
| margin: 4px 0 0 0; | |||
| color: var(--red); | |||
| font-size: 1.4rem; | |||
| order: 3; | |||
| } | |||
| form { | |||
| margin: 0; | |||
| &>div { | |||
| margin-top: 32px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| &:last-of-type { | |||
| text-align: right; | |||
| display: block; | |||
| } | |||
| } | |||
| } | |||
| .with-errors { | |||
| & label { | |||
| color: var(--red); | |||
| } | |||
| & input[type=text], | |||
| input[type=password] { | |||
| border-color: var(--red); | |||
| &:focus { | |||
| border-color: var(--red); | |||
| &+label { | |||
| color: var(--red); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| footer { | |||
| text-align: right; | |||
| margin-top: 40px; | |||
| font-size: 1.4rem; | |||
| color: #666; | |||
| } | |||
| @ -0,0 +1,412 @@ | |||
| /** | |||
| * tom-select.css (v2.3.1) | |||
| * Copyright (c) contributors | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this | |||
| * file except in compliance with the License. You may obtain a copy of the License at: | |||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, software distributed under | |||
| * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF | |||
| * ANY KIND, either express or implied. See the License for the specific language | |||
| * governing permissions and limitations under the License. | |||
| * | |||
| */ | |||
| .ts-control { | |||
| border: 1px solid #d0d0d0; | |||
| padding: 8px 8px; | |||
| width: 100%; | |||
| overflow: hidden; | |||
| position: relative; | |||
| z-index: 1; | |||
| box-sizing: border-box; | |||
| box-shadow: none; | |||
| border-radius: 3px; | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| } | |||
| .ts-wrapper.multi.has-items .ts-control { | |||
| padding: calc(8px - 2px - 0) 8px calc(8px - 2px - 3px - 0); | |||
| } | |||
| .full .ts-control { | |||
| background-color: #fff; | |||
| } | |||
| .disabled .ts-control, .disabled .ts-control * { | |||
| cursor: default !important; | |||
| } | |||
| .focus .ts-control { | |||
| box-shadow: none; | |||
| } | |||
| .ts-control > * { | |||
| vertical-align: baseline; | |||
| display: inline-block; | |||
| } | |||
| .ts-wrapper.multi .ts-control > div { | |||
| cursor: pointer; | |||
| margin: 0 3px 3px 0; | |||
| padding: 2px 6px; | |||
| background: #f2f2f2; | |||
| color: #303030; | |||
| border: 0 solid #d0d0d0; | |||
| } | |||
| .ts-wrapper.multi .ts-control > div.active { | |||
| background: #e8e8e8; | |||
| color: #303030; | |||
| border: 0 solid #cacaca; | |||
| } | |||
| .ts-wrapper.multi.disabled .ts-control > div, .ts-wrapper.multi.disabled .ts-control > div.active { | |||
| color: #7d7d7d; | |||
| background: white; | |||
| border: 0 solid white; | |||
| } | |||
| .ts-control > input { | |||
| flex: 1 1 auto; | |||
| min-width: 7rem; | |||
| display: inline-block !important; | |||
| padding: 0 !important; | |||
| min-height: 0 !important; | |||
| max-height: none !important; | |||
| max-width: 100% !important; | |||
| margin: 0 !important; | |||
| text-indent: 0 !important; | |||
| border: 0 none !important; | |||
| background: none !important; | |||
| line-height: inherit !important; | |||
| -webkit-user-select: auto !important; | |||
| -moz-user-select: auto !important; | |||
| -ms-user-select: auto !important; | |||
| user-select: auto !important; | |||
| box-shadow: none !important; | |||
| } | |||
| .ts-control > input::-ms-clear { | |||
| display: none; | |||
| } | |||
| .ts-control > input:focus { | |||
| outline: none !important; | |||
| } | |||
| .has-items .ts-control > input { | |||
| margin: 0 4px !important; | |||
| } | |||
| .ts-control.rtl { | |||
| text-align: right; | |||
| } | |||
| .ts-control.rtl.single .ts-control:after { | |||
| left: 15px; | |||
| right: auto; | |||
| } | |||
| .ts-control.rtl .ts-control > input { | |||
| margin: 0 4px 0 -2px !important; | |||
| } | |||
| .disabled .ts-control { | |||
| opacity: 0.5; | |||
| background-color: #fafafa; | |||
| } | |||
| .input-hidden .ts-control > input { | |||
| opacity: 0; | |||
| position: absolute; | |||
| left: -10000px; | |||
| } | |||
| .ts-dropdown { | |||
| position: absolute; | |||
| top: 100%; | |||
| left: 0; | |||
| width: 100%; | |||
| z-index: 10; | |||
| border: 1px solid #d0d0d0; | |||
| background: #fff; | |||
| margin: 0.25rem 0 0; | |||
| border-top: 0 none; | |||
| box-sizing: border-box; | |||
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | |||
| border-radius: 0 0 3px 3px; | |||
| } | |||
| .ts-dropdown [data-selectable] { | |||
| cursor: pointer; | |||
| overflow: hidden; | |||
| } | |||
| .ts-dropdown [data-selectable] .highlight { | |||
| background: rgba(125, 168, 208, 0.2); | |||
| border-radius: 1px; | |||
| } | |||
| .ts-dropdown .option, | |||
| .ts-dropdown .optgroup-header, | |||
| .ts-dropdown .no-results, | |||
| .ts-dropdown .create { | |||
| padding: 5px 8px; | |||
| } | |||
| .ts-dropdown .option, .ts-dropdown [data-disabled], .ts-dropdown [data-disabled] [data-selectable].option { | |||
| cursor: inherit; | |||
| opacity: 0.5; | |||
| } | |||
| .ts-dropdown [data-selectable].option { | |||
| opacity: 1; | |||
| cursor: pointer; | |||
| } | |||
| .ts-dropdown .optgroup:first-child .optgroup-header { | |||
| border-top: 0 none; | |||
| } | |||
| .ts-dropdown .optgroup-header { | |||
| color: #303030; | |||
| background: #fff; | |||
| cursor: default; | |||
| } | |||
| .ts-dropdown .active { | |||
| background-color: #f5fafd; | |||
| color: #495c68; | |||
| } | |||
| .ts-dropdown .active.create { | |||
| color: #495c68; | |||
| } | |||
| .ts-dropdown .create { | |||
| color: rgba(48, 48, 48, 0.5); | |||
| } | |||
| .ts-dropdown .spinner { | |||
| display: inline-block; | |||
| width: 30px; | |||
| height: 30px; | |||
| margin: 5px 8px; | |||
| } | |||
| .ts-dropdown .spinner::after { | |||
| content: " "; | |||
| display: block; | |||
| width: 24px; | |||
| height: 24px; | |||
| margin: 3px; | |||
| border-radius: 50%; | |||
| border: 5px solid #d0d0d0; | |||
| border-color: #d0d0d0 transparent #d0d0d0 transparent; | |||
| animation: lds-dual-ring 1.2s linear infinite; | |||
| } | |||
| @keyframes lds-dual-ring { | |||
| 0% { | |||
| transform: rotate(0deg); | |||
| } | |||
| 100% { | |||
| transform: rotate(360deg); | |||
| } | |||
| } | |||
| .ts-dropdown-content { | |||
| overflow: hidden auto; | |||
| max-height: 200px; | |||
| scroll-behavior: smooth; | |||
| } | |||
| .ts-wrapper.plugin-drag_drop .ts-dragging { | |||
| color: transparent !important; | |||
| } | |||
| .ts-wrapper.plugin-drag_drop .ts-dragging > * { | |||
| visibility: hidden !important; | |||
| } | |||
| .plugin-checkbox_options:not(.rtl) .option input { | |||
| margin-right: 0.5rem; | |||
| } | |||
| .plugin-checkbox_options.rtl .option input { | |||
| margin-left: 0.5rem; | |||
| } | |||
| /* stylelint-disable function-name-case */ | |||
| .plugin-clear_button { | |||
| --ts-pr-clear-button: 1em; | |||
| } | |||
| .plugin-clear_button .clear-button { | |||
| opacity: 0; | |||
| position: absolute; | |||
| top: 50%; | |||
| transform: translateY(-50%); | |||
| right: calc(8px - 6px); | |||
| margin-right: 0 !important; | |||
| background: transparent !important; | |||
| transition: opacity 0.5s; | |||
| cursor: pointer; | |||
| } | |||
| .plugin-clear_button.form-select .clear-button, .plugin-clear_button.single .clear-button { | |||
| right: max(var(--ts-pr-caret), 8px); | |||
| } | |||
| .plugin-clear_button.focus.has-items .clear-button, .plugin-clear_button:not(.disabled):hover.has-items .clear-button { | |||
| opacity: 1; | |||
| } | |||
| .ts-wrapper .dropdown-header { | |||
| position: relative; | |||
| padding: 10px 8px; | |||
| border-bottom: 1px solid #d0d0d0; | |||
| background: color-mix(#fff, #d0d0d0, 85%); | |||
| border-radius: 3px 3px 0 0; | |||
| } | |||
| .ts-wrapper .dropdown-header-close { | |||
| position: absolute; | |||
| right: 8px; | |||
| top: 50%; | |||
| color: #303030; | |||
| opacity: 0.4; | |||
| margin-top: -12px; | |||
| line-height: 20px; | |||
| font-size: 20px !important; | |||
| } | |||
| .ts-wrapper .dropdown-header-close:hover { | |||
| color: black; | |||
| } | |||
| .plugin-dropdown_input.focus.dropdown-active .ts-control { | |||
| box-shadow: none; | |||
| border: 1px solid #d0d0d0; | |||
| } | |||
| .plugin-dropdown_input .dropdown-input { | |||
| border: 1px solid #d0d0d0; | |||
| border-width: 0 0 1px; | |||
| display: block; | |||
| padding: 8px 8px; | |||
| box-shadow: none; | |||
| width: 100%; | |||
| background: transparent; | |||
| } | |||
| .plugin-dropdown_input .items-placeholder { | |||
| border: 0 none !important; | |||
| box-shadow: none !important; | |||
| width: 100%; | |||
| } | |||
| .plugin-dropdown_input.has-items .items-placeholder, .plugin-dropdown_input.dropdown-active .items-placeholder { | |||
| display: none !important; | |||
| } | |||
| .ts-wrapper.plugin-input_autogrow.has-items .ts-control > input { | |||
| min-width: 0; | |||
| } | |||
| .ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input { | |||
| flex: none; | |||
| min-width: 4px; | |||
| } | |||
| .ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::-ms-input-placeholder { | |||
| color: transparent; | |||
| } | |||
| .ts-wrapper.plugin-input_autogrow.has-items.focus .ts-control > input::placeholder { | |||
| color: transparent; | |||
| } | |||
| .ts-dropdown.plugin-optgroup_columns .ts-dropdown-content { | |||
| display: flex; | |||
| } | |||
| .ts-dropdown.plugin-optgroup_columns .optgroup { | |||
| border-right: 1px solid #f2f2f2; | |||
| border-top: 0 none; | |||
| flex-grow: 1; | |||
| flex-basis: 0; | |||
| min-width: 0; | |||
| } | |||
| .ts-dropdown.plugin-optgroup_columns .optgroup:last-child { | |||
| border-right: 0 none; | |||
| } | |||
| .ts-dropdown.plugin-optgroup_columns .optgroup::before { | |||
| display: none; | |||
| } | |||
| .ts-dropdown.plugin-optgroup_columns .optgroup-header { | |||
| border-top: 0 none; | |||
| } | |||
| .ts-wrapper.plugin-remove_button .item { | |||
| display: inline-flex; | |||
| align-items: center; | |||
| } | |||
| .ts-wrapper.plugin-remove_button .item .remove { | |||
| color: inherit; | |||
| text-decoration: none; | |||
| vertical-align: middle; | |||
| display: inline-block; | |||
| padding: 0 6px; | |||
| border-radius: 0 2px 2px 0; | |||
| box-sizing: border-box; | |||
| } | |||
| .ts-wrapper.plugin-remove_button .item .remove:hover { | |||
| background: rgba(0, 0, 0, 0.05); | |||
| } | |||
| .ts-wrapper.plugin-remove_button.disabled .item .remove:hover { | |||
| background: none; | |||
| } | |||
| .ts-wrapper.plugin-remove_button .remove-single { | |||
| position: absolute; | |||
| right: 0; | |||
| top: 0; | |||
| font-size: 23px; | |||
| } | |||
| .ts-wrapper.plugin-remove_button:not(.rtl) .item { | |||
| padding-right: 0 !important; | |||
| } | |||
| .ts-wrapper.plugin-remove_button:not(.rtl) .item .remove { | |||
| border-left: 1px solid #d0d0d0; | |||
| margin-left: 6px; | |||
| } | |||
| .ts-wrapper.plugin-remove_button:not(.rtl) .item.active .remove { | |||
| border-left-color: #cacaca; | |||
| } | |||
| .ts-wrapper.plugin-remove_button:not(.rtl).disabled .item .remove { | |||
| border-left-color: white; | |||
| } | |||
| .ts-wrapper.plugin-remove_button.rtl .item { | |||
| padding-left: 0 !important; | |||
| } | |||
| .ts-wrapper.plugin-remove_button.rtl .item .remove { | |||
| border-right: 1px solid #d0d0d0; | |||
| margin-right: 6px; | |||
| } | |||
| .ts-wrapper.plugin-remove_button.rtl .item.active .remove { | |||
| border-right-color: #cacaca; | |||
| } | |||
| .ts-wrapper.plugin-remove_button.rtl.disabled .item .remove { | |||
| border-right-color: white; | |||
| } | |||
| :root { | |||
| --ts-pr-clear-button: 0; | |||
| --ts-pr-caret: 0; | |||
| --ts-pr-min: .75rem; | |||
| } | |||
| .ts-wrapper.single .ts-control, .ts-wrapper.single .ts-control input { | |||
| cursor: pointer; | |||
| } | |||
| .ts-control:not(.rtl) { | |||
| padding-right: max(var(--ts-pr-min), var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important; | |||
| } | |||
| .ts-control.rtl { | |||
| padding-left: max(var(--ts-pr-min), var(--ts-pr-clear-button) + var(--ts-pr-caret)) !important; | |||
| } | |||
| .ts-wrapper { | |||
| position: relative; | |||
| } | |||
| .ts-dropdown, | |||
| .ts-control, | |||
| .ts-control input { | |||
| color: #303030; | |||
| font-family: inherit; | |||
| font-size: 13px; | |||
| line-height: 18px; | |||
| } | |||
| .ts-control, | |||
| .ts-wrapper.single.input-active .ts-control { | |||
| background: #fff; | |||
| cursor: text; | |||
| } | |||
| .ts-hidden-accessible { | |||
| border: 0 !important; | |||
| clip: rect(0 0 0 0) !important; | |||
| -webkit-clip-path: inset(50%) !important; | |||
| clip-path: inset(50%) !important; | |||
| overflow: hidden !important; | |||
| padding: 0 !important; | |||
| position: absolute !important; | |||
| width: 1px !important; | |||
| white-space: nowrap !important; | |||
| } | |||
| /*# sourceMappingURL=tom-select.css.map */ | |||
| @ -0,0 +1,410 @@ | |||
| trix-editor { | |||
| border: 1px solid #bbb; | |||
| border-radius: 3px; | |||
| margin: 0; | |||
| padding: 0.4em 0.6em; | |||
| min-height: 5em; | |||
| outline: none; } | |||
| trix-toolbar * { | |||
| box-sizing: border-box; } | |||
| trix-toolbar .trix-button-row { | |||
| display: flex; | |||
| flex-wrap: nowrap; | |||
| justify-content: space-between; | |||
| overflow-x: auto; } | |||
| trix-toolbar .trix-button-group { | |||
| display: flex; | |||
| margin-bottom: 10px; | |||
| border: 1px solid #bbb; | |||
| border-top-color: #ccc; | |||
| border-bottom-color: #888; | |||
| border-radius: 3px; } | |||
| trix-toolbar .trix-button-group:not(:first-child) { | |||
| margin-left: 1.5vw; } | |||
| @media (max-device-width: 768px) { | |||
| trix-toolbar .trix-button-group:not(:first-child) { | |||
| margin-left: 0; } } | |||
| trix-toolbar .trix-button-group-spacer { | |||
| flex-grow: 1; } | |||
| @media (max-device-width: 768px) { | |||
| trix-toolbar .trix-button-group-spacer { | |||
| display: none; } } | |||
| trix-toolbar .trix-button { | |||
| position: relative; | |||
| float: left; | |||
| color: rgba(0, 0, 0, 0.6); | |||
| font-size: 0.75em; | |||
| font-weight: 600; | |||
| white-space: nowrap; | |||
| padding: 0 0.5em; | |||
| margin: 0; | |||
| outline: none; | |||
| border: none; | |||
| border-bottom: 1px solid #ddd; | |||
| border-radius: 0; | |||
| background: transparent; } | |||
| trix-toolbar .trix-button:not(:first-child) { | |||
| border-left: 1px solid #ccc; } | |||
| trix-toolbar .trix-button.trix-active { | |||
| background: #cbeefa; | |||
| color: black; } | |||
| trix-toolbar .trix-button:not(:disabled) { | |||
| cursor: pointer; } | |||
| trix-toolbar .trix-button:disabled { | |||
| color: rgba(0, 0, 0, 0.125); } | |||
| @media (max-device-width: 768px) { | |||
| trix-toolbar .trix-button { | |||
| letter-spacing: -0.01em; | |||
| padding: 0 0.3em; } } | |||
| trix-toolbar .trix-button--icon { | |||
| font-size: inherit; | |||
| width: 2.6em; | |||
| height: 1.6em; | |||
| max-width: calc(0.8em + 4vw); | |||
| text-indent: -9999px; } | |||
| @media (max-device-width: 768px) { | |||
| trix-toolbar .trix-button--icon { | |||
| height: 2em; | |||
| max-width: calc(0.8em + 3.5vw); } } | |||
| trix-toolbar .trix-button--icon::before { | |||
| display: inline-block; | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| opacity: 0.6; | |||
| content: ""; | |||
| background-position: center; | |||
| background-repeat: no-repeat; | |||
| background-size: contain; } | |||
| @media (max-device-width: 768px) { | |||
| trix-toolbar .trix-button--icon::before { | |||
| right: 6%; | |||
| left: 6%; } } | |||
| trix-toolbar .trix-button--icon.trix-active::before { | |||
| opacity: 1; } | |||
| trix-toolbar .trix-button--icon:disabled::before { | |||
| opacity: 0.125; } | |||
| trix-toolbar .trix-button--icon-attach::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); | |||
| top: 8%; | |||
| bottom: 4%; } | |||
| trix-toolbar .trix-button--icon-bold::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-italic::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-link::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-strike::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-quote::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-heading-1::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-code::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-bullet-list::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-number-list::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-undo::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-redo::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-decrease-nesting-level::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-button--icon-increase-nesting-level::before { | |||
| background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
| trix-toolbar .trix-dialogs { | |||
| position: relative; } | |||
| trix-toolbar .trix-dialog { | |||
| position: absolute; | |||
| top: 0; | |||
| left: 0; | |||
| right: 0; | |||
| font-size: 0.75em; | |||
| padding: 15px 10px; | |||
| background: #fff; | |||
| box-shadow: 0 0.3em 1em #ccc; | |||
| border-top: 2px solid #888; | |||
| border-radius: 5px; | |||
| z-index: 5; } | |||
| trix-toolbar .trix-input--dialog { | |||
| font-size: inherit; | |||
| font-weight: normal; | |||
| padding: 0.5em 0.8em; | |||
| margin: 0 10px 0 0; | |||
| border-radius: 3px; | |||
| border: 1px solid #bbb; | |||
| background-color: #fff; | |||
| box-shadow: none; | |||
| outline: none; | |||
| -webkit-appearance: none; | |||
| -moz-appearance: none; } | |||
| trix-toolbar .trix-input--dialog.validate:invalid { | |||
| box-shadow: #F00 0px 0px 1.5px 1px; } | |||
| trix-toolbar .trix-button--dialog { | |||
| font-size: inherit; | |||
| padding: 0.5em; | |||
| border-bottom: none; } | |||
| trix-toolbar .trix-dialog--link { | |||
| max-width: 600px; } | |||
| trix-toolbar .trix-dialog__link-fields { | |||
| display: flex; | |||
| align-items: baseline; } | |||
| trix-toolbar .trix-dialog__link-fields .trix-input { | |||
| flex: 1; } | |||
| trix-toolbar .trix-dialog__link-fields .trix-button-group { | |||
| flex: 0 0 content; | |||
| margin: 0; } | |||
| trix-editor [data-trix-mutable]:not(.attachment__caption-editor) { | |||
| -webkit-user-select: none; | |||
| -moz-user-select: none; | |||
| -ms-user-select: none; | |||
| user-select: none; } | |||
| trix-editor [data-trix-mutable]::-moz-selection, | |||
| trix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection { | |||
| background: none; } | |||
| trix-editor [data-trix-mutable]::selection, | |||
| trix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection { | |||
| background: none; } | |||
| trix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection { | |||
| background: highlight; } | |||
| trix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection { | |||
| background: highlight; } | |||
| trix-editor [data-trix-mutable].attachment.attachment--file { | |||
| box-shadow: 0 0 0 2px highlight; | |||
| border-color: transparent; } | |||
| trix-editor [data-trix-mutable].attachment img { | |||
| box-shadow: 0 0 0 2px highlight; } | |||
| trix-editor .attachment { | |||
| position: relative; } | |||
| trix-editor .attachment:hover { | |||
| cursor: default; } | |||
| trix-editor .attachment--preview .attachment__caption:hover { | |||
| cursor: text; } | |||
| trix-editor .attachment__progress { | |||
| position: absolute; | |||
| z-index: 1; | |||
| height: 20px; | |||
| top: calc(50% - 10px); | |||
| left: 5%; | |||
| width: 90%; | |||
| opacity: 0.9; | |||
| transition: opacity 200ms ease-in; } | |||
| trix-editor .attachment__progress[value="100"] { | |||
| opacity: 0; } | |||
| trix-editor .attachment__caption-editor { | |||
| display: inline-block; | |||
| width: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| font-size: inherit; | |||
| font-family: inherit; | |||
| line-height: inherit; | |||
| color: inherit; | |||
| text-align: center; | |||
| vertical-align: top; | |||
| border: none; | |||
| outline: none; | |||
| -webkit-appearance: none; | |||
| -moz-appearance: none; } | |||
| trix-editor .attachment__toolbar { | |||
| position: absolute; | |||
| z-index: 1; | |||
| top: -0.9em; | |||
| left: 0; | |||
| width: 100%; | |||
| text-align: center; } | |||
| trix-editor .trix-button-group { | |||
| display: inline-flex; } | |||
| trix-editor .trix-button { | |||
| position: relative; | |||
| float: left; | |||
| color: #666; | |||
| white-space: nowrap; | |||
| font-size: 80%; | |||
| padding: 0 0.8em; | |||
| margin: 0; | |||
| outline: none; | |||
| border: none; | |||
| border-radius: 0; | |||
| background: transparent; } | |||
| trix-editor .trix-button:not(:first-child) { | |||
| border-left: 1px solid #ccc; } | |||
| trix-editor .trix-button.trix-active { | |||
| background: #cbeefa; } | |||
| trix-editor .trix-button:not(:disabled) { | |||
| cursor: pointer; } | |||
| trix-editor .trix-button--remove { | |||
| text-indent: -9999px; | |||
| display: inline-block; | |||
| padding: 0; | |||
| outline: none; | |||
| width: 1.8em; | |||
| height: 1.8em; | |||
| line-height: 1.8em; | |||
| border-radius: 50%; | |||
| background-color: #fff; | |||
| border: 2px solid highlight; | |||
| box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); } | |||
| trix-editor .trix-button--remove::before { | |||
| display: inline-block; | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| opacity: 0.7; | |||
| content: ""; | |||
| background-image: url("data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); | |||
| background-position: center; | |||
| background-repeat: no-repeat; | |||
| background-size: 90%; } | |||
| trix-editor .trix-button--remove:hover { | |||
| border-color: #333; } | |||
| trix-editor .trix-button--remove:hover::before { | |||
| opacity: 1; } | |||
| trix-editor .attachment__metadata-container { | |||
| position: relative; } | |||
| trix-editor .attachment__metadata { | |||
| position: absolute; | |||
| left: 50%; | |||
| top: 2em; | |||
| transform: translate(-50%, 0); | |||
| max-width: 90%; | |||
| padding: 0.1em 0.6em; | |||
| font-size: 0.8em; | |||
| color: #fff; | |||
| background-color: rgba(0, 0, 0, 0.7); | |||
| border-radius: 3px; } | |||
| trix-editor .attachment__metadata .attachment__name { | |||
| display: inline-block; | |||
| max-width: 100%; | |||
| vertical-align: bottom; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; } | |||
| trix-editor .attachment__metadata .attachment__size { | |||
| margin-left: 0.2em; | |||
| white-space: nowrap; } | |||
| .trix-content { | |||
| line-height: 1.5; } | |||
| .trix-content * { | |||
| box-sizing: border-box; | |||
| margin: 0; | |||
| padding: 0; } | |||
| .trix-content h1 { | |||
| font-size: 1.2em; | |||
| line-height: 1.2; } | |||
| .trix-content blockquote { | |||
| border: 0 solid #ccc; | |||
| border-left-width: 0.3em; | |||
| margin-left: 0.3em; | |||
| padding-left: 0.6em; } | |||
| .trix-content [dir=rtl] blockquote, | |||
| .trix-content blockquote[dir=rtl] { | |||
| border-width: 0; | |||
| border-right-width: 0.3em; | |||
| margin-right: 0.3em; | |||
| padding-right: 0.6em; } | |||
| .trix-content li { | |||
| margin-left: 1em; } | |||
| .trix-content [dir=rtl] li { | |||
| margin-right: 1em; } | |||
| .trix-content pre { | |||
| display: inline-block; | |||
| width: 100%; | |||
| vertical-align: top; | |||
| font-family: monospace; | |||
| font-size: 0.9em; | |||
| padding: 0.5em; | |||
| white-space: pre; | |||
| background-color: #eee; | |||
| overflow-x: auto; } | |||
| .trix-content img { | |||
| max-width: 100%; | |||
| height: auto; } | |||
| .trix-content .attachment { | |||
| display: inline-block; | |||
| position: relative; | |||
| max-width: 100%; } | |||
| .trix-content .attachment a { | |||
| color: inherit; | |||
| text-decoration: none; } | |||
| .trix-content .attachment a:hover, .trix-content .attachment a:visited:hover { | |||
| color: inherit; } | |||
| .trix-content .attachment__caption { | |||
| text-align: center; } | |||
| .trix-content .attachment__caption .attachment__name + .attachment__size::before { | |||
| content: ' \2022 '; } | |||
| .trix-content .attachment--preview { | |||
| width: 100%; | |||
| text-align: center; } | |||
| .trix-content .attachment--preview .attachment__caption { | |||
| color: #666; | |||
| font-size: 0.9em; | |||
| line-height: 1.2; } | |||
| .trix-content .attachment--file { | |||
| color: #333; | |||
| line-height: 1; | |||
| margin: 0 2px 2px 2px; | |||
| padding: 0.4em 1em; | |||
| border: 1px solid #bbb; | |||
| border-radius: 5px; } | |||
| .trix-content .attachment-gallery { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| position: relative; } | |||
| .trix-content .attachment-gallery .attachment { | |||
| flex: 1 0 33%; | |||
| padding: 0 0.5em; | |||
| max-width: 33%; } | |||
| .trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment { | |||
| flex-basis: 50%; | |||
| max-width: 50%; } | |||
| @ -0,0 +1,81 @@ | |||
| class Admin::AdminController < ApplicationController | |||
| layout 'admin' | |||
| before_action :authenticate_user! | |||
| before_action :only_admin! | |||
| helper_method :current_user | |||
| helper_method :user_signed_in? | |||
| helper_method :form_locale | |||
| private | |||
| def default_url_options | |||
| { locale: I18n.locale } | |||
| end | |||
| def form_locale | |||
| (params['form_locale'] || I18n.locale).to_sym | |||
| end | |||
| def authenticate_user! | |||
| unless user_signed_in? | |||
| store_location | |||
| redirect_to admin_sessions_path | |||
| end | |||
| end | |||
| def only_admin! | |||
| unless current_user&.admin_role? | |||
| redirect_to root_path | |||
| end | |||
| end | |||
| def current_user | |||
| Current.user ||= authenticate_user_from_session | |||
| end | |||
| def authenticate_user_from_session | |||
| User.enabled.find_by(id: session[:user_id]) | |||
| end | |||
| def user_signed_in? | |||
| current_user.present? | |||
| end | |||
| def login(user) | |||
| Current.user = user | |||
| reset_session | |||
| user.touch(:last_logon_at) | |||
| session[:user_id] = user.id | |||
| end | |||
| def logout(user) | |||
| Current.user = nil | |||
| reset_session | |||
| end | |||
| def redirect_back_or_default(default) | |||
| redirect_to(session[:return_to] || default) | |||
| session[:return_to] = nil | |||
| end | |||
| def store_location | |||
| session[:return_to] = request.fullpath if request.get? | |||
| end | |||
| end | |||
| @ -0,0 +1,82 @@ | |||
| class Admin::AssetsController < Admin::AdminController | |||
| before_action :set_asset, only: %i[ edit update destroy ] | |||
| helper_method :sorting_technique | |||
| # GET /assets | |||
| def index | |||
| @assets = Asset.public_send(sorting_technique, params[:reverse].present?) | |||
| .simple_search(params[:q]) | |||
| .page(params[:page] || 1) | |||
| render (params[:node_id] || params[:newsletter_id]) ? 'explore' : 'index' | |||
| end | |||
| # GET /assets/1/edit | |||
| def edit | |||
| end | |||
| # PATCH/PUT /assets/1 or /assets/1.json | |||
| def update | |||
| respond_to do |format| | |||
| if @asset.update(asset_params) | |||
| format.html { redirect_to edit_admin_asset_url(@asset), notice: t(:'assets.updated') } | |||
| else | |||
| format.html { render :edit, status: :unprocessable_entity } | |||
| end | |||
| end | |||
| end | |||
| def upload | |||
| blob = ActiveStorage::Blob.find_signed(params[:signed_id]) | |||
| @asset = Asset.create(file: blob, title: blob.filename.base.gsub("_", " ")) | |||
| respond_to do |format| | |||
| format.turbo_stream | |||
| end | |||
| end | |||
| # DELETE /assets/1 or /assets/1.json | |||
| def destroy | |||
| @asset.destroy! | |||
| respond_to do |format| | |||
| format.html { redirect_to url_for(action: :index, format: :html), notice: t('assets.destroyed') } | |||
| unless params[:redirect_to] | |||
| format.turbo_stream { flash.now[:notice] = t('assets.destroyed') } | |||
| end | |||
| end | |||
| end | |||
| private | |||
| def sorting_technique | |||
| params[:sort].presence_in(%w(by_filename by_last_modified)) || :by_filename | |||
| end | |||
| # Use callbacks to share common setup or constraints between actions. | |||
| def set_asset | |||
| @asset = Asset.find(params[:id]) | |||
| end | |||
| # Only allow a list of trusted parameters through. | |||
| def asset_params | |||
| params.require(:asset).permit( | |||
| :title | |||
| ) | |||
| end | |||
| end | |||
| @ -0,0 +1,29 @@ | |||
| class Admin::AttachmentsController < Admin::AdminController | |||
| before_action :set_attachable_for, only: %i[ new ] | |||
| def new | |||
| @attachments = [] | |||
| if params[:asset_ids] | |||
| Asset.where(id: params[:asset_ids].split(',')).each do |asset| | |||
| @attachments << @attachable_for.attachments.new(asset: asset) | |||
| end | |||
| else | |||
| @attachments << @attachable_for.attachments.new | |||
| end | |||
| end | |||
| private | |||
| def set_attachable_for | |||
| @attachable_for = Node.find(params[:node_id]) if params[:node_id] | |||
| @attachable_for = Newsletter.find(params[:newsletter_id]) if params[:newsletter_id] | |||
| end | |||
| end | |||
| @ -0,0 +1,188 @@ | |||
| class Admin::NodesController < Admin::AdminController | |||
| before_action :set_node, only: %i[ edit update destroy children sort toggle ] | |||
| helper_method :current_node_id, :open_node_ids | |||
| # GET /nodes | |||
| def index | |||
| # Can be nil | |||
| @node = Node.find_by(id: current_node_id) || Node.roots.first | |||
| end | |||
| # GET /nodes/1/edit | |||
| def edit | |||
| respond_to do |format| | |||
| format.turbo_stream | |||
| format.html | |||
| end | |||
| end | |||
| # GET /nodes/tree /nodes/1/tree | |||
| def tree | |||
| @node = params[:id] ? Node.find(params[:id]) : Node.roots.first | |||
| session[:current_node_id] = @node&.id | |||
| respond_to do |format| | |||
| format.turbo_stream | |||
| end | |||
| end | |||
| # POST /nodes or /nodes.json | |||
| def create | |||
| @node = Node.new(node_params) | |||
| @node.status = :status_draft | |||
| @node.template = @node.root.site? ? Node::NODE_TEMPLATES.first : Node::TILE_TEMPLATES.first | |||
| base_title = t('ui.untitled') | |||
| title = base_title | |||
| count = 0 | |||
| while @node.siblings.exists?(title: {I18n.locale => title}) | |||
| count += 1 | |||
| title = "#{base_title} #{count}" | |||
| end | |||
| @node.title = title | |||
| respond_to do |format| | |||
| if @node.save | |||
| session[:open_node_ids] << @node.id unless session[:open_node_ids].include?(@node.id) | |||
| format.turbo_stream | |||
| format.html { redirect_to edit_admin_node_url(@node), notice: t('ui.category_created', category: t(@node.template, scope: 'nodes.templates')) } | |||
| else | |||
| format.html { render :new, status: :unprocessable_entity } | |||
| end | |||
| end | |||
| end | |||
| # GET /nodes/1/children | |||
| def children | |||
| store_node_toggle_status | |||
| respond_to do |format| | |||
| format.turbo_stream | |||
| end | |||
| end | |||
| # PATCH/PUT /nodes/1 | |||
| def update | |||
| params[:node][:expires_at] ||= nil | |||
| respond_to do |format| | |||
| if @node.update(node_params) | |||
| format.turbo_stream { | |||
| flash.now[:notice] = t('ui.category_updated', category: t(@node.template, scope: 'nodes.templates')) | |||
| } | |||
| format.html { redirect_to edit_node_url(@node), notice: t('ui.category_updated', category: t(@node.category, scope: 'nodes.categories')) } | |||
| else | |||
| format.html { render :edit, status: :unprocessable_entity } | |||
| end | |||
| end | |||
| end | |||
| # DELETE /nodes/1 | |||
| def destroy | |||
| if @node.destroy! | |||
| @destroyed_node = @node | |||
| @node = @node.parent | |||
| end | |||
| respond_to do |format| | |||
| format.turbo_stream { | |||
| flash.now[:notice] = t('ui.category_destroyed', category: t(@destroyed_node.category, scope: 'nodes.categories')) | |||
| } | |||
| format.html { redirect_to nodes_url, notice: t(:'nodes.destroyed') } | |||
| end | |||
| end | |||
| # PATCH /nodes/sort?id= | |||
| # {"id"=>"2", "old_index"=>1, "new_index"=>4, "node"=>{"id"=>"2"}} | |||
| def sort | |||
| @node.insert_at(params[:new_index].to_i + 1) | |||
| respond_to do |format| | |||
| format.turbo_stream | |||
| end | |||
| end | |||
| # Expand or collapse node in drawer | |||
| # PATCH /nodes/toggle?id=1&expanded=true/false | |||
| def toggle | |||
| store_node_toggle_status | |||
| head :ok | |||
| end | |||
| private | |||
| # Use callbacks to share common setup or constraints between actions. | |||
| def set_node | |||
| @node = Node.find(params[:id]) | |||
| end | |||
| # Only allow a list of trusted parameters through. | |||
| def node_params | |||
| params.require(:node).permit( | |||
| :parent_id, | |||
| :title_da, :title_en, :title_de, | |||
| :page_title_da, :page_title_en, :page_title_de, | |||
| :page_description_da, :page_description_en, :page_description_de, | |||
| :slug_da, :slug_en, :slug_de, | |||
| :template, | |||
| :href_da, :href_en, :href_de, | |||
| :status, | |||
| :page_description, | |||
| :parent_id, | |||
| :position, | |||
| :published_at, | |||
| :expires_at, | |||
| :is_allowlist, | |||
| excluded_locales: [], | |||
| settings: [], | |||
| attachments_attributes: [:id, :asset_id, :body_da, :body_en, :body_de, :fg_color, :bg_color, :alignment, :template, :position, :_destroy] | |||
| ) | |||
| end | |||
| def current_node_id | |||
| session[:current_node_id] | |||
| end | |||
| def open_node_ids | |||
| Array(session[:open_node_ids]) | |||
| end | |||
| def ensure_open_node_ids | |||
| session[:open_node_ids] ||= [] | |||
| end | |||
| def store_node_toggle_status | |||
| ensure_open_node_ids | |||
| if params[:expanded] == false | |||
| session[:open_node_ids].delete(@node.id) | |||
| else | |||
| session[:open_node_ids] << @node.id unless session[:open_node_ids].include?(@node.id) | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,56 @@ | |||
| class Admin::SessionsController < Admin::AdminController | |||
| layout 'sessions' | |||
| skip_before_action :authenticate_user!, except: %i[destroy] | |||
| skip_before_action :only_admin! | |||
| def index | |||
| render action: 'new' | |||
| end | |||
| def create | |||
| if user = User.enabled.authenticate_by(params.permit(:email, :password)) | |||
| # login user | |||
| # redirect_back_or_default(admin_root_path(locale: I18n.default_locale)) | |||
| session[:verify_user_id] = user.id | |||
| UserMailer.with(user: user, verification_code: user.verification_codes.create).verify_email.deliver_later | |||
| redirect_to action: 'verification', locale: nil | |||
| else | |||
| flash.now.alert = t :'sessions.login_failed' | |||
| render action: 'new', status: :unprocessable_entity | |||
| end | |||
| end | |||
| def destroy | |||
| logout current_user | |||
| redirect_to root_path | |||
| end | |||
| # GET | |||
| def verification | |||
| end | |||
| # POST | |||
| def verify | |||
| if params[:verification_code] =~ /\A\d{6}\z/ and | |||
| user = User.enabled.find(session[:verify_user_id]) and | |||
| user.verification_codes.valid.find_by(token: params[:verification_code]) | |||
| login user | |||
| redirect_back_or_default(admin_root_path(locale: I18n.default_locale)) | |||
| else | |||
| flash.now.alert = t :'sessions.verification_failed' | |||
| render "verification" | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,91 @@ | |||
| class Admin::UsersController < Admin::AdminController | |||
| before_action :set_user, only: %i[ edit update destroy ] | |||
| helper_method :sorting_technique | |||
| # GET /users | |||
| def index | |||
| @users = User.public_send(sorting_technique, params[:reverse].present?) | |||
| .simple_search(params[:q]) | |||
| .page(params[:page] || 1) | |||
| end | |||
| # GET /users/new | |||
| def new | |||
| @user = User.new | |||
| end | |||
| # GET /users/1/edit | |||
| def edit | |||
| end | |||
| # POST /users or /users.json | |||
| def create | |||
| @user = User.new(user_params) | |||
| respond_to do |format| | |||
| if @user.save | |||
| format.html { redirect_to edit_admin_user_url(@user), notice: t(:'users.created') } | |||
| else | |||
| format.html { render :new, status: :unprocessable_entity } | |||
| end | |||
| end | |||
| end | |||
| # PATCH/PUT /users/1 or /users/1.json | |||
| def update | |||
| respond_to do |format| | |||
| if @user.update(user_params) | |||
| format.html { redirect_to edit_admin_user_url(@user), notice: t(:'users.updated') } | |||
| else | |||
| format.html { render :edit, status: :unprocessable_entity } | |||
| end | |||
| end | |||
| end | |||
| # DELETE /users/1 or /users/1.json | |||
| def destroy | |||
| @user.destroy! | |||
| respond_to do |format| | |||
| format.html { redirect_to url_for(action: :index), notice: t(:'users.destroyed') } | |||
| end | |||
| end | |||
| private | |||
| def sorting_technique | |||
| params[:sort].presence_in(%w(by_name by_email by_last_modified)) || :by_name | |||
| end | |||
| # Use callbacks to share common setup or constraints between actions. | |||
| def set_user | |||
| @user = User.find(params[:id]) | |||
| end | |||
| # Only allow a list of trusted parameters through. | |||
| def user_params | |||
| params.require(:user).permit( | |||
| :enabled_at, | |||
| :title, | |||
| :role, | |||
| :phone, | |||
| :firstname, | |||
| :lastname, | |||
| :email, | |||
| :password, | |||
| :password_confirmation | |||
| ) | |||
| end | |||
| end | |||
| @ -0,0 +1,41 @@ | |||
| module Admin::AdminHelper | |||
| def entries_info(collection) | |||
| n = collection.respond_to?(:total_count) ? collection.total_count : collection.size | |||
| tag.div class: 'entries-info', id: 'n-entries' do | |||
| return "#{n} #{collection.model_name.human(count: n)}" if collection.respond_to?(:model_name) | |||
| return "#{n} #{collection.first.model_name.human(count: n)}" if collection.is_a?(Array) and collection.any? | |||
| end | |||
| end | |||
| def audit_info(obj) | |||
| tag.div do | |||
| concat tag.div(l(obj.updated_at, format: :listing), title: l(obj.updated_at, format: :long), class: 'audit-date') | |||
| concat tag.div(obj.own_and_associated_audits&.first&.user&.name, class: 'audit-info') if obj.respond_to?(:audits) | |||
| end | |||
| end | |||
| def link_to_sortable(sort_by, title=nil, options={}) | |||
| title ||= sort_by.titleize | |||
| current = (sort_by.to_sym == sorting_technique.to_sym) | |||
| reverse = (params[:reverse].blank? and current) ? 1 : nil | |||
| css_class = ['sort_link'] | |||
| css_class << 'current' if current | |||
| css_class << (reverse.blank? ? 'asc' : 'desc') | |||
| link_to title, | |||
| url_for( | |||
| params.permit(:q, :category, :tag).merge(sort: sort_by, reverse: reverse) | |||
| ), | |||
| data: options[:data] || { | |||
| turbo_action: 'advance' | |||
| } , | |||
| class: css_class.join(' ') | |||
| end | |||
| end | |||
| @ -0,0 +1,2 @@ | |||
| module Admin::AssetsHelper | |||
| end | |||
| @ -0,0 +1,2 @@ | |||
| module Admin::AttachmentsHelper | |||
| end | |||
| @ -0,0 +1,145 @@ | |||
| module Admin::NodesHelper | |||
| def node_structure_for_select(parent, node) | |||
| result = [] | |||
| result << [parent.title, parent.id] if parent.root? | |||
| parent.children.ordered.each do |child| | |||
| result << ["#{"\u00A0\u00A0" * (child.depth)}#{child.title}".html_safe, child.id, { disabled: (child == node or child.ancestor_ids.include?(node.id)) }] | |||
| result = result + node_structure_for_select(child, node) | |||
| end | |||
| result | |||
| end | |||
| def spacer_node(node) | |||
| return if node.root? | |||
| tag.div class: 'spacer' do | |||
| node.depth.times do | |||
| concat tag.div nil | |||
| end | |||
| end | |||
| end | |||
| def toggle_node(node) | |||
| return tag.div nil, class: 'child' if node.document? | |||
| link_to url_for(controller: 'nodes', action: 'children', id: node.id), | |||
| class: 'child parent', | |||
| data: { | |||
| turbo_stream: true, | |||
| controller: "nodes", | |||
| action: "click->nodes#toggle_children" | |||
| } do | |||
| concat tag.span('expand_more') | |||
| concat tag.span('keyboard_arrow_right') | |||
| end | |||
| end | |||
| def tree_title(node) | |||
| # result = [link_to(ENV['PROJECT_NAME'], | |||
| # url_for(url_base_options.merge(id: nil)), | |||
| # class: "list-title-link#{' has-popup-menu' if node.blank?}", | |||
| # data: { | |||
| # icon: 'museum', | |||
| # action: node.blank? ? 'click->popup#toggle' : 'click->nodes#set_current', | |||
| # turbo_stream: node.blank? ? nil : true, | |||
| # })] | |||
| result = [] | |||
| node&.path&.each do |n| | |||
| result << tree_node_title_link(n, node) | |||
| end | |||
| result[-1] = "#{tag.div "#{result[-1]}#{node_popup_menu(node)}".html_safe, data: {controller: 'popup'} }" | |||
| result.join(tag.span('>')).html_safe | |||
| end | |||
| def node_popup_menu(node=nil) | |||
| tag.div class: 'popup-menu' do | |||
| tag.ul do | |||
| concat tag.li(link_to t('ui.edit'), edit_admin_node_path(node), data: {icon: 'edit', turbo_action: 'advance'}) if node | |||
| Node.categories.each do |node_category| | |||
| concat tag.li button_to(t(node_category, scope: 'nodes.new_categories'), | |||
| url_for(controller: 'nodes', action: 'create'), | |||
| params: {node: {parent_id: node&.id}}, | |||
| data: { | |||
| action: 'click->popup#close_open', | |||
| icon: t(node_category, scope: 'nodes.icons') | |||
| }) | |||
| end | |||
| # concat tag.li button_to(t('ui.reindex'), | |||
| # url_for(controller: 'nodes', action: 'reindex', id: node.id), | |||
| # method: :patch, | |||
| # data: { | |||
| # action: 'click->popup#close_open', | |||
| # icon: 'refresh' | |||
| # }) if node | |||
| end | |||
| end | |||
| end | |||
| def node_link_title(node) | |||
| # return node.title if !node.document? or node.attachments.blank? or !node.attachments.first.asset.representable? | |||
| # asset = node.attachments.first.asset | |||
| # node_icon = image_tag(rails_storage_proxy_path(asset.representation(resize_to_limit: [48,72], format: :jpg))) if asset.representable? | |||
| node_icon = nil | |||
| node_title = tag.span(node.title) | |||
| (node_icon || '').concat(node_title).html_safe | |||
| end | |||
| def node_flags(node) | |||
| return if node.copyright_with_inheritance.blank? and node.tags_with_inheritance.blank? | |||
| tag.div class: 'node__flags' do | |||
| concat tag.span('copyright') if node.copyright_with_inheritance.present? | |||
| concat tag.span('sell') if node.tags_with_inheritance.any? | |||
| end | |||
| end | |||
| def drawer_node_link(node) | |||
| link_to tag.span(node.title), | |||
| url_for(controller: 'nodes', action: node.document? ? 'edit' : 'tree', id: node.id), | |||
| title: node.title, | |||
| class: 'node-title', | |||
| id: dom_id(node, 'drawer-link'), | |||
| data: { | |||
| nodes_id_param: node.id, | |||
| turbo_stream: true, | |||
| action: 'click->nodes#set_current', | |||
| icon: t("nodes.icons.#{node.category}") | |||
| } | |||
| end | |||
| def tree_node_title_link(node, current_node=nil) | |||
| current_node ||= node | |||
| url_base_options = {controller: 'nodes', action: 'tree'} | |||
| link_to(node.title, | |||
| url_for(url_base_options.merge(id: node.id)), | |||
| class: current_node == node ? 'list-title-link has-popup-menu' : 'list-title-link', | |||
| id: dom_id(node, 'list-title-link'), | |||
| data: { | |||
| action: current_node == node ? 'click->popup#toggle' : 'click->nodes#set_current', | |||
| turbo_stream: current_node != node ? true : nil, | |||
| nodes_id_param: node.id, | |||
| icon: t("nodes.icons.#{node.category}") | |||
| }) | |||
| end | |||
| end | |||
| @ -0,0 +1,2 @@ | |||
| module Admin::UsersHelper | |||
| end | |||
| @ -0,0 +1,38 @@ | |||
| import "@hotwired/turbo-rails" | |||
| import "controllers" | |||
| import Trix from "trix" | |||
| // Bind ctrl + s to submit form | |||
| document.addEventListener('keydown', function (event) { | |||
| // Check if Ctrl key is pressed and the pressed key is 'S' | |||
| if ((event.ctrlKey || event.metaKey) && event.key === 's') { | |||
| // Prevent the browser's default save action | |||
| event.preventDefault(); | |||
| // Submit the form | |||
| const form = document.querySelector('.has--key-ctrls'); | |||
| if (form) { | |||
| form.requestSubmit(); | |||
| } | |||
| } | |||
| }); | |||
| // Trix | |||
| Trix.config.blockAttributes.heading2 = { tagName: "h2", terminal: true, breakOnReturn: true, group: false } | |||
| // Trix.config.blockAttributes.heading3 = { tagName: "h3", terminal: true, breakOnReturn: true, group: false } | |||
| document.addEventListener("trix-initialize", event => { | |||
| var buttonHTML | |||
| buttonHTML = '<button type="button" class="trix-button trix-button--icon trix-button--icon-heading-2" data-trix-attribute="heading2">Heading</button>'; | |||
| const groupElement = event.target.toolbarElement.querySelector(".trix-button-group--block-tools .trix-button--icon-heading-1") | |||
| groupElement.insertAdjacentHTML("afterend", buttonHTML) | |||
| const { toolbarElement } = event.target | |||
| const inputElement = toolbarElement.querySelector("input[name=href]") | |||
| inputElement.type = "text" | |||
| inputElement.pattern = "(https?://|/).+" | |||
| }) | |||
| @ -0,0 +1,34 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| import { get } from '@rails/request.js' | |||
| export default class extends Controller { | |||
| static values = { | |||
| url: String | |||
| } | |||
| connect () { | |||
| this.fetchingData = false | |||
| } | |||
| appendAttachments(event) { | |||
| if (this.fetchingData) return | |||
| const checkedCheckboxes = this.element.querySelectorAll('input[name="asset_ids[]"]:checked') | |||
| const checkedValues = Array.from(checkedCheckboxes).map(checkbox => checkbox.value) | |||
| this.getAttachments(checkedValues) | |||
| this.dispatch("click", { target: document.getElementById('overlay'), prefix: null}) | |||
| } | |||
| async getAttachments(ids) { | |||
| this.fetchingData = true | |||
| await get(this.urlValue, { query: {asset_ids: ids.join(',')}, responseKind: "turbo-stream" } ) | |||
| this.fetchingData = false | |||
| } | |||
| } | |||
| @ -0,0 +1,15 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| connect() { | |||
| } | |||
| removeField(event) { | |||
| const input = this.element.querySelector('.destroy__field') | |||
| input.value = '1' | |||
| this.element.style = 'display: none'; | |||
| } | |||
| } | |||
| @ -1,7 +0,0 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.element.textContent = "Hello World!" | |||
| } | |||
| } | |||
| @ -0,0 +1,26 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.input = document.createElement('input'); | |||
| this.input.type = 'hidden'; | |||
| this.input.name = '_locale'; // Set the name for the input | |||
| this.input.value = this.element.getAttribute('data-locale'); // Set the value for the input | |||
| this.element.appendChild(this.input); | |||
| } | |||
| setFormLocale(event) { | |||
| const locale = event.target.getAttribute('value') | |||
| this.input.value = locale | |||
| this.element.setAttribute('data-locale', locale) | |||
| } | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| import { useIntersection } from "stimulus-use" | |||
| import { get } from "@rails/request.js" | |||
| export default class extends Controller { | |||
| static values = { | |||
| url: String | |||
| } | |||
| connect() { | |||
| this.fetchingData = false | |||
| useIntersection(this) | |||
| } | |||
| async appear () { | |||
| if (this.fetchingData) return | |||
| this.fetchingData = true | |||
| await get(this.urlValue, { responseKind: "turbo-stream" } ) | |||
| this.fetchingData = false | |||
| } | |||
| } | |||
| @ -0,0 +1,14 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| set_current() { | |||
| document.querySelectorAll('#navbar .current').forEach((node) => { | |||
| node.classList.remove('current') | |||
| }) | |||
| this.element.classList.add('current') | |||
| } | |||
| } | |||
| @ -0,0 +1,59 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.url = document.getElementById('drawer').getAttribute('data-url') | |||
| this.token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') | |||
| } | |||
| // Toggle node children in drawer | |||
| toggle_children(event) { | |||
| const node = this.element.closest('.node-row'); | |||
| node.classList.toggle('closed'); | |||
| // Already loaded | |||
| if (node.parentNode.classList.contains('loaded')) { | |||
| fetch(this.url, { | |||
| method: 'PATCH', | |||
| headers: { | |||
| 'X-CSRF-Token': this.token, | |||
| 'Content-Type': 'application/json' | |||
| }, | |||
| body: JSON.stringify({ | |||
| id: node.parentNode.dataset.id, | |||
| expanded: !node.classList.contains('closed') | |||
| }) | |||
| }).then((response) => { | |||
| // console.info('resolved', response) | |||
| }).catch((err) => { | |||
| console.info('rejected', err) | |||
| }) | |||
| event.preventDefault(); | |||
| } else { | |||
| node.parentNode.classList.add('loaded') | |||
| } | |||
| } | |||
| // Mark current node in drawer | |||
| set_current(event) { | |||
| // Remove old current | |||
| var node = document.querySelector("#drawer .current") | |||
| if (node) { | |||
| node.classList.remove('current'); | |||
| } | |||
| // Add new current | |||
| node = document.getElementById(`node_${event.params['id']}`) | |||
| if (node) { | |||
| node.classList.add('current') | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,82 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.element.style.position = 'relative' | |||
| this.boundClosePopupOnClickOutside = this.closePopupOnClickOutside.bind(this) | |||
| } | |||
| toggle(event) { | |||
| const margin = 12 | |||
| const btn = event.currentTarget | |||
| const popup_menu = btn.nextElementSibling | |||
| popup_menu.classList.toggle('open') | |||
| btn.classList.toggle('open') | |||
| if (popup_menu.classList.contains('open')) { | |||
| const bound_rect = popup_menu.getBoundingClientRect() | |||
| // Open to the right | |||
| if (btn.classList.contains('open--right')) { | |||
| popup_menu.style.left = btn.offsetWidth + margin + 'px' | |||
| popup_menu.style.top = "0px" | |||
| const viewportHeight = window.innerHeight || document.documentElement.clientHeight; | |||
| // Overflow buttom | |||
| if (bound_rect.bottom > viewportHeight) { | |||
| popup_menu.style.bottom = "0px" | |||
| popup_menu.style.top = "auto" | |||
| } | |||
| } else { | |||
| const viewportWidth = window.innerWidth || document.documentElement.clientWidth; | |||
| // Overflow right | |||
| if (bound_rect.right > viewportWidth) { | |||
| popup_menu.style.left = '-' + (bound_rect.width - btn.offsetWidth) + 'px' | |||
| } | |||
| } | |||
| document.addEventListener('mousedown', this.boundClosePopupOnClickOutside) | |||
| } else { | |||
| popup_menu.removeAttribute('style') | |||
| } | |||
| event.preventDefault() | |||
| } | |||
| close_open(e) { | |||
| document.querySelectorAll('.popup-menu.open, .has-popup-menu.open').forEach((node) => { | |||
| node.classList.remove('open') | |||
| node.removeAttribute('style') | |||
| }) | |||
| document.removeEventListener('mousedown', this.boundClosePopupOnClickOutside) | |||
| } | |||
| closePopupOnClickOutside(e) { | |||
| const popup_menu = e.target.closest('.popup-menu, .has-popup-menu'); | |||
| if (popup_menu == null) { | |||
| document.querySelectorAll('.popup-menu.open, .has-popup-menu.open').forEach((node) => { | |||
| node.classList.remove('open') | |||
| node.removeAttribute('style') | |||
| }) | |||
| document.removeEventListener('mousedown', this.boundClosePopupOnClickOutside) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| import TomSelect from "tom-select"; | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.initializeTomSelect(); | |||
| } | |||
| // Triggered when the Stimulus controller is removed from the DOM. | |||
| disconnect() { | |||
| this.destroyTomSelect(); | |||
| } | |||
| // Initialize the TomSelect dropdown with the desired configurations. | |||
| initializeTomSelect() { | |||
| // Return early if no element is associated with the controller. | |||
| if (!this.element) return; | |||
| this.select = new TomSelect(this.element, { | |||
| create: this.element.getAttribute('data-tags') == 'true' | |||
| }); | |||
| } | |||
| destroyTomSelect() { | |||
| if (this.select) { | |||
| this.select.destroy(); | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,51 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| import Sortable from 'sortablejs' | |||
| export default class extends Controller { | |||
| connect() { | |||
| this.sortable = new Sortable(this.element, { | |||
| onEnd: this.end.bind(this), | |||
| handle: '.handle' | |||
| }) | |||
| } | |||
| disconnect() { | |||
| this.sortable.destroy() | |||
| } | |||
| end(event) { | |||
| // sortableUrl | |||
| if (this.element.dataset.sortableUrl != undefined) { | |||
| const item = this.element.children[event.newIndex] | |||
| const url = this.element.dataset.sortableUrl | |||
| const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') | |||
| const formData = new FormData() | |||
| formData.append('id', item.dataset.id) | |||
| formData.append('new_index', event.newIndex) | |||
| // console.log("Move", event.oldIndex, "to", event.newIndex); | |||
| fetch(url, { | |||
| method: 'PATCH', | |||
| headers: { | |||
| 'Accept': "text/vnd.turbo-stream.html", | |||
| 'X-CSRF-Token': token | |||
| }, | |||
| body: formData | |||
| }) | |||
| .then (response => response.text()) | |||
| .then(html => Turbo.renderStreamMessage(html)) | |||
| .catch((err) => { | |||
| console.info('rejected', err) | |||
| }) | |||
| } else { | |||
| this.element.querySelectorAll('input.position').forEach((input, index) => { | |||
| input.value = index + 1 | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,117 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| import { DirectUpload } from "@rails/activestorage" | |||
| // Connects to data-controller="upload" | |||
| export default class extends Controller { | |||
| static values = { | |||
| url: String, | |||
| createUrl: String, | |||
| parentId: Number, | |||
| target: String | |||
| } | |||
| connect() { | |||
| // console.info(this.urlValue) | |||
| // console.info('URL', this.createUrlValue) | |||
| } | |||
| // This will be triggered when a file is selected or dropped | |||
| upload(event) { | |||
| event.preventDefault() | |||
| const container = document.getElementById(this.targetValue) | |||
| const files = event.target.files || event.dataTransfer.files; | |||
| Array.from(files).forEach((file, index) => { | |||
| const uid = 'new_' + Date.now() + '_' + index | |||
| const target_div = document.createElement('div') | |||
| target_div.setAttribute('id', uid) | |||
| if (this.targetValue == 'attachments') { | |||
| target_div.classList.add('attachment', 'loading') | |||
| target_div.innerHTML = '<span class="progress">0</span>' | |||
| } else if (this.targetValue == 'tree-nodes') { | |||
| target_div.classList.add('row') | |||
| target_div.innerHTML = '<div class="cell"><span class="node-link progress" data-icon="description">0</span></div>' | |||
| } | |||
| container.append(target_div) | |||
| new Uploader(this, file, uid).start() | |||
| }); | |||
| } | |||
| // Drag over event handler | |||
| dragover(event) { | |||
| event.preventDefault(); | |||
| this.element.classList.add('drag-over'); | |||
| } | |||
| // Drag leave event handler | |||
| dragleave(event) { | |||
| event.preventDefault(); | |||
| this.element.classList.remove('drag-over'); | |||
| } | |||
| // Drop event handler | |||
| drop(event) { | |||
| event.preventDefault(); | |||
| this.element.classList.remove('drag-over'); | |||
| // Call the upload method and pass in the drop event | |||
| this.upload(event); | |||
| } | |||
| } | |||
| class Uploader { | |||
| constructor(source, file, uid) { | |||
| this.source = source | |||
| this.uid = uid | |||
| this.upload = new DirectUpload(file, source.urlValue, this) | |||
| } | |||
| start() { | |||
| this.upload.create((error, blob) => { | |||
| if (error) { | |||
| // Handle the error | |||
| } else { | |||
| const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') | |||
| const formData = new FormData() | |||
| if (this.source.parentIdValue > 0) { | |||
| formData.append('id', this.source.parentIdValue) | |||
| } | |||
| formData.append('signed_id', blob.signed_id) | |||
| formData.append('uid', this.uid) | |||
| fetch(this.source.createUrlValue, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Accept': "text/vnd.turbo-stream.html", | |||
| 'X-CSRF-Token': token | |||
| }, | |||
| body: formData | |||
| }) | |||
| .then (response => response.text()) | |||
| .then(html => Turbo.renderStreamMessage(html)) | |||
| .catch((err) => { | |||
| console.info('rejected', err) | |||
| }) | |||
| } | |||
| }) | |||
| } | |||
| directUploadWillStoreFileWithXHR(request) { | |||
| request.upload.addEventListener("progress", | |||
| event => this.directUploadDidProgress(event)) | |||
| } | |||
| directUploadDidProgress(event) { | |||
| const target = document.getElementById(this.uid).querySelector('.progress') | |||
| const loaded = Math.round((event.loaded / event.total) * 100) | |||
| target.innerHTML = loaded | |||
| } | |||
| } | |||
| @ -0,0 +1,75 @@ | |||
| import { Controller } from "@hotwired/stimulus" | |||
| // Connects to data-controller="removals" | |||
| export default class extends Controller { | |||
| static values = { | |||
| statusText: String | |||
| } | |||
| connect() { | |||
| // console.info(this.tagIdsValue) | |||
| // console.info(this.updateUrlValue) | |||
| } | |||
| replaceWithStatus(event) { | |||
| this.element.innerText = this.statusTextValue; | |||
| } | |||
| changeDataN() { | |||
| // console.info(this.element.selectedOptions[0]) | |||
| const context = this.element.closest('.context-container') | |||
| context.setAttribute('data-n', this.element.selectedOptions[0].getAttribute('data-n')) | |||
| } | |||
| disableScroll(){ | |||
| document.body.style.overflow = 'hidden' | |||
| } | |||
| enableScroll(){ | |||
| document.body.style.overflow = 'auto' | |||
| } | |||
| toogleSearchFilters(event) { | |||
| document.getElementById('search-filters').classList.toggle('open') | |||
| } | |||
| setCurrent(event) { | |||
| this.element.querySelector('.current').classList.remove('current') | |||
| event.currentTarget.closest('.node-attachment').classList.add('current') | |||
| } | |||
| closeOverlay(event){ | |||
| // console.log(event); | |||
| if (event.target == this.element || event.target.getAttribute('data-action') == 'click->utils#closeOverlay') { | |||
| this.closeOverlayAndEnableScroll() | |||
| } | |||
| } | |||
| closeOverlayAndEnableScroll() { | |||
| const overlay = document.getElementById('overlay') | |||
| if (overlay) { | |||
| this.enableScroll() | |||
| overlay.remove(); | |||
| } | |||
| } | |||
| remove() { | |||
| this.element.remove() | |||
| } | |||
| toggleDisabled() { | |||
| const elements = this.element.querySelectorAll('select, input') | |||
| elements.forEach(child => { | |||
| child.disabled = !child.disabled; | |||
| }) | |||
| } | |||
| } | |||
| @ -1,4 +1,11 @@ | |||
| class ApplicationMailer < ActionMailer::Base | |||
| default from: "from@example.com" | |||
| ADMIN = "Two-factor authentication <2fa@onc.dk>" | |||
| CLIENT = ADMIN | |||
| default from: ADMIN, | |||
| return_path: ADMIN | |||
| layout "mailer" | |||
| end | |||
| @ -0,0 +1,12 @@ | |||
| class UserMailer < ApplicationMailer | |||
| def verify_email | |||
| @user = params[:user] | |||
| @verification_code = params[:verification_code] | |||
| mail to: email_address_with_name(@user.email, @user.name), | |||
| subject: t('mailers.verify_email_subject', token: @verification_code&.token) | |||
| end | |||
| end | |||
| @ -0,0 +1,19 @@ | |||
| class Asset < ApplicationRecord | |||
| has_one_attached :file | |||
| include PgSearch::Model | |||
| pg_search_scope :pg_search, | |||
| against: [:title], | |||
| associated_against: { | |||
| file_blob: [:filename, :content_type] | |||
| }, | |||
| using: { | |||
| tsearch: { prefix: true } | |||
| } | |||
| scope :by_last_modified, ->(rev) { order(updated_at: rev ? :asc : :desc, id: rev ? :desc : :asc) } | |||
| scope :by_filename, ->(rev) { order(title: rev ? :desc : :asc) } | |||
| scope :simple_search, ->(q) { pg_search(q) unless q.blank? } | |||
| end | |||
| @ -0,0 +1,49 @@ | |||
| class Attachment < ApplicationRecord | |||
| extend Mobility | |||
| translates :body, :url, locale_accessors: I18n.available_locales | |||
| ALIGNMENT_TILE = %w"N NE E SE S SW W NW" | |||
| ALIGNMENT_SITE = %w"E W" | |||
| TEMPLATE_SITE_ASSET = %w"Hero L" | |||
| belongs_to :attachable_for, polymorphic: true, optional: true | |||
| belongs_to :asset, optional: true | |||
| store_accessor :props, :fg_color, :bg_color, :alignment, :template | |||
| acts_as_list scope: [:attachable_for_id, :attachable_for_type] | |||
| scope :ordered, -> { order(position: :asc) } | |||
| scope :favorites, -> { where(is_favorite: true) } | |||
| scope :favorites_first, -> { reorder("(is_favorite = TRUE) DESC, position ASC") } | |||
| scope :portraits_first, -> { reorder(Arel.sql("(props ->> 'is_portrait' = '1') DESC, position ASC")) } | |||
| scope :not_portraits, -> { where("(props ->> 'is_portrait' IS NULL OR props ->> 'is_portrait' != '1')") } | |||
| def is_large? | |||
| self.is_large.to_i == 1 | |||
| end | |||
| def is_portrait? | |||
| self.is_portrait.to_i == 1 | |||
| end | |||
| def template_subclass | |||
| return 'na' if self.template.blank? | |||
| self.template.downcase | |||
| end | |||
| def alignment_subclass | |||
| return 'na' if self.alignment.blank? | |||
| self.alignment.downcase | |||
| end | |||
| end | |||
| @ -0,0 +1,54 @@ | |||
| module AncestryWithSortedUrl | |||
| extend ActiveSupport::Concern | |||
| included do | |||
| has_ancestry orphan_strategy: :restrict, | |||
| cache_depth: true | |||
| acts_as_list scope: [:ancestry] | |||
| before_validation :format_slug, :generate_url | |||
| # validates_presence_of :slug, :url, unless: Proc.new { |node| node.root? } | |||
| # validates_uniqueness_of :slug, scope: [:ancestry] | |||
| after_commit :update_decendensts_url_if_changed | |||
| end | |||
| private | |||
| def format_slug | |||
| I18n.available_locales.each do |l| | |||
| v = self.root? ? (I18n.default_locale == l ? '' : l.to_s) : | |||
| self.send("slug_#{l}").blank? ? (self.title(locale: l) || '').parameterize : self.send("slug_#{l}").parameterize | |||
| self.send(:slug=, v, locale: l) | |||
| end | |||
| end | |||
| def generate_url | |||
| I18n.available_locales.each do |l| | |||
| Mobility.with_locale(l) do | |||
| v = File.join(self.ancestors.map { |node| node.slug || '' }, self.slug || '') | |||
| v = File.join('', v) unless I18n.default_locale == l | |||
| self.send(:url=, v, locale: l) | |||
| end | |||
| end | |||
| end | |||
| def update_decendensts_url_if_changed | |||
| self.reload.descendants.find_each(&:save) if self.previous_changes[:slug].present? or self.previous_changes[:ancestry] | |||
| end | |||
| end | |||
| @ -0,0 +1,37 @@ | |||
| module HasAttachments | |||
| extend ActiveSupport::Concern | |||
| included do | |||
| has_many :attachments, -> { order :position }, dependent: :destroy, as: :attachable_for, class_name: "Attachment" | |||
| has_many :assets, through: :attachments | |||
| has_many :assets, | |||
| through: :attachments, | |||
| source: :asset | |||
| #has_many :images, | |||
| # -> { with_attached_file.where('assets.content_type LIKE ?', "image/%") }, | |||
| # through: :attachments, | |||
| # source: :asset | |||
| # | |||
| #has_many :images_favorites_first, | |||
| # -> { with_attached_file.where('assets.content_type LIKE ?', "image/%").reorder('attachments.is_favorite DESC', 'attachments.position ASC') }, | |||
| # through: :attachments, | |||
| # source: :asset | |||
| accepts_nested_attributes_for :attachments, allow_destroy: true | |||
| end | |||
| def icon | |||
| self.assets.map{ |asset| return asset if asset.file and asset.file.content_type.starts_with?('image/') } | |||
| nil | |||
| end | |||
| end | |||
| @ -0,0 +1,48 @@ | |||
| module HasTags | |||
| extend ActiveSupport::Concern | |||
| included do | |||
| scope :tagged_with, ->(tag) { tagged_with_any(tag) } | |||
| scope :all_or_tagged_with, ->(tag) { tagged_with_any(tag) if tag } | |||
| scope :tagged_with_any, ->(tags) { where("tags -> '#{I18n.locale}' ?| ARRAY[:tags]", tags: tags) } | |||
| scope :tagged_with_all, ->(tags) { where("tags @> ?", {"#{I18n.locale}": [tags].flatten}.to_json) } | |||
| end | |||
| def descendant_tags | |||
| return self.class.tags unless self.respond_to?(:descendants) | |||
| result = {} | |||
| I18n.available_locales.each { |l| result[l] = [] } | |||
| self.descendants.viewable.pluck(:tags).flatten.map do |tags| | |||
| tags.each do |k,v| | |||
| result[k.to_sym] += v | |||
| end | |||
| end | |||
| result.each{ |k, v| result[k] = v.flatten.uniq.sort } | |||
| end | |||
| module ClassMethods | |||
| def tags | |||
| result = {} | |||
| I18n.available_locales.each { |l| result[l] = [] } | |||
| pluck(:tags).flatten.map do |tags| | |||
| tags.each do |k,v| | |||
| result[k.to_sym] += v | |||
| end | |||
| end | |||
| result.each{ |k, v| result[k] = v.flatten.uniq.sort } | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,3 @@ | |||
| class Current < ActiveSupport::CurrentAttributes | |||
| attribute :user | |||
| end | |||
| @ -0,0 +1,134 @@ | |||
| class Node < ApplicationRecord | |||
| MENUS = %i"main_menu sub_menu footer_node cta_link opening_hours negative_menu buy_ticket newsletter" | |||
| COOKIE_POLICY = :cookie_policy | |||
| SETTINGS = MENUS << COOKIE_POLICY | |||
| include AncestryWithSortedUrl | |||
| include HasAttachments | |||
| include HasTags | |||
| extend Mobility | |||
| translates :slug, locale_accessors: I18n.available_locales | |||
| translates :tags, locale_accessors: I18n.available_locales | |||
| translates :title, | |||
| :url, | |||
| :href, | |||
| :page_title, | |||
| :page_description, | |||
| fallbacks: { en: :da, de: :en }, | |||
| locale_accessors: I18n.available_locales | |||
| NODE_TEMPLATES = %w"tmpl_article tmpl_index" | |||
| enum status: { status_published: 0, status_draft: 1, status_archived: 2 } | |||
| enum template: { tmpl_index: 0, | |||
| tmpl_article: 1 | |||
| } | |||
| include PgSearch::Model | |||
| pg_search_scope :pg_search, | |||
| against: {title: 'A', url: 'B', page_title: 'A', page_description: 'B', href: 'B', slug: 'B' }, | |||
| associated_against: { | |||
| attachments: [:body, :url] | |||
| } | |||
| before_validation :remove_empty_tags | |||
| validates_presence_of :title | |||
| validates :expires_at, | |||
| date: { after: :published_at }, | |||
| allow_nil: true | |||
| scope :ordered, -> { order(position: :asc) } | |||
| scope :by_title, ->(rev) { order(Arel.sql(rev ? "title->>'#{I18n.locale.to_s}' DESC, id DESC": "title ->>'#{I18n.locale.to_s}' ASC, id ASC")) } | |||
| scope :by_status, ->(rev) { order(status: rev ? :desc : :asc, id: rev ? :desc : :asc) } | |||
| scope :by_slug, ->(rev) { order(ancestry: rev ? :desc : :asc, position: rev ? :desc : :asc, id: rev ? :desc : :asc) } | |||
| scope :by_last_modified, ->(rev) { order(updated_at: rev ? :asc : :desc, id: rev ? :desc : :asc) } | |||
| # scope :not_excluded, -> { where "NOT(? = ANY (excluded_locales))", I18n.locale.to_s } | |||
| scope :for_current_locale, -> { where("(cardinality(excluded_locales) = 0 AND is_allowlist = false) OR | |||
| (is_allowlist = true AND ? = ANY(excluded_locales)) OR | |||
| (is_allowlist = false AND NOT(? = ANY(excluded_locales)))", | |||
| I18n.locale.to_s, I18n.locale.to_s ) } | |||
| scope :viewable, -> { status_published.for_current_locale.where('published_at <= ? AND (expires_at IS NULL OR expires_at > ?)', Time.current, Time.current) } | |||
| scope :of_template, ->(tmpl) { where(template: Node.templates[tmpl.to_s]) } | |||
| scope :with_setting, ->(setting) { setting.kind_of?(Array) ? where("settings && ?", "{#{setting.join(',')}}") : where("settings @> ?", "{#{setting}}") } | |||
| scope :simple_search, ->(q) { pg_search(q) unless q.blank? } | |||
| scope :tiles, -> { where("ancestry LIKE '/?/%'", Node.where(position: 2, ancestry_depth: 0).first) } | |||
| def self.tags | |||
| result = {} | |||
| I18n.available_locales.each do |locale| | |||
| result[locale] = pluck(Arel.sql("tags->'#{locale}'")).flatten.compact.uniq.sort.reject{|v| v.blank?} | |||
| end | |||
| result | |||
| end | |||
| def self.categories | |||
| ['document'] | |||
| end | |||
| def category | |||
| self.children.any? ? 'folder' : 'document' | |||
| end | |||
| def document? | |||
| self.category == 'document' | |||
| end | |||
| def href_or_url | |||
| href.present?? href : url | |||
| end | |||
| def viewable? | |||
| Node.viewable.where(id: self.path_ids).count == self.path_ids.length | |||
| end | |||
| def index? | |||
| self.attachments.blank? | |||
| end | |||
| def site? | |||
| (self.root || self).position == 1 | |||
| end | |||
| def tile? | |||
| (self.root || self).position == 2 | |||
| end | |||
| def node_type | |||
| return "site" if self.site? | |||
| 'tile' | |||
| end | |||
| private | |||
| def remove_empty_tags | |||
| %w"tags_da tags_en tags_de settings excluded_locales".map do |k| | |||
| self.send "#{k}=", self.send(k).reject { |v| v.blank? } unless self.send(k).blank? | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,70 @@ | |||
| class User < ApplicationRecord | |||
| enum role: { user: 'user', admin: 'admin' }, _suffix: true | |||
| include PgSearch::Model | |||
| pg_search_scope :pg_search, against: [:lastname, :firstname, :email, :phone, :title], | |||
| using: {tsearch: {dictionary: "danish"}} | |||
| has_secure_password | |||
| has_many :verification_codes, dependent: :destroy | |||
| before_destroy :dont_destroy_admin | |||
| validates_presence_of :email | |||
| validates_presence_of :password, on: :create | |||
| validates_uniqueness_of :email | |||
| validates_format_of :email, with: URI::MailTo::EMAIL_REGEXP | |||
| normalizes :email, with: -> email { email.strip.downcase } | |||
| validate :cant_change_admin, on: :update | |||
| scope :enabled, -> { where.not enabled_at: nil } | |||
| scope :by_last_modified, ->(rev) { order(updated_at: rev ? :asc : :desc) } | |||
| scope :by_name, ->(rev) { order(lastname: rev ? :desc : :asc, firstname: rev ? :desc : :asc) } | |||
| scope :by_email, ->(rev) { order(email: rev ? :desc : :asc) } | |||
| scope :by_title, ->(rev) { order(title: rev ? :desc : :asc) } | |||
| scope :simple_search, ->(q) { pg_search(q) unless q.blank? } | |||
| def su? | |||
| email == 'mattias@oncotype.dk' | |||
| end | |||
| def name | |||
| return email if lastname.blank? and firstname.blank? | |||
| [firstname, lastname].select{ |v| !v.blank? }.join(' ') | |||
| end | |||
| def initials | |||
| name.split(' ').map{ |s| s[0] }.join('').mb_chars.upcase | |||
| end | |||
| def enabled? | |||
| !self.enabled_at.nil? | |||
| end | |||
| protected | |||
| #Prevent the user admins from beeing changed | |||
| def cant_change_admin | |||
| user = self.class.find(self.id) | |||
| errors.add(:email, I18n.t(:you_cant_change_the_email_on_this_user, scope: 'users')) if user.su? and self.email != user.email | |||
| errors.add(:email, I18n.t(:you_cant_change_this_on_this_user, scope: 'users')) if user.su? and !self.admin_role? and self.role_changed? | |||
| errors.add(:email, I18n.t(:you_cant_disable_this_user, scope: 'users')) if user.su? and self.enabled_at.nil? | |||
| end | |||
| # Prevents the super user admin to be removed" | |||
| def dont_destroy_admin | |||
| raise I18n.t(:cant_destroy_this_user, scope: 'users') if self.su? | |||
| end | |||
| end | |||
| @ -0,0 +1,21 @@ | |||
| class VerificationCode < ApplicationRecord | |||
| VALID_FOR = 15.minutes | |||
| before_validation(on: :create) do | |||
| self.token = rand(100000..999999).to_s | |||
| end | |||
| validates_presence_of :token | |||
| belongs_to :user | |||
| scope :valid, -> { where("verification_codes.created_at > ?", VALID_FOR.ago) } | |||
| scope :expired, -> { where("verification_codes.created_at <= ?", VALID_FOR.ago) } | |||
| def expires_at | |||
| self.created_at + VALID_FOR | |||
| end | |||
| end | |||
| @ -0,0 +1,33 @@ | |||
| <%= tag.div class: 'asset', id: dom_id(asset) do %> | |||
| <div class="asset__thumbnail"> | |||
| <div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %></div> | |||
| </div> | |||
| <%= tag.div asset.title, class: 'asset__title' %> | |||
| <%= tag.div [ | |||
| asset.file.filename.extension_without_delimiter.upcase, | |||
| number_to_human_size(asset.file.byte_size).sub(/\s/, ''), | |||
| [asset.file.metadata.dig('width'), asset.file.metadata.dig('height')].compact.join('x') | |||
| ].reject{ |v| v.blank? }.join(' '), | |||
| class: 'asset__mimetypes' %> | |||
| <%= label_tag nil, class: "icon-cb-round" do %> | |||
| <%= check_box_tag 'asset_ids[]', asset.id, false, id: nil%> | |||
| <span></span> | |||
| <%- end -%> | |||
| <div class="asset-ctrls"> | |||
| <div> | |||
| <%= link_to 'edit', edit_admin_asset_path(asset), data: {turbo_frame: 'main', turbo_action: 'advance'}, title: t('ui.edit') %> | |||
| <%= link_to 'download', rails_blob_path(asset.file, disposition: "attachment"), title: t('ui.download') %> | |||
| </div> | |||
| <%= link_to 'delete_forever', admin_asset_path(asset), data: { turbo_confirm: t(:'ui.are_you_sure'), turbo_method: :delete }, title: t('ui.delete') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,14 @@ | |||
| <%= form_with(model: [ :admin, asset ], class: 'form-plain') do |form| %> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :title } %> | |||
| </div> | |||
| <div class="form-ctrls"> | |||
| <%= link_to t(:'ui.cancel'), url_for(controller: 'assets', action: 'index'), data: {turbo_action: 'advance'} %> | |||
| <%= form.submit t(:'ui.save') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,51 @@ | |||
| <div class="list-title"> | |||
| <h1><%= link_to yield(:title), params.permit(:sort, :reverse), class: 'list-title-link' %></h1> | |||
| <div class="flex"> | |||
| <%= render partial: 'material/search', locals: (params[:node_id] ? {turbo_stream: true} : nil) %> | |||
| <%= tag.form do %> | |||
| <%= tag.label class: "dropzone dropzone--small", | |||
| data: { | |||
| controller: 'upload', | |||
| upload_url_value: rails_direct_uploads_url, | |||
| upload_create_url_value: url_for(controller: 'assets', action: 'upload'), | |||
| upload_target_value: 'assets', | |||
| action: "dragover->upload#dragover dragleave->upload#dragleave drop->upload#drop" | |||
| } do %> | |||
| <span>arrow_upward</span> | |||
| <input type="file" multiple data-action="change->upload#upload"> | |||
| <% end %> | |||
| <% end %> | |||
| </div> | |||
| </div> | |||
| <div class="list-container"> | |||
| <nav class="assets-sort"> | |||
| <%= render partial: 'sort' %> | |||
| <div id="entries_info"> | |||
| <%= entries_info @assets %> | |||
| </div> | |||
| </nav> | |||
| <div class="list"> | |||
| <div class="assets__container"> | |||
| <div id="assets"> | |||
| <%= render @assets %> | |||
| </div> | |||
| <%= tag.div nil, | |||
| id: 'load-more', | |||
| data: { | |||
| controller: "load-more", | |||
| load_more_list_id_value: "results", | |||
| load_more_url_value: path_to_next_page(@assets) | |||
| } unless @assets.blank? or @assets.last_page? %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,12 @@ | |||
| <div data-controller="popup" id="asset__sort"> | |||
| <button class="navbar-link has-popup-menu" data-action="click->popup#toggle"> | |||
| <%= link_to_sortable sorting_technique, t(sorting_technique, scope: 'sort') %> | |||
| </button> | |||
| <div class="popup-menu"> | |||
| <ul> | |||
| <li><%= link_to_sortable :by_filename, t('sort.by_filename'), data: { turbo_stream: true } %></li> | |||
| <li><%= link_to_sortable :by_last_modified, t('sort.by_last_modified'), data: { turbo_stream: true } %></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,3 @@ | |||
| <%= turbo_stream.remove @asset %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| @ -0,0 +1,25 @@ | |||
| <%= content_for :title, @asset.title %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <div class="form-container"> | |||
| <%= link_to t(:'assets.list'), url_for(controller: 'assets', action: 'index'), data: {turbo_action: 'advance'}, class: 'back-link' %> | |||
| <div class="form-header"> | |||
| <h1><%= yield :title %></h1> | |||
| <%= button_to t(:'assets.destroy'), | |||
| url_for(action: 'show', id: @asset), | |||
| method: :delete, | |||
| params: {redirect_to: admin_assets_path}, | |||
| data: { turbo_confirm: t(:'ui.are_you_sure') }, | |||
| tabindex: '-1', | |||
| class: 'delete-link' %> | |||
| </div> | |||
| <%= render "form", asset: @asset %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,57 @@ | |||
| <%= content_for :title, t(:'assets.title') %> | |||
| <% if request.query_string.blank? %> | |||
| <turbo-stream action="append" targets="body"> | |||
| <template> | |||
| <div id="overlay" class="overlay" data-controller="utils assets" data-action="click->utils#closeOverlay" data-assets-url-value="<%= url_for(controller: 'attachments', action: 'new', node_id: params[:node_id] ) %>"> | |||
| <div class="inner-window-container"> | |||
| <%= button_tag 'close', class: 'close-overlay', data: { action: "click->utils#closeOverlay" } %> | |||
| <div id="inner-window"> | |||
| <%= render partial: 'list', formats: :html %> | |||
| <div class="form-ctrls form-ctrls-overlay"> | |||
| <%= button_tag t(:'ui.append'), data: { action: "click->assets#appendAttachments" } %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| </turbo-stream> | |||
| <% else %> | |||
| <turbo-stream action="<%= @assets.first_page? ? 'update' : 'append' %>" target="assets"> | |||
| <template> | |||
| <%= render partial: 'asset', | |||
| collection: @assets, | |||
| formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <turbo-stream action="replace" target="asset__sort"> | |||
| <template> | |||
| <%= render partial: 'sort', formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <turbo-stream action="replace" target="load-more"> | |||
| <template> | |||
| <% if @assets.blank? or @assets.last_page? %> | |||
| <div id='load-more'></div> | |||
| <% else %> | |||
| <%= tag.div nil, | |||
| id: 'load-more', | |||
| data: { | |||
| controller: "load-more", | |||
| load_more_list_id_value: "results", | |||
| load_more_url_value: path_to_next_page(@assets) | |||
| } %> | |||
| <% end %> | |||
| </template> | |||
| </turbo-stream> | |||
| <% end %> | |||
| @ -0,0 +1,9 @@ | |||
| <%= content_for :title, t(:'assets.title') %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <%= render partial: 'list' %> | |||
| <% end %> | |||
| @ -0,0 +1,30 @@ | |||
| <turbo-stream action="<%= @assets.first_page? ? 'update' : 'append' %>" target="assets"> | |||
| <template> | |||
| <%= render partial: 'asset', | |||
| collection: @assets, | |||
| formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <turbo-stream action="replace" target="asset__sort"> | |||
| <template> | |||
| <%= render partial: 'sort', formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <turbo-stream action="replace" target="load-more"> | |||
| <template> | |||
| <% if @assets.blank? or @assets.last_page? %> | |||
| <div id='load-more'></div> | |||
| <% else %> | |||
| <%= tag.div nil, | |||
| id: 'load-more', | |||
| data: { | |||
| controller: "load-more", | |||
| load_more_list_id_value: "results", | |||
| load_more_url_value: path_to_next_page(@assets) | |||
| } %> | |||
| <% end %> | |||
| </template> | |||
| </turbo-stream> | |||
| @ -0,0 +1,7 @@ | |||
| <turbo-stream action="prepend" targets="#assets"> | |||
| <template> | |||
| <%= render partial: 'asset', object: @asset, formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="attachment__asset"> | |||
| <div class="asset__thumbnail"> | |||
| <div><%= image_tag rails_storage_proxy_path(asset.file.representation(resize_to_limit: [320,320], format: :jpg)) if asset.file.representable? %></div> | |||
| </div> | |||
| <%= tag.div asset.title, class: 'asset__title' %> | |||
| <%= tag.div [ | |||
| asset.file.filename.extension_without_delimiter.upcase, | |||
| number_to_human_size(asset.file.byte_size).sub(/\s/, ''), | |||
| [asset.file.metadata.dig('width'), asset.file.metadata.dig('height')].compact.join('x') | |||
| ].reject{ |v| v.blank? }.join(' '), | |||
| class: 'asset__mimetypes' %> | |||
| </div> | |||
| @ -0,0 +1,43 @@ | |||
| <div class="attachment attachment-<%= f.object.attachable_for&.node_type %>" data-controller="fields"> | |||
| <div class="attachment__content"> | |||
| <%= render partial: 'admin/attachments/asset', collection: Array(f.object.asset) %> | |||
| <%= render partial: 'material/trix_field_i18n', locals: { f: f, attr: :body } %> | |||
| <div class="attachment__content-three"> | |||
| <%= render partial: 'material/text_field', locals: { f: f, attr: :bg_color } %> | |||
| <%= render partial: 'material/text_field', locals: { f: f, attr: :fg_color } %> | |||
| <%= render partial: 'material/select_field', | |||
| locals: { | |||
| f: f, | |||
| attr: :alignment, | |||
| choices: (f.object.attachable_for&.site? ? Attachment::ALIGNMENT_SITE : Attachment::ALIGNMENT_TILE).map { |k,v| [t(k, scope: :'attachments.alignments'), k] }, | |||
| include_blank: true | |||
| } %> | |||
| <%= render partial: 'material/select_field', | |||
| locals: { | |||
| f: f, | |||
| attr: :template, | |||
| choices: Array(Attachment::TEMPLATE_SITE_ASSET).map { |k,v| [t(k, scope: :'attachments.templates'), k] }, | |||
| include_blank: true | |||
| } %> | |||
| </div> | |||
| </div> | |||
| <%= button_tag 'delete', | |||
| type: 'button', | |||
| data: { action: 'click->fields#removeField' }, | |||
| class: 'destroy__field-btn' %> | |||
| <%= f.hidden_field :_destroy, class: 'destroy__field' %> | |||
| <%= f.hidden_field :position, class: 'position' %> | |||
| <%= f.hidden_field :asset_id %> | |||
| <div class="handle">more_vert</div> | |||
| </div> | |||
| @ -0,0 +1,14 @@ | |||
| <turbo-stream action="append" targets="#attachments"> | |||
| <template> | |||
| <% @attachments.each_with_index do |attachment, i| %> | |||
| <%= fields_for "#{@attachable_for.class.name.underscore.singularize}[attachments_attributes][#{ Time.now.to_i + i }]", attachment do |builder| %> | |||
| <%= render partial: 'admin/attachments/attachment', locals: {f: builder}, formats: :html %> | |||
| <% end %> | |||
| <% end %> | |||
| <%# fields model: @node do |form| %> | |||
| <%# form.fields_for :attachments, @attachments do |builder| %> | |||
| <%# render partial: 'admin/attachments/attachment', locals: {f: builder} %> | |||
| <%# end%> | |||
| <%# end %> | |||
| </template> | |||
| </turbo-stream> | |||
| @ -0,0 +1,5 @@ | |||
| <div id="drawer" data-controller="nodes" data-url="<%= url_for(controller: 'nodes', action: 'toggle') %>"> | |||
| <div id="drawer-structure"> | |||
| <%= render Node.roots.ordered %> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,14 @@ | |||
| <div class="form-container"> | |||
| <div class="form-nav-links"> | |||
| <%= link_to t(:'nodes.list'), | |||
| url_for(action: 'tree' , id: @node.parent&.id), | |||
| data: { turbo_stream: true }, | |||
| class: 'back-link' %> | |||
| <%= button_to t(:'nodes.destroy'), url_for(action: 'show', id: @node), method: :delete, data: { turbo_confirm: t('ui.are_you_sure') }, tabindex: '-1', class: 'delete-link' %> | |||
| </div> | |||
| <%= render "form" , node: @node %> | |||
| </div> | |||
| @ -0,0 +1,203 @@ | |||
| <%= form_with(model: [:admin, node], | |||
| class: 'form-plain has--key-ctrls', | |||
| id: 'node_form', | |||
| data: { | |||
| locale: form_locale, | |||
| controller: "i18n-form" | |||
| }) do |form| %> | |||
| <div class="form-header__titled"> | |||
| <%= form.label :title, class: "title-box", data: { icon: t("nodes.icons.#{@node.category}") } do %> | |||
| <%= render partial: 'material/text_field_i18n_simple', locals: { f: form, attr: :title } %> | |||
| <% end %> | |||
| <div class="i18n__from-ctrls"> | |||
| <%- I18n.available_locales.each do |locale| %> | |||
| <%= label_tag do %> | |||
| <%= radio_button_tag 'form_locale', locale, form_locale == locale, data: {action: 'change->i18n-form#setFormLocale' } %> | |||
| <%= tag.span locale %> | |||
| <% end %> | |||
| <% end %> | |||
| </div> | |||
| </div> | |||
| <div class="form-section" id="node-attachments"> | |||
| <div class="field"> | |||
| <div> | |||
| <label><%= t(:'activerecord.attributes.node.attachments') %></label> | |||
| <div class="button-container"> | |||
| <%= link_to t('ui.append_text'), | |||
| url_for(controller: 'attachments', action: 'new', node_id: node.id), | |||
| class: 'btn', | |||
| data: { turbo_stream: true } %> | |||
| <%= link_to t('ui.append_asset'), | |||
| url_for(controller: 'assets', action: 'index', node_id: node.id), | |||
| class: 'btn', | |||
| data: { | |||
| controller: 'utils', | |||
| action: 'click->utils#disableScroll', | |||
| turbo_stream: true | |||
| } %> | |||
| </div> | |||
| </div> | |||
| <div id="attachments" data-controller="sort"> | |||
| <%= form.fields_for :attachments do |builder| %> | |||
| <%= render partial: 'admin/attachments/attachment', locals: {f: builder} %> | |||
| <% end%> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/select_field', | |||
| locals: { | |||
| f: form, | |||
| attr: :parent_id, | |||
| choices: (@node.root? ? [] : node_structure_for_select(@node.root, @node)), | |||
| selected: @node.parent_id, | |||
| include_blank: false | |||
| } unless form.object.root? %> | |||
| <%= render partial: 'material/select_field', | |||
| locals: { | |||
| f: form, | |||
| attr: :template, | |||
| include_blank: form.object.root?, | |||
| choices: Node.templates.slice(*(Node::NODE_TEMPLATES)).map { |k,v| [t(k, scope: :'nodes.templates'), k] }.sort } %> | |||
| <%= render partial: 'material/tom_select_field', | |||
| locals: { | |||
| f: form, | |||
| attr: :settings, | |||
| choices: Node::SETTINGS.map{ |tag| [ t(tag, scope: :'nodes.settings') , tag.to_s] }.sort, | |||
| multiple: true | |||
| } %> | |||
| <%= render partial: 'material/text_field_i18n', locals: { f: form, attr: :href } unless form.object.root? %> | |||
| <%= render partial: 'material/tom_select_field_i18n', | |||
| locals: { | |||
| f: form, | |||
| attr: :tags, | |||
| choices: Node.tags, | |||
| multiple: true, | |||
| tags: true | |||
| } unless form.object.root? %> | |||
| </div> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/text_field_i18n', locals: { f: form, attr: :page_title } %> | |||
| <%= render partial: 'material/text_field_i18n', locals: { f: form, attr: :page_description } %> | |||
| <%= tag.div class: 'field' do %> | |||
| <div> | |||
| <%= form.label :slug, for: nil, class: 'i18n__label' %> | |||
| </div> | |||
| <div> | |||
| <%- I18n.available_locales.each do |locale| %> | |||
| <%- i18n_attr = "slug_#{locale}" -%> | |||
| <%= form.label i18n_attr, class: "input-box input-box-url i18n__input i18n__input-#{locale}" do %> | |||
| <%= tag.span File.join(form.object.parent&.url(locale: locale) || '', ''), class: 'base__url' %> | |||
| <%= form.text_field i18n_attr, | |||
| class: 'material__input', | |||
| disabled: form.object.root? %> | |||
| <% end %> | |||
| <%- form.object.errors.full_messages_for(i18n_attr).uniq.each do |msg| -%> | |||
| <%= content_tag :p, msg, role: 'alert' %> | |||
| <% end %> | |||
| <% end %> | |||
| </div> | |||
| <%- end -%> | |||
| </div> | |||
| <div class="form-section"> | |||
| <div class="field"> | |||
| <div> | |||
| <%= form.label :status, for: nil %> | |||
| </div> | |||
| <div> | |||
| <ul class="node__status"> | |||
| <%- Node.statuses.each do |status| -%> | |||
| <li><%= form.radio_button :status, status[0], id: status[1] %> <%= label_tag status[1], t(status[0], scope: :'nodes.statuses'), class: 'plain' %></li> | |||
| <% end %> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div><%= form.label :published_at %></div> | |||
| <div> | |||
| <div class="datetime__select"> | |||
| <%= form.datetime_select :published_at, | |||
| {datetime_separator: '<span>@</span>', | |||
| time_separator: '<span>:</span>', | |||
| use_short_month: true}, {class: 'material__input material__input-select'} %> | |||
| </div> | |||
| <%- form.object.errors.full_messages_for(:published_at).uniq.each do |msg| -%> | |||
| <%= content_tag :p, msg, role: 'alert' %> | |||
| <% end %> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div><%= form.label :expires_at %></div> | |||
| <div> | |||
| <div data-controller="utils" class="datetime__select with-toggles"> | |||
| <%= form.datetime_select :expires_at, | |||
| {datetime_separator: '<span>@</span>', | |||
| time_separator: '<span>:</span>', | |||
| use_short_month: true, | |||
| default: (1.month.from_now.at_midnight-1.minute), | |||
| disabled: (@node.expires_at.blank?)}, { class: 'material__input material__input-select' } %> | |||
| <%= button_tag 'add_alarm', type: 'button', data: { action: 'click->utils#toggleDisabled' } %> | |||
| <%= button_tag 'remove_circle_outline', type: 'button', data: { action: 'click->utils#toggleDisabled' } %> | |||
| </div> | |||
| <%- form.object.errors.full_messages_for(:expires_at).uniq.each do |msg| -%> | |||
| <%= content_tag :p, msg, role: 'alert' %> | |||
| <% end %> | |||
| </div> | |||
| </div> | |||
| <%= render partial: 'material/tom_select_field', | |||
| locals: { | |||
| f: form, | |||
| attr: :excluded_locales, | |||
| choices: options_for_select(I18n.available_locales.map{ |v| [t(v, scope: :'nodes.langs'), v.to_s] }, form.object.excluded_locales), | |||
| multiple: true | |||
| } %> | |||
| <%= render partial: 'material/check_box_icon', locals: { f: form, attr: :is_allowlist} %> | |||
| </div> | |||
| <div class="form-ctrls"> | |||
| <%= link_to t(:'ui.cancel'), | |||
| url_for(action: 'tree', id: @node.parent&.id), | |||
| data: { | |||
| turbo_stream: true, | |||
| turbo_action: 'advance' | |||
| } %> | |||
| <%= form.submit t(:'ui.save') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,17 @@ | |||
| <%= tag.div class: "node#{' loaded' if open_node_ids.include?(node.id)}#{' current' if node.id == current_node_id}", | |||
| id: dom_id(node), | |||
| data: { | |||
| id: node.id | |||
| } do %> | |||
| <%= tag.div class: "node-row #{node.category}#{' closed' unless open_node_ids.include?(node.id)}", | |||
| style: "--level: #{node.depth}" do %> | |||
| <%= spacer_node node %> | |||
| <%= toggle_node node %> | |||
| <%= drawer_node_link node %> | |||
| <% end %> | |||
| <%= render node.children.ordered if open_node_ids.include?(node.id) %> | |||
| <% end %> | |||
| @ -0,0 +1,24 @@ | |||
| <div> | |||
| <div class="list-title"> | |||
| <div class="node-path" data-controller="nodes"> | |||
| <%= tree_title(@node) %> | |||
| </div> | |||
| </div> | |||
| <div class="list tree-children"> | |||
| <div class="list-header"> | |||
| <div class="cell"><%= t(:'activerecord.attributes.node.title') %></div> | |||
| <div class="cell flags"></div> | |||
| <div class="cell"><%= t(:'activerecord.attributes.node.occasions') %></div> | |||
| <div class="cell"><%= t(:'ui.updated') %></div> | |||
| <div class="cell actions"></div> | |||
| </div> | |||
| <div id="tree-nodes" data-controller="sort" data-sortable-url="<%= url_for(controller: 'nodes', action: 'sort') %>"> | |||
| <%= render partial: 'tree_node', collection: @node ? @node.children.ordered : Node.roots.ordered, as: 'node' %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,38 @@ | |||
| <div class="row" data-id="<%= node.id %>"> | |||
| <div class="cell" data-controller="nodes"> | |||
| <%= link_to node_link_title(node), | |||
| url_for(action: 'tree', id: node), | |||
| class: 'node-link', | |||
| data: { | |||
| turbo_stream: true, | |||
| icon: t("nodes.icons.#{node.category}"), | |||
| nodes_id_param: node.id, | |||
| action: 'click->nodes#set_current' | |||
| } %> | |||
| </div> | |||
| <div class="cell flags"> | |||
| </div> | |||
| <div class="cell date"> | |||
| <%# node_flags(node) %> | |||
| <ul> | |||
| <% node.occasions.each do |occasion| %> | |||
| <%= tag.li occasion_date(occasion) %> | |||
| <% end %> | |||
| </ul> | |||
| </div> | |||
| <div class="cell"> | |||
| <%= audit_info node %> | |||
| </div> | |||
| <div class="cell actions"> | |||
| <%= link_to 'edit', edit_admin_node_path(node), class:"icon size--medium round", data: {turbo_action: 'advance'} %> | |||
| <%= tag.div 'more_vert', class: 'handle' %> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,8 @@ | |||
| <%= turbo_stream.append @node do %> | |||
| <% @node.children.ordered.each do |node| %> | |||
| <%= render node, closed: true %> | |||
| <% end %> | |||
| <% end %> | |||
| @ -0,0 +1,27 @@ | |||
| <%= turbo_stream.append "tree-nodes" do %> | |||
| <%= render partial: 'tree_node', object: @node, as: 'node' %> | |||
| <% end %> | |||
| <% if @node.parent %> | |||
| <turbo-stream action="replace" targets="#<%= dom_id(@node.parent) %>"> | |||
| <template> | |||
| <%= render partial: 'node', object: @node.parent %> | |||
| </template> | |||
| </turbo-stream> | |||
| <turbo-stream action="replace" targets="#<%= dom_id(@node.parent, 'list-title-link') %>"> | |||
| <template><%= tree_node_title_link @node.parent %></template> | |||
| </turbo-stream> | |||
| <% else %> | |||
| <turbo-stream action="append" targets="#drawer-structure"> | |||
| <template> | |||
| <%= render partial: 'node', object: @node %> | |||
| </template> | |||
| </turbo-stream> | |||
| <% end %> | |||
| @ -0,0 +1,16 @@ | |||
| <%= turbo_stream.remove @destroyed_node %> | |||
| <% if @node %> | |||
| <turbo-stream action="replace" targets="#<%= dom_id(@node) %>"> | |||
| <template> | |||
| <%= render partial: 'node', object: @node %> | |||
| </template> | |||
| </turbo-stream> | |||
| <% end %> | |||
| <%= turbo_stream.update "tree" do %> | |||
| <%= render partial: "tree" %> | |||
| <% end %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| @ -0,0 +1,13 @@ | |||
| <%= content_for :title, @node.title %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash' , partial: 'layouts/flash' %> | |||
| <%= render partial: 'drawer' %> | |||
| <%= turbo_frame_tag 'tree' do %> | |||
| <%= render partial: 'edit' %> | |||
| <% end %> | |||
| <% end %> | |||
| @ -0,0 +1,5 @@ | |||
| <%= turbo_stream.update "tree" do %> | |||
| <%= render partial: "edit" %> | |||
| <% end %> | |||
| @ -0,0 +1,15 @@ | |||
| <%= content_for :title, t(:'nodes.title') %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <%= render partial: 'drawer' %> | |||
| <%= turbo_frame_tag 'tree' do %> | |||
| <%= render partial: 'tree' %> | |||
| <% end %> | |||
| <% end %> | |||
| @ -0,0 +1,15 @@ | |||
| <% if @node.root? %> | |||
| <turbo-stream action="update" target="drawer-structure"> | |||
| <template> | |||
| <%= render partial: 'node', collection: Node.roots.ordered, formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <% end %> | |||
| <% if @node.parent %> | |||
| <turbo-stream action="replace" targets="#<%= dom_id(@node.parent) %>.loaded"> | |||
| <template> | |||
| <%= render partial: 'node', object: @node.parent, formats: :html %> | |||
| </template> | |||
| </turbo-stream> | |||
| <% end %> | |||
| @ -0,0 +1,3 @@ | |||
| <%= turbo_stream.update 'tree' do %> | |||
| <%= render partial: 'tree' %> | |||
| <% end %> | |||
| @ -0,0 +1,10 @@ | |||
| <%= turbo_stream.replace "node_form" do %> | |||
| <%= render "form", node: @node %> | |||
| <% end %> | |||
| <turbo-stream action="replace" targets="#<%= dom_id(@node, 'drawer-link') %>"> | |||
| <template><%= drawer_node_link @node %></template> | |||
| </turbo-stream> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| @ -0,0 +1,29 @@ | |||
| <%= link_to(svg('ikea-foundation'), root_path) %> | |||
| <%= form_tag url_for(action: 'index', locale: nil) do %> | |||
| <div> | |||
| <%= label_tag :email, t('sessions.email') %> | |||
| <%= label_tag :email, class: "input-box" do %> | |||
| <%= text_field_tag :email, params[:email] %> | |||
| <% end %> | |||
| </div> | |||
| <%= content_tag :div, class: flash.any? ? 'with-errors' : nil do %> | |||
| <%= label_tag :password, t('sessions.password') %> | |||
| <%= label_tag :password, class: "input-box" do %> | |||
| <%= password_field_tag :password %> | |||
| <% end %> | |||
| <%- flash.each do |name, msg| -%> | |||
| <%= content_tag :p, msg, role: 'alert' %> | |||
| <%- end -%> | |||
| <%- end -%> | |||
| <div> | |||
| <%= button_tag t(:'sessions.login') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,20 @@ | |||
| <%= link_to(svg('ikea-foundation'), root_path) %> | |||
| <%= form_tag url_for(action: 'verify', locale: nil) do %> | |||
| <%= content_tag :div, class: flash.any? ? 'with-errors' : nil do %> | |||
| <%= label_tag :verification_code, t('sessions.verification_code') %> | |||
| <%= label_tag :verification_code, class: "input-box" do %> | |||
| <%= text_field_tag :verification_code, params[:verification_code] %> | |||
| <% end %> | |||
| <%- flash.each do |name, msg| -%> | |||
| <%= content_tag :p, msg, role: 'alert' %> | |||
| <%- end -%> | |||
| <% end %> | |||
| <div> | |||
| <%= button_tag t(:'sessions.verify_email') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,27 @@ | |||
| <%= form_with(model: [ :admin, user ], class: 'form-plain has--key-ctrls' ) do |form| %> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/check_box_icon', locals: { f: form, attr: :enabled_at, label: t(:'activerecord.attributes.user.enabled_at')} %> | |||
| <%= render partial: 'material/select_field', locals: { f: form, attr: :role, include_blank: false, choices: User.roles.map { |k,v| [t(k, scope: :'users.roles'), k] }.sort } %> | |||
| </div> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :firstname } %> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :lastname } %> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :title } %> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :email } %> | |||
| <%= render partial: 'material/text_field', locals: { f: form, attr: :phone } %> | |||
| </div> | |||
| <div class="form-section"> | |||
| <%= render partial: 'material/password_field', locals: { f: form, attr: :password } %> | |||
| <%= render partial: 'material/password_field', locals: { f: form, attr: :password_confirmation } %> | |||
| </div> | |||
| <div class="form-ctrls"> | |||
| <%= link_to t(:'ui.cancel'), url_for(controller: 'users', action: 'index'), data: {turbo_action: 'advance'} %> | |||
| <%= form.submit t(:'ui.save') %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,29 @@ | |||
| <div class="row" id="<%= dom_id user %>"> | |||
| <div class="cell"> | |||
| <ul> | |||
| <li><%= user.name %></li> | |||
| <li class="user__role"><%= t user.role, scope: 'users.roles' %></li> | |||
| </ul> | |||
| </div> | |||
| <div class="cell"> | |||
| <%= user.email %> | |||
| </div> | |||
| <div class="cell"> | |||
| <%= user.phone %> | |||
| </div> | |||
| <div class="cell"> | |||
| <ul> | |||
| <li> | |||
| <% if user.enabled? %> | |||
| <span class="icon size--small enabled">check_circle</span> <span><%= t :'ui.active' %></span> | |||
| <% else %> | |||
| <span class="icon size--small disabled">cancel</span> <span><%= t :'ui.inactive' %></span> | |||
| <% end %> | |||
| </li> | |||
| <li><%= audit_info user %></li> | |||
| </ul> | |||
| </div> | |||
| <div class="cell actions"> | |||
| <%= link_to 'edit', url_for(action: 'edit', id: user), class:"icon size--medium round", data: {turbo_action: 'advance'} %> | |||
| </div> | |||
| </div> | |||
| @ -0,0 +1,19 @@ | |||
| <%= content_for :title, @user.name %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <div class="form-container"> | |||
| <%= link_to t(:'users.list'), url_for(controller: 'users', action: 'index'), data: {turbo_action: 'advance'}, class: 'back-link' %> | |||
| <div class="form-header"> | |||
| <h1><%= yield :title %></h1> | |||
| <%= button_to t(:'users.destroy'), url_for(action: 'show', id: @user), method: :delete, data: { turbo_confirm: t(:'ui.are_you_sure') }, tabindex: '-1', class: 'delete-link' %> | |||
| </div> | |||
| <%= render "form", user: @user %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,41 @@ | |||
| <%= content_for :title, t(:'users.title') %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <div class="list-title"> | |||
| <h1><%= link_to yield(:title), params.permit(:sort, :reverse), class: 'list-title-link' %></h1> | |||
| <div class="flex"> | |||
| <%= render 'material/search' %> | |||
| <%= link_to tag.span(t(:'ui.new')), url_for(action: 'new'), class: 'btn add-btn', data: {turbo_action: 'advance'} %> | |||
| </div> | |||
| </div> | |||
| <div class="list-container"> | |||
| <div class="list" > | |||
| <div class="list-header"> | |||
| <div class="cell"><%= link_to_sortable :by_name, t(:'activerecord.attributes.user.name') %></div> | |||
| <div class="cell"><%= link_to_sortable :by_email, t(:'activerecord.attributes.user.email') %></div> | |||
| <div class="cell"><%= t(:'activerecord.attributes.user.phone') %></div> | |||
| <div class="cell"><%= t(:'activerecord.attributes.user.status') %></div> | |||
| <div class="cell actions"></div> | |||
| </div> | |||
| <div id="users"> | |||
| <%= render @users %> | |||
| </div> | |||
| <div class="pagination-container"> | |||
| <div><%= page_entries_info @users %></div> | |||
| <%= paginate @users %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,17 @@ | |||
| <%= content_for :title, t(:'users.new') %> | |||
| <%= turbo_frame_tag 'main' do %> | |||
| <%= turbo_stream.append 'flash', partial: 'layouts/flash' %> | |||
| <div class="form-container"> | |||
| <%= link_to t(:'users.list'), url_for(controller: 'users', action: 'index'), data: {turbo_action: 'advance'}, class: 'back-link' %> | |||
| <div class="form-header"> | |||
| <h1><%= yield :title %></h1> | |||
| </div> | |||
| <%= render "form", user: @user %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,11 @@ | |||
| <%# Link to the "First" page | |||
| - available local variables | |||
| url: url to the first page | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="first"> | |||
| <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %> | |||
| </span> | |||
| @ -0,0 +1,8 @@ | |||
| <%# Non-link tag that stands for skipped pages... | |||
| - available local variables | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="page gap"><%= t('views.pagination.truncate').html_safe %></span> | |||
| @ -0,0 +1,11 @@ | |||
| <%# Link to the "Last" page | |||
| - available local variables | |||
| url: url to the last page | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="last"> | |||
| <%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %> | |||
| </span> | |||
| @ -0,0 +1,11 @@ | |||
| <%# Link to the "Next" page | |||
| - available local variables | |||
| url: url to the next page | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="next"> | |||
| <%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote %> | |||
| </span> | |||
| @ -0,0 +1,12 @@ | |||
| <%# Link showing page number | |||
| - available local variables | |||
| page: a page object for "this" page | |||
| url: url to this page | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="page<%= ' current' if page.current? %>"> | |||
| <%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %> | |||
| </span> | |||
| @ -0,0 +1,18 @@ | |||
| <%# The container tag | |||
| - available local variables | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| paginator: the paginator that renders the pagination tags inside | |||
| -%> | |||
| <%= paginator.render do -%> | |||
| <nav class="pagination" role="navigation" aria-label="pager"> | |||
| <%= first_page_tag %> | |||
| <%= prev_page_tag %> | |||
| <% unless current_page.out_of_range? %> | |||
| <%= next_page_tag %> | |||
| <%= last_page_tag %> | |||
| <% end %> | |||
| </nav> | |||
| <% end -%> | |||
| @ -0,0 +1,11 @@ | |||
| <%# Link to the "Previous" page | |||
| - available local variables | |||
| url: url to the previous page | |||
| current_page: a page object for the currently displayed page | |||
| total_pages: total number of pages | |||
| per_page: number of items to fetch per page | |||
| remote: data-remote | |||
| -%> | |||
| <span class="prev"> | |||
| <%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %> | |||
| </span> | |||
| @ -0,0 +1,5 @@ | |||
| <% flash.each do |flash_type, message| %> | |||
| <div class="flash__message" data-controller="utils" data-action="animationend->utils#remove"> | |||
| <span class="icon">info</span></span><%= tag.span message %> | |||
| </div> | |||
| <% end %> | |||
| @ -0,0 +1,70 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <title><%= content_for?(:title) ? yield(:title) : ENV['PROJECT_NAME_LONG'] %></title> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |||
| <meta name="turbo-prefetch" content="false"> | |||
| <%= csrf_meta_tags %> | |||
| <%= csp_meta_tag %> | |||
| <%= stylesheet_link_tag "admin", "trix", "tom-select", "popup-menu", "forms", "lists", "assets", "nodes", "attachments" %> | |||
| <%= javascript_importmap_tags 'admin' %> | |||
| </head> | |||
| <body> | |||
| <nav id="navbar"> | |||
| <div class="navbar-upper"> | |||
| <% %i[nodes assets].each do |c| %> | |||
| <%= link_to url_for(controller: c, action: 'index'), | |||
| class: (controller_name == c.to_s ? 'navbar-link current' : 'navbar-link'), | |||
| data: { | |||
| controller: 'navbar', | |||
| action: 'click->navbar#set_current', | |||
| turbo_frame: 'main', | |||
| turbo_action: 'advance' | |||
| } do %> | |||
| <span class="icon"><%= t c, scope: 'icons' %></span> | |||
| <% end %> | |||
| <% end %> | |||
| </div> | |||
| <%= link_to svg('ikea-foundation-white'), root_url, class: 'logo', target: '_blank' %> | |||
| <div class="navbar-lower"> | |||
| <% %i[users].each do |c| %> | |||
| <%= link_to url_for(controller: c, action: 'index'), | |||
| class: (controller_name == c.to_s ? 'navbar-link current' : 'navbar-link'), | |||
| data: { | |||
| controller: 'navbar', | |||
| action: 'click->navbar#set_current', | |||
| turbo_frame: 'main', | |||
| turbo_action: 'advance' | |||
| } do %> | |||
| <span class="icon"><%= t c, scope: 'icons' %></span> | |||
| <% end %> | |||
| <% end if current_user.admin_role? %> | |||
| <div data-controller="popup"> | |||
| <button class="navbar-link has-popup-menu open--right" data-action="click->popup#toggle"> | |||
| <span class="icon">settings</span> | |||
| </button> | |||
| <div class="popup-menu"> | |||
| <ul> | |||
| <li><%= tag.div current_user.email %></li> | |||
| <li><%= link_to t('sessions.logout'), admin_logout_path, data: {icon: 'logout', turbo_frame: '_top'} %></li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </nav> | |||
| <%= yield %> | |||
| <div id="flash"> | |||
| <%= render 'layouts/flash' %> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| @ -1,13 +1,121 @@ | |||
| <!DOCTYPE html> | |||
| <!doctype html> | |||
| <html> | |||
| <head> | |||
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | |||
| <style> | |||
| /* Email styles need to be inline */ | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <%= yield %> | |||
| </body> | |||
| </html> | |||
| <head> | |||
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |||
| <style> | |||
| #outlook a { | |||
| padding: 0; | |||
| } | |||
| body { | |||
| margin: 0; | |||
| padding: 0; | |||
| -webkit-text-size-adjust: 100%; | |||
| -ms-text-size-adjust: 100% | |||
| } | |||
| table, | |||
| td { | |||
| border-collapse: collapse; | |||
| mso-table-lspace: 0; | |||
| mso-table-rspace: 0 | |||
| } | |||
| img { | |||
| border: 0; | |||
| height: auto; | |||
| line-height: 100%; | |||
| outline: 0; | |||
| text-decoration: none; | |||
| -ms-interpolation-mode: bicubic; | |||
| } | |||
| p { | |||
| font-family: system-ui, sans-serif; | |||
| font-size: 18px; | |||
| line-height: 26px; | |||
| font-weight: 400; | |||
| text-align: center; | |||
| margin: 0 0 0 0; | |||
| } | |||
| .booking p { | |||
| text-align: left; | |||
| } | |||
| h1 { | |||
| font-family: system-ui, sans-serif; | |||
| font-size: 32px; | |||
| line-height: 1.2; | |||
| margin: 0 0 0 0; | |||
| font-weight: bold; | |||
| } | |||
| h2 { | |||
| font-family: system-ui, sans-serif; | |||
| font-size: 24px; | |||
| line-height: 1.2; | |||
| margin: 0 0 0 0; | |||
| font-weight: bold; | |||
| } | |||
| h3 { | |||
| font-family: system-ui, sans-serif; | |||
| font-size: 18px; | |||
| line-height: 1.2; | |||
| margin: 1em 0 0 0; | |||
| font-weight: bold; | |||
| } | |||
| .align-left { | |||
| text-align: left; | |||
| } | |||
| a.btn { | |||
| font-family: system-ui, sans-serif; | |||
| color: #FFFFFF; | |||
| background-color: #FF4F32; | |||
| text-decoration: none; | |||
| text-transform: uppercase; | |||
| padding: 9px 40px 9px 40px; | |||
| } | |||
| .column-px-320 { | |||
| width: 320px !important; | |||
| max-width: 320px | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <table border="0" cellpadding="0" cellspacing="0" width="100%"> | |||
| <tbody> | |||
| <tr> | |||
| <td align="center" valign="top" style="padding: 40px 20px 0 20px;"> | |||
| <table border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 480px;"> | |||
| <tbody> | |||
| <tr> | |||
| <td align='center' style="height: 58px; text-align: left; padding: 0 0 40px 0"> | |||
| <%# link_to image_tag('den-hirschsprungske-samling.png', size: "180x58" , alt: I18n.t(:client_name)), | |||
| root_url, title: I18n.t(:client_name) %> | |||
| <h1> | |||
| Two-factor authentication | |||
| </h1> | |||
| </td> | |||
| </tr> | |||
| <%= yield %> | |||
| </tbody> | |||
| </table> | |||
| </td> | |||
| </tr> | |||
| </table> | |||
| </body> | |||
| </html> | |||