diff --git a/config.toml b/config.toml index 303c623..372f87c 100644 --- a/config.toml +++ b/config.toml @@ -13,6 +13,22 @@ priority = -1 bgColor = '#ffffff' resampleFilter = 'CatmullRom' +[markup] + [markup.highlight] + anchorLineNos = false + codeFences = true + guessSyntax = false + hl_Lines = '' + hl_inline = false + lineAnchors = '' + lineNoStart = 1 + lineNos = false + lineNumbersInTable = true + noClasses = false + noHl = false + style = 'emacs' + tabWidth = 4 + [params] company = "Leigh Hackspace CIC" company_number = "09404083" diff --git a/content/blog/2024/creating-an-appletalk-nas/index.md b/content/blog/2024/creating-an-appletalk-nas/index.md new file mode 100644 index 0000000..8f82d5a --- /dev/null +++ b/content/blog/2024/creating-an-appletalk-nas/index.md @@ -0,0 +1,202 @@ +--- +title: "Creating an AppleTalk NAS" +subtitle: "Enabling file sharing with pre-OSX Mac systems" +date: 2024-02-21T07:40:38Z +tags: + - macos + - appletalk + - apple +draft: false +author: Andrew Williams +author_email: andy@tensixtyone.com +listing_image: network-browser.jpg +--- +{{< image src="netatalk.png" width="400x" class="is-pulled-right" title="The Netatalk logo.">}} + +Working on [retro Apple Macs](../../2023/powerbook-g4-disk-replacement/) sometimes presents its own challenges in a modern context, most systems make use of either outdated or dying media which may be unreliable or difficult to locate. In my case, I've recently acquired a Power Mac G4 Sawtooth which while it has USB the interface is slow and wrangling files between computers using USB sticks isn't the quickest method. For even older Macs this isn't even an option, so you have to resort to burnt CDs, Zip disks, or floppy disks. Thankfully Apple has always supported some level of networking on even their earliest machines; AppleTalk. + +[Netatalk](https://netatalk.io) is a modern implementation of the AppleTalk protocols and can be used to provide file shares and time services over Ethernet. To connect to LocalTalk devices you'll need a [quite expensive network converter](). We're going to setu p, install, and configure a basic Netatalk 2.3 server on Ubuntu 24.02 and share a folder and time services. + +## System preparation + +First of all, you'll need a Ubuntu 24.02 system, or any Debian-based distribution for that matter, which is out of the scope of this article. As we'll be building Netatalk from source (as no package is available for the later Ubuntu/Debian versions) we'll need to install some prereq packages: + +```bash-session +# apt-get install automake libtool build-essential pkg-config checkinstall git-core avahi-daemon libavahi-client-dev libssl-dev libdb5.3-dev db-util db5.3-util libgcrypt20 libgcrypt20-dev libcrack2-dev libpam0g-dev libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libwrap0-dev systemtap-sdt-dev libacl1-dev libldap2-dev libevent-dev +``` + +## Downloading, Compiling, and installing Netatalk + +```bash-session +# curl -O netatalk-2.3.1.tar.bz2 https://sourceforge.net/projects/netatalk/files/netatalk-2-3-1/netatalk-2.3.1.tar.bz2/download +# tar jxvf netatalk-2.3.1.tar.bz2 +``` + +You should have a folder named `netatalk-2.3.1` as the result. Now we need to configure the build, for anyone who has ever compiled any standard C project in the last ten years should find this quite familiar: + +```bash-session +# ./configure --enable-systemd --sysconfdir=/etc --with-uams-path=/usr/lib/netatalk --enable-ddp +``` + +A quick rundown of the options used here: + +* `--enable-systemd` - Creates systemd compatible startup scripts, needed for Ubuntu/Debian +* `--sysconfdir=/etc` - Set the configuration folder to `/etc`, if you want you can have this set to `/usr/local/etc` to completely keep it separate from the distribution install. +* `--with-uams-path=/usr/local/lib/netatalk` - Destination for the 'UAMS' modules, which provide authentication methods. +* `--enable-ddp` - Enables AppleTalk, should be on by default but just to make sure... + +Netatalk includes several other options, all of which can be viewed by using `./configure --help`, these include configuring CUPS printing via PAP, SLP support for OSX 10.1, and various backend database support for metadata (which the stock one is just fine). + +Now we need to build and install: + +```bash-session +# make +# make install +# systemctl daemon-reload +``` + +If everything worked as expected, you should have a `/etc/netatalk` folder with some basic configuration files, and the main executables in `/usr/sbin`. + +## Configuring Netatalk + +### `atalkd` + +`atalkd` is the service that handles the AppleTalk over Ethernet side of the services. The service heavily leans on the kernel's support for AppleTalk which is still included in most modern Linux kernels, including Ubuntu and Debian. To be sure that it is available run `modprobe appletalk`. + +Out of the box, `atalkd` should run without any additional configuration, but we're going to modify some parameters for the small network I have at home. + +**/etc/netatalk/atalkd.conf** +``` +ens192 -router -phase 2 -net 1 -addr 1.1 -zone "Doofnet" +``` + +So, to break down what that means: + +* `ens192` - the network interface we want to speak AppleTalk on, in this instance, we're using a VM so the primary network interface is `ens192`, but on your system, it'll be different. +* `-router` - advertise this service as a router. +* `-phase 2` - use ['Phase II' AppleTalk over Ethernet](https://en.wikipedia.org/wiki/AppleTalk). +* `-net 1 -addr 1.1` - The network and node ID of this service, **this can be skipped** as `atalkd` will auto-generate this if it is missing. +* `-zone "Doofnet"` - The name of the 'zone' to advertise this service as part of, this can be anything or skipped, as this is just more for visual fluff for client devices. + +Once the configuration file is done, you can start `atalkd`: + +```bash-session +# systemctl start atalkd +``` + +After a short pause, you can check `journalctl` and see the logs of the service starting up. + +```bash-session +# journalctl -u atalkd -f -n 100 + +Feb 20 22:35:39 nas-afp systemd[1]: Starting atalkd.service - Netatalk AppleTalk daemon... +Feb 20 22:35:39 nas-afp atalkd[44891]: Set syslog logging to level: LOG_DEBUG +Feb 20 22:35:39 nas-afp atalkd[44891]: restart (2.3.1) +Feb 20 22:35:40 nas-afp atalkd[44891]: zip_getnetinfo for ens192 +Feb 20 22:35:50 nas-afp atalkd[44891]: zip_getnetinfo for ens192 +Feb 20 22:36:00 nas-afp atalkd[44891]: zip_getnetinfo for ens192 +Feb 20 22:36:10 nas-afp atalkd[44891]: as_timer configured ens192 phase 2 from seed +Feb 20 22:36:10 nas-afp atalkd[44891]: ready 0/0/0 +Feb 20 22:36:10 nas-afp systemd[1]: Started atalkd.service - Netatalk AppleTalk daemon. +``` + +### `afpd` + +`afpd` handles the AppleTalk File Protocol requests, much like `smbd`. This service only needs a few configuration items to get working, and again, leaving the defaults will be absolutely fine. But, as we're trying to make things look good we'll add some extra fluff: + +**/etc/netatalk/afpd.conf** +``` +- -transall -uamlist uams_guest.so,uams_randnum.so,uams_dhx.so,uams_dhx2.so -icon -mimicmodel AirPort +``` + +* `-transall` - Use TCP and Appletalk (all transports). +* `-uamlist` - The list of 'UAM' modules to use for authentication, we've enabled Guest, Randnum, DHX, and DHX2. +* `-icon` - Use the Netatalk icon for mounted volumes. +* `-mimicmodel` - Mimic a Mac model, in this instance an AirPort (options can be found in `man afpd.conf` and `/System/Library/CoreServices/CoreTypes.bundle/Contents/Info.plist` on a macOS system). + +Again, we restart `aftpd` and watch the results + +```bash-session +# systemctl restart afpd +# journalctl -u afpd -f + +Feb 20 22:36:10 nas-afp systemd[1]: Starting afpd.service - Netatalk AFP fileserver for Macintosh clients... +Feb 20 22:36:10 nas-afp systemd[1]: Started afpd.service - Netatalk AFP fileserver for Macintosh clients. +Feb 20 22:36:16 nas-afp afpd[44900]: AFP/TCP started, advertising 10.101.2.123:548 (2.3.1) +``` + +Now, with a little luck, your new AFP server should be visible to your client devices: + +{{< gallery >}} +{{< image src="network-browser.jpg" title="Mac OS 9.2.2 'Network Browser' showing the 'nas-afp' service available via AFP over AppleTalk.">}} +{{< image src="osx-finder.png" title="Mac OS X 10.4 Finder showing the 'nas-afp' service available via AFP over TCP/IP.">}} +{{< image src="sonoma-finder.png" title="macOS Sonoma Finder showing the 'nas-afp' service available via AFP over TCP/IP.">}} +{{< /gallery >}} + +### Sharing Folders + +The final step is to get some folders shared, but first we need to approach the issue of [resource forks](https://en.wikipedia.org/wiki/Resource_fork). + +#### Resource Forks + +Apple files are not just files, due to how the original Mac OS system was designed files consist of a data and resource fork and while some files work perfectly fine with just a 'data' fork, others require the resource fork. The resource forks are stored differently on the file system and were unique to Mac OS for a while, so much so that FAT/FAT32, NFS, SMBv1 have no concept of how to handle them. Over time support has improved and NTFS and modern Linux filesystems have the concept of 'Extended Attributes' which can be used to store resource forks. + +This matters because, behind the scenes, Netatalk handles resource forks either by working with the filesystem to write 'extended attributes' or storing the resource forks and other metadata in 'AppleDouble' files. When creating shares via `afpd` you'll need to take this into account, but here is some bias suggestions: + +* Avoid NFS mounts - it's easy to just mount a folder on a VM and share out with AFP, but that presents a whole set of new problems and no extended attribute support +* Use a filesystem like XFS - It supports extended attributes and is just straight up awesome. ext2/3/4 supports them also but with size limits. +Avoid interfering with `.AppleDouble` files! Loss of this data may make other files useless. + +Now, back to the configuration: + +#### Configuring Volumes + +Configuration for the shares is held in `AppleVolumes.default`, some other files exist as well, but you can read about them from `man AppleVolumes` + +**/etc/netatalk/AppleVolumes.default** +``` +:DEFAULT: options:upriv,usedots + +/srv/transfer "Transfer" ea:sys +/srv/archive "Software Archive" ea:sys +``` + +Here we're sharing two folders, Transfer and Software Archive, both from within the `/srv` folder (know your Linux FHS people!). We only have a few options here: + +* `/srv/transfer` - The path to the folder to share +* `"Transfer"` - The name of the share to show to the client devices +* `ea:sys` - Use 'extended attributes' on the filesystem for metadata and [resource forks](https://en.wikipedia.org/wiki/Resource_fork). This can probably be set to `ea:auto` or missed out (as the default is `auto`). + +Once done, we can restart `aftpd` again: + +```bash-session +# systemctl restart afpd +``` + +{{< gallery >}} +{{< image src="network-browser-share.jpg" title="Mac OS 9.2.2 'Network Browser' showing the 'nas-afp' server and shares.">}} +{{< image src="osx-shares.png" title="Mac OS X 10.4 Finder showing the 'nas-afp' server and shares.">}} +{{< image src="sonoma-shares.png" title="macOS Sonoma Finder showing the 'nas-afp' server and shares.">}} +{{< /gallery >}} + +## Common Issues + +### Debugging Issue + +Unfortunately, before Mac OSX we had little or no tools to diagnose issues on the client side (to my understanding, I may be wrong), but with Mac OS X we have the `appletalk` command which can be used to print out node information, routing table and other useful bits of information to gain insights on how the network is operating. + +On the server side, you can use `tcpdump` to inspect AppleTalk traffic. I've found success with the following filter set: + +```bash-session +# tcpdump -i ens192 atalk or aarp or stp +``` + +### My AppleTalk devices can't see the server + +First thing to check is that your network switches, make sure that 'IGMP Snooping' isn't enabled. This setting tries to reduce the load of multicast traffic on the network, which AppleTalk is mostly multicast traffic! This setting will strip out the vast majority of AppleTalk related broadcast traffic and will cause systems to be isolated away from each other. + +In UniFi, this is under the Site configuration and the network settings. + +### My WiFi AppleTalk device can't see the server + +Do you have Unifi? Check the 'Multicast Enhancements' setting under your AP configuration, much like IGMP Snooping it is trying to do 'smart' things to reduce broadcast traffic over WiFi. This usually presents with being able to run a `tcpdump` on the server and seeing the client device announcements, but it's not responding to any broadcasts from your server. \ No newline at end of file diff --git a/content/blog/2024/creating-an-appletalk-nas/netatalk.png b/content/blog/2024/creating-an-appletalk-nas/netatalk.png new file mode 100644 index 0000000..e61c7f8 Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/netatalk.png differ diff --git a/content/blog/2024/creating-an-appletalk-nas/network-browser-share.jpg b/content/blog/2024/creating-an-appletalk-nas/network-browser-share.jpg new file mode 100644 index 0000000..2ad8abb Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/network-browser-share.jpg differ diff --git a/content/blog/2024/creating-an-appletalk-nas/network-browser.jpg b/content/blog/2024/creating-an-appletalk-nas/network-browser.jpg new file mode 100644 index 0000000..acc545e Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/network-browser.jpg differ diff --git a/content/blog/2024/creating-an-appletalk-nas/osx-finder.png b/content/blog/2024/creating-an-appletalk-nas/osx-finder.png new file mode 100644 index 0000000..c7526ad Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/osx-finder.png differ diff --git a/content/blog/2024/creating-an-appletalk-nas/osx-shares.png b/content/blog/2024/creating-an-appletalk-nas/osx-shares.png new file mode 100644 index 0000000..81404c1 Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/osx-shares.png differ diff --git a/content/blog/2024/creating-an-appletalk-nas/sonoma-finder.png b/content/blog/2024/creating-an-appletalk-nas/sonoma-finder.png new file mode 100644 index 0000000..f8c8bf9 Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/sonoma-finder.png differ diff --git a/content/blog/2024/creating-an-appletalk-nas/sonoma-shares.png b/content/blog/2024/creating-an-appletalk-nas/sonoma-shares.png new file mode 100644 index 0000000..9da506d Binary files /dev/null and b/content/blog/2024/creating-an-appletalk-nas/sonoma-shares.png differ diff --git a/themes/lhs-retro/layouts/shortcodes/gallery.html b/themes/lhs-retro/layouts/shortcodes/gallery.html new file mode 100644 index 0000000..88c822a --- /dev/null +++ b/themes/lhs-retro/layouts/shortcodes/gallery.html @@ -0,0 +1 @@ +{{ .Inner }} \ No newline at end of file diff --git a/themes/lhs/assets/js/bulma-carousel.min.js b/themes/lhs/assets/js/bulma-carousel.min.js new file mode 100644 index 0000000..5fff069 --- /dev/null +++ b/themes/lhs/assets/js/bulma-carousel.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.bulmaCarousel=e():t.bulmaCarousel=e()}("undefined"!=typeof self?self:this,function(){return function(i){var n={};function s(t){if(n[t])return n[t].exports;var e=n[t]={i:t,l:!1,exports:{}};return i[t].call(e.exports,e,e.exports,s),e.l=!0,e.exports}return s.m=i,s.c=n,s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=5)}([function(t,e,i){"use strict";i.d(e,"d",function(){return s}),i.d(e,"e",function(){return r}),i.d(e,"b",function(){return o}),i.d(e,"c",function(){return a}),i.d(e,"a",function(){return l});var n=i(2),s=function(e,t){(t=Array.isArray(t)?t:t.split(" ")).forEach(function(t){e.classList.remove(t)})},r=function(t){return t.getBoundingClientRect().width||t.offsetWidth},o=function(t){return t.getBoundingClientRect().height||t.offsetHeight},a=function(t){var e=1=t._x&&this._x<=e._x&&this._y>=t._y&&this._y<=e._y}},{key:"constrain",value:function(t,e){if(t._x>e._x||t._y>e._y)return this;var i=this._x,n=this._y;return null!==t._x&&(i=Math.max(i,t._x)),null!==e._x&&(i=Math.min(i,e._x)),null!==t._y&&(n=Math.max(n,t._y)),null!==e._y&&(n=Math.min(n,e._y)),new s(i,n)}},{key:"reposition",value:function(t){t.style.top=this._y+"px",t.style.left=this._x+"px"}},{key:"toString",value:function(){return"("+this._x+","+this._y+")"}},{key:"x",get:function(){return this._x},set:function(){var t=0this.state.length-this.slidesToShow&&!this.options.centerMode?this.state.next=this.state.index:this.state.next=this.state.index+this.slidesToScroll,this.show()}},{key:"previous",value:function(){this.options.loop||this.options.infinite||0!==this.state.index?this.state.next=this.state.index-this.slidesToScroll:this.state.next=this.state.index,this.show()}},{key:"start",value:function(){this._autoplay.start()}},{key:"pause",value:function(){this._autoplay.pause()}},{key:"stop",value:function(){this._autoplay.stop()}},{key:"show",value:function(t){var e=1this.options.slidesToShow&&(this.options.slidesToScroll=this.slidesToShow),this._breakpoint.init(),this.state.index>=this.state.length&&0!==this.state.index&&(this.state.index=this.state.index-this.slidesToScroll),this.state.length<=this.slidesToShow&&(this.state.index=0),this._ui.wrapper.appendChild(this._navigation.init().render()),this._ui.wrapper.appendChild(this._pagination.init().render()),this.options.navigationSwipe?this._swipe.bindEvents():this._swipe._bindEvents(),this._breakpoint.apply(),this._slides.forEach(function(t){return e._ui.container.appendChild(t)}),this._transitioner.init().apply(!0,this._setHeight.bind(this)),this.options.autoplay&&this._autoplay.init().start()}},{key:"destroy",value:function(){var e=this;this._unbindEvents(),this._items.forEach(function(t){e.element.appendChild(t)}),this.node.remove()}},{key:"id",get:function(){return this._id}},{key:"index",set:function(t){this._index=t},get:function(){return this._index}},{key:"length",set:function(t){this._length=t},get:function(){return this._length}},{key:"slides",get:function(){return this._slides},set:function(t){this._slides=t}},{key:"slidesToScroll",get:function(){return"translate"===this.options.effect?this._breakpoint.getSlidesToScroll():1}},{key:"slidesToShow",get:function(){return"translate"===this.options.effect?this._breakpoint.getSlidesToShow():1}},{key:"direction",get:function(){return"rtl"===this.element.dir.toLowerCase()||"rtl"===this.element.style.direction?"rtl":"ltr"}},{key:"wrapper",get:function(){return this._ui.wrapper}},{key:"wrapperWidth",get:function(){return this._wrapperWidth||0}},{key:"container",get:function(){return this._ui.container}},{key:"containerWidth",get:function(){return this._containerWidth||0}},{key:"slideWidth",get:function(){return this._slideWidth||0}},{key:"transitioner",get:function(){return this._transitioner}}],[{key:"attach",value:function(){var i=this,t=0>t/4).toString(16)})}},function(t,e,i){"use strict";var n=i(3),s=i(8),r=function(){function n(t,e){for(var i=0;i=t.slider.state.length-t.slider.slidesToShow&&!t.slider.options.loop&&!t.slider.options.infinite?t.stop():t.slider.next())},this.slider.options.autoplaySpeed))}},{key:"stop",value:function(){this._interval=clearInterval(this._interval),this.emit("stop",this)}},{key:"pause",value:function(){var t=this,e=0parseInt(e.changePoint,10)}),this._currentBreakpoint=this._getActiveBreakpoint(),this}},{key:"destroy",value:function(){this._unbindEvents()}},{key:"_bindEvents",value:function(){window.addEventListener("resize",this[s]),window.addEventListener("orientationchange",this[s])}},{key:"_unbindEvents",value:function(){window.removeEventListener("resize",this[s]),window.removeEventListener("orientationchange",this[s])}},{key:"_getActiveBreakpoint",value:function(){var t=!0,e=!1,i=void 0;try{for(var n,s=this.options.breakpoints[Symbol.iterator]();!(t=(n=s.next()).done);t=!0){var r=n.value;if(r.changePoint>=window.innerWidth)return r}}catch(t){e=!0,i=t}finally{try{!t&&s.return&&s.return()}finally{if(e)throw i}}return this._defaultBreakpoint}},{key:"getSlidesToShow",value:function(){return this._currentBreakpoint?this._currentBreakpoint.slidesToShow:this._defaultBreakpoint.slidesToShow}},{key:"getSlidesToScroll",value:function(){return this._currentBreakpoint?this._currentBreakpoint.slidesToScroll:this._defaultBreakpoint.slidesToScroll}},{key:"apply",value:function(){this.slider.state.index>=this.slider.state.length&&0!==this.slider.state.index&&(this.slider.state.index=this.slider.state.index-this._currentBreakpoint.slidesToScroll),this.slider.state.length<=this._currentBreakpoint.slidesToShow&&(this.slider.state.index=0),this.options.loop&&this.slider._loop.init().apply(),this.options.infinite&&this.slider._infinite.init().apply(),this.slider._setDimensions(),this.slider._transitioner.init().apply(!0,this.slider._setHeight.bind(this.slider)),this.slider._setClasses(),this.slider._navigation.refresh(),this.slider._pagination.refresh()}},{key:s,value:function(t){var e=this._getActiveBreakpoint();e.slidesToShow!==this._currentBreakpoint.slidesToShow&&(this._currentBreakpoint=e,this.apply())}}]),e}();e.a=r},function(t,e,i){"use strict";var n=function(){function n(t,e){for(var i=0;ithis.slider.state.length-1-this._infiniteCount;i-=1)e=i-1,t.unshift(this._cloneSlide(this.slider.slides[e],e-this.slider.state.length));for(var n=[],s=0;s=this.slider.state.length?(this.slider.state.index=this.slider.state.next=this.slider.state.next-this.slider.state.length,this.slider.transitioner.apply(!0)):this.slider.state.next<0&&(this.slider.state.index=this.slider.state.next=this.slider.state.length+this.slider.state.next,this.slider.transitioner.apply(!0)))}},{key:"_cloneSlide",value:function(t,e){var i=t.cloneNode(!0);return i.dataset.sliderIndex=e,i.dataset.cloned=!0,(i.querySelectorAll("[id]")||[]).forEach(function(t){t.setAttribute("id","")}),i}}]),e}();e.a=s},function(t,e,i){"use strict";var n=i(12),s=function(){function n(t,e){for(var i=0;ithis.slider.state.length-this.slider.slidesToShow&&Object(n.a)(this.slider._slides[this.slider.state.length-1],this.slider.wrapper)?this.slider.state.next=0:this.slider.state.next=Math.min(Math.max(this.slider.state.next,0),this.slider.state.length-this.slider.slidesToShow):this.slider.state.next=0:this.slider.state.next<=0-this.slider.slidesToScroll?this.slider.state.next=this.slider.state.length-this.slider.slidesToShow:this.slider.state.next=0)}}]),e}();e.a=r},function(t,e,i){"use strict";i.d(e,"a",function(){return n});var n=function(t,e){var i=t.getBoundingClientRect();return e=e||document.documentElement,0<=i.top&&0<=i.left&&i.bottom<=(window.innerHeight||e.clientHeight)&&i.right<=(window.innerWidth||e.clientWidth)}},function(t,e,i){"use strict";var n=i(14),s=i(1),r=function(){function n(t,e){for(var i=0;ithis.slider.slidesToShow?(this._ui.previous.classList.remove("is-hidden"),this._ui.next.classList.remove("is-hidden"),0===this.slider.state.next?(this._ui.previous.classList.add("is-hidden"),this._ui.next.classList.remove("is-hidden")):this.slider.state.next>=this.slider.state.length-this.slider.slidesToShow&&!this.slider.options.centerMode?(this._ui.previous.classList.remove("is-hidden"),this._ui.next.classList.add("is-hidden")):this.slider.state.next>=this.slider.state.length-1&&this.slider.options.centerMode&&(this._ui.previous.classList.remove("is-hidden"),this._ui.next.classList.add("is-hidden"))):(this._ui.previous.classList.add("is-hidden"),this._ui.next.classList.add("is-hidden")))}},{key:"render",value:function(){return this.node}}]),e}();e.a=o},function(t,e,i){"use strict";e.a=function(t){return'
'+t.previous+'
\n
'+t.next+"
"}},function(t,e,i){"use strict";var n=i(16),s=i(17),r=i(1),o=function(){function n(t,e){for(var i=0;ithis.slider.slidesToShow){for(var t=0;t<=this._count;t++){var e=document.createRange().createContextualFragment(Object(s.a)()).firstChild;e.dataset.index=t*this.slider.slidesToScroll,this._pages.push(e),this._ui.container.appendChild(e)}this._bindEvents()}}},{key:"onPageClick",value:function(t){this._supportsPassive||t.preventDefault(),this.slider.state.next=t.currentTarget.dataset.index,this.slider.show()}},{key:"onResize",value:function(){this._draw()}},{key:"refresh",value:function(){var e=this,t=void 0;(t=this.slider.options.infinite?Math.ceil(this.slider.state.length-1/this.slider.slidesToScroll):Math.ceil((this.slider.state.length-this.slider.slidesToShow)/this.slider.slidesToScroll))!==this._count&&(this._count=t,this._draw()),this._pages.forEach(function(t){t.classList.remove("is-active"),parseInt(t.dataset.index,10)===e.slider.state.next%e.slider.state.length&&t.classList.add("is-active")})}},{key:"render",value:function(){return this.node}}]),e}();e.a=a},function(t,e,i){"use strict";e.a=function(){return'
'}},function(t,e,i){"use strict";e.a=function(){return'
'}},function(t,e,i){"use strict";var n=i(4),s=i(1),r=function(){function n(t,e){for(var i=0;iMath.abs(this._lastTranslate.y)&&(this._supportsPassive||t.preventDefault(),t.stopPropagation())}}},{key:"onStopDrag",value:function(t){this._origin&&this._lastTranslate&&(Math.abs(this._lastTranslate.x)>.2*this.width?this._lastTranslate.x<0?this.slider.next():this.slider.previous():this.slider.show(!0)),this._origin=null,this._lastTranslate=null}}]),e}();e.a=o},function(t,e,i){"use strict";var n=i(20),s=i(21),r=function(){function n(t,e){for(var i=0;it.x?(s.x=0,this.slider.state.next=0):this.options.vertical&&Math.abs(this._position.y)>t.y&&(s.y=0,this.slider.state.next=0)),this._position.x=s.x,this._position.y=s.y,this.options.centerMode&&(this._position.x=this._position.x+this.slider.wrapperWidth/2-Object(o.e)(i)/2),"rtl"===this.slider.direction&&(this._position.x=-this._position.x,this._position.y=-this._position.y),this.slider.container.style.transform="translate3d("+this._position.x+"px, "+this._position.y+"px, 0)",n.x>t.x&&this.slider.transitioner.end()}}},{key:"onTransitionEnd",value:function(t){"translate"===this.options.effect&&(this.transitioner.isAnimating()&&t.target==this.slider.container&&this.options.infinite&&this.slider._infinite.onTransitionEnd(t),this.transitioner.end())}}]),n}();e.a=n},function(t,e,i){"use strict";e.a={initialSlide:0,slidesToScroll:1,slidesToShow:1,navigation:!0,navigationKeys:!0,navigationSwipe:!0,pagination:!0,loop:!1,infinite:!1,effect:"translate",duration:300,timing:"ease",autoplay:!1,autoplaySpeed:3e3,pauseOnHover:!0,breakpoints:[{changePoint:480,slidesToShow:1,slidesToScroll:1},{changePoint:640,slidesToShow:2,slidesToScroll:2},{changePoint:768,slidesToShow:3,slidesToScroll:3}],onReady:null,icons:{previous:'\n \n ',next:'\n \n '}}},function(t,e,i){"use strict";e.a=function(t){return'
\n
\n
'}},function(t,e,i){"use strict";e.a=function(){return'
'}}]).default}); \ No newline at end of file diff --git a/themes/lhs/assets/sass/bulma-carousel.sass b/themes/lhs/assets/sass/bulma-carousel.sass new file mode 100644 index 0000000..18ff7c4 --- /dev/null +++ b/themes/lhs/assets/sass/bulma-carousel.sass @@ -0,0 +1,145 @@ +.slider + position: relative + width: 100% + // overflow: hidden +.slider-container + display: flex + flex-wrap: nowrap + flex-direction: row + overflow: hidden + transform: translate3d(0, 0, 0) + min-height: 100% + &.is-vertical + flex-direction: column + .slider-item + flex: none + .image + &.is-covered + img + object-fit: cover + object-position: center center + height: 100% + width: 100% + // Responsive embedded objects + .video-container + height: 0 + padding-bottom: 0 + padding-top: 56.25% // ratio 16:9 + margin: 0 + position: relative + &.is-1by1, + &.is-square + padding-top: 100% + &.is-4by3 + padding-top: 75% + &.is-21by9 + padding-top: 42.857143% + & iframe, + & object, + & embed + position: absolute + top: 0 + left: 0 + width: 100% !important + height: 100% !important + + +.slider-navigation-previous, +.slider-navigation-next + display: flex + justify-content: center + align-items: center + position: absolute + width: 42px + height: 42px + background: white center center no-repeat + background-size: 20px 20px + border: 1px solid white + border-radius: 25091983px + box-shadow: 0 2px 5px #3232321a + top: 50% + margin-top: -20px + left: 0px + cursor: pointer + transition: transform .3s, opacity .3s + &:hover + transform: scale(1.2) + &.is-hidden + display: none + opacity: 0 + svg + width: 25% +.slider-navigation-next + left: auto + right: 0px + background: white center center no-repeat + background-size: 20px 20px + +.slider-pagination + display: none + justify-content: center + align-items: center + position: absolute + bottom: 0 + left: 0 + right: 0 + padding: .5rem 1rem + text-align: center + .slider-page + background: white + width: 10px + height: 10px + border-radius: 25091983px + display: inline-block + margin: 0 3px + box-shadow: 0 2px 5px #3232321a + transition: transform .3s + cursor: pointer + &.is-active, + &:hover + transform: scale(1.4) + + @media screen and (min-width: 800px) + display: flex + + +// Hero Carousel +=hero-carousel + position: absolute + top: 0 + left: 0 + bottom: 0 + right: 0 + height: auto + border: none + margin: auto + padding: 0 + z-index: 0 + +.hero + &.has-carousel + position: relative + + .hero-body, + + .hero-head, + + .hero-footer + z-index: 10 + overflow: hidden + .hero-carousel + +hero-carousel + .slider + width: 100% + max-width: 100% + overflow: hidden + height: 100% !important + max-height: 100% + z-index: 0 + .has-background + max-height: 100% + .is-background + object-fit: cover + object-position: center center + height: 100% + width: 100% + .hero-body + margin: 0 3rem + z-index: 10 \ No newline at end of file diff --git a/themes/lhs/assets/sass/chroma.scss b/themes/lhs/assets/sass/chroma.scss new file mode 100644 index 0000000..a2990af --- /dev/null +++ b/themes/lhs/assets/sass/chroma.scss @@ -0,0 +1,86 @@ +/* Background */ .bg { background-color: #f8f8f8; } +/* PreWrapper */ .chroma { background-color: #f8f8f8; } +/* Other */ .chroma .x { } +/* Error */ .chroma .err { } +/* CodeLine */ .chroma .cl { } +/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit } +/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } +/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } +/* LineHighlight */ .chroma .hl { background-color: #dfdfdf } +/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* Line */ .chroma .line { display: flex; } +/* Keyword */ .chroma .k { color: #aa22ff; font-weight: bold } +/* KeywordConstant */ .chroma .kc { color: #aa22ff; font-weight: bold } +/* KeywordDeclaration */ .chroma .kd { color: #aa22ff; font-weight: bold } +/* KeywordNamespace */ .chroma .kn { color: #aa22ff; font-weight: bold } +/* KeywordPseudo */ .chroma .kp { color: #aa22ff } +/* KeywordReserved */ .chroma .kr { color: #aa22ff; font-weight: bold } +/* KeywordType */ .chroma .kt { color: #00bb00; font-weight: bold } +/* Name */ .chroma .n { } +/* NameAttribute */ .chroma .na { color: #bb4444 } +/* NameBuiltin */ .chroma .nb { color: #aa22ff } +/* NameBuiltinPseudo */ .chroma .bp { } +/* NameClass */ .chroma .nc { color: #0000ff } +/* NameConstant */ .chroma .no { color: #880000 } +/* NameDecorator */ .chroma .nd { color: #aa22ff } +/* NameEntity */ .chroma .ni { color: #999999; font-weight: bold } +/* NameException */ .chroma .ne { color: #d2413a; font-weight: bold } +/* NameFunction */ .chroma .nf { color: #00a000 } +/* NameFunctionMagic */ .chroma .fm { } +/* NameLabel */ .chroma .nl { color: #a0a000 } +/* NameNamespace */ .chroma .nn { color: #0000ff; font-weight: bold } +/* NameOther */ .chroma .nx { } +/* NameProperty */ .chroma .py { } +/* NameTag */ .chroma .nt { color: #008000; font-weight: bold } +/* NameVariable */ .chroma .nv { color: #b8860b } +/* NameVariableClass */ .chroma .vc { } +/* NameVariableGlobal */ .chroma .vg { } +/* NameVariableInstance */ .chroma .vi { } +/* NameVariableMagic */ .chroma .vm { } +/* Literal */ .chroma .l { } +/* LiteralDate */ .chroma .ld { } +/* LiteralString */ .chroma .s { color: #bb4444 } +/* LiteralStringAffix */ .chroma .sa { color: #bb4444 } +/* LiteralStringBacktick */ .chroma .sb { color: #bb4444 } +/* LiteralStringChar */ .chroma .sc { color: #bb4444 } +/* LiteralStringDelimiter */ .chroma .dl { color: #bb4444 } +/* LiteralStringDoc */ .chroma .sd { color: #bb4444; font-style: italic } +/* LiteralStringDouble */ .chroma .s2 { color: #bb4444 } +/* LiteralStringEscape */ .chroma .se { color: #bb6622; font-weight: bold } +/* LiteralStringHeredoc */ .chroma .sh { color: #bb4444 } +/* LiteralStringInterpol */ .chroma .si { color: #bb6688; font-weight: bold } +/* LiteralStringOther */ .chroma .sx { color: #008000 } +/* LiteralStringRegex */ .chroma .sr { color: #bb6688 } +/* LiteralStringSingle */ .chroma .s1 { color: #bb4444 } +/* LiteralStringSymbol */ .chroma .ss { color: #b8860b } +/* LiteralNumber */ .chroma .m { color: #666666 } +/* LiteralNumberBin */ .chroma .mb { color: #666666 } +/* LiteralNumberFloat */ .chroma .mf { color: #666666 } +/* LiteralNumberHex */ .chroma .mh { color: #666666 } +/* LiteralNumberInteger */ .chroma .mi { color: #666666 } +/* LiteralNumberIntegerLong */ .chroma .il { color: #666666 } +/* LiteralNumberOct */ .chroma .mo { color: #666666 } +/* Operator */ .chroma .o { color: #666666 } +/* OperatorWord */ .chroma .ow { color: #aa22ff; font-weight: bold } +/* Punctuation */ .chroma .p { } +/* Comment */ .chroma .c { color: #008800; font-style: italic } +/* CommentHashbang */ .chroma .ch { color: #008800; font-style: italic } +/* CommentMultiline */ .chroma .cm { color: #008800; font-style: italic } +/* CommentSingle */ .chroma .c1 { color: #008800; font-style: italic } +/* CommentSpecial */ .chroma .cs { color: #008800; font-weight: bold } +/* CommentPreproc */ .chroma .cp { color: #008800 } +/* CommentPreprocFile */ .chroma .cpf { color: #008800 } +/* Generic */ .chroma .g { } +/* GenericDeleted */ .chroma .gd { color: #a00000 } +/* GenericEmph */ .chroma .ge { font-style: italic } +/* GenericError */ .chroma .gr { color: #ff0000 } +/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold } +/* GenericInserted */ .chroma .gi { color: #00a000 } +/* GenericOutput */ .chroma .go { color: #888888 } +/* GenericPrompt */ .chroma .gp { color: #000080; font-weight: bold } +/* GenericStrong */ .chroma .gs { font-weight: bold } +/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold } +/* GenericTraceback */ .chroma .gt { color: #0044dd } +/* GenericUnderline */ .chroma .gl { text-decoration: underline } +/* TextWhitespace */ .chroma .w { color: #bbbbbb } \ No newline at end of file diff --git a/themes/lhs/assets/sass/leighhack.scss b/themes/lhs/assets/sass/leighhack.scss index d9b77d4..b970fad 100644 --- a/themes/lhs/assets/sass/leighhack.scss +++ b/themes/lhs/assets/sass/leighhack.scss @@ -26,6 +26,10 @@ $body-font-size: 1.25em; // Import Bulmas styles @import "bulma.sass"; +@import "chroma.scss"; + +// Add Carousel add-on +@import "bulma-carousel.sass"; // ------------------------------------------------------- diff --git a/themes/lhs/layouts/partials/foot.html b/themes/lhs/layouts/partials/foot.html index 66bc30f..ef55576 100644 --- a/themes/lhs/layouts/partials/foot.html +++ b/themes/lhs/layouts/partials/foot.html @@ -2,10 +2,19 @@ {{ if .Page.Store.Get "hasMermaid" }} - + +{{ end }} + +{{ if .Page.Store.Get "hasCarousel" }} +{{ with resources.Get "js/bulma-carousel.min.js" }} + +{{ end }} + {{ end }} {{ $theme := resources.Get "js/theme.js" }} diff --git a/themes/lhs/layouts/shortcodes/gallery.html b/themes/lhs/layouts/shortcodes/gallery.html new file mode 100644 index 0000000..8f991bc --- /dev/null +++ b/themes/lhs/layouts/shortcodes/gallery.html @@ -0,0 +1,7 @@ + + + + +{{ .Page.Store.Set "hasCarousel" true }} \ No newline at end of file diff --git a/themes/lhs/layouts/shortcodes/image.html b/themes/lhs/layouts/shortcodes/image.html index 5eb2a2c..3270528 100644 --- a/themes/lhs/layouts/shortcodes/image.html +++ b/themes/lhs/layouts/shortcodes/image.html @@ -3,6 +3,9 @@ {{ $img = $img.Resize (print (default "1264x" $width) " webp") }} {{ $title := .Get "title" }} {{ $class := .Get "class" }} +{{- with .Parent -}} +
+{{ end }}
{{ $title }} @@ -13,4 +16,7 @@ {{ end }} -
\ No newline at end of file + +{{- with .Parent -}} +
+{{ end }} \ No newline at end of file