Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 09/15/2019 in all areas

  1. Nginx' performance advantages over Apache were built on three factors: modern-day multiprocessing in the server, a lot less overhead due to reduced functionality and memory caching. Over the last five years, Apache has greatly reduced that gap by adapting Nginx' multiprocessing approach (one keyword there is the event MPM module), so Apache isn't spending most of its time spinning up and tearing down whole server instances anymore. File system access has greatly improved with solid state disks, too. Apache still has a lot more functionality, and its distributed config file approach, most prominently the ability to make configuration changes with a .htaccess file inside the web directories, hurts performance. Its dynamic module loading approach and the dozens of pre-installed modules most distributions ship also take up processing time and memory. Nowadays, Apache can be stripped down a lot and compiled to be head to head with Nginx, though few actually care to do that, since it also means removing functionality one might need in the future. A stock Apache is usually still quite a bit slower and reaches its limits faster (about the factor 2). This becomes an issue under heavy load or on slow machines. Where Nginx still shines brightly is load balancing. Apache can do it too, but with Nginx it is straight forward and well documented, having been there for a long time. For those interested in a bit of (highly subjective) history: for a long time (speak eighties and nineties), the classic forking mechanism that was common on *nix OSes was the way to do multiprocessing in network servers, and therefore in Apache too. This meant spawning a full copy of the server process and initializing it, then tearing it down when the request was done. Apache brought a small revolution to that approach by implementing preforking, meaning to keep spare server instances around to fulfill requests with little delay. After a while, there were other approaches too when faster multiprocessing approaches become part of common operating systems, like multi threading, which is supported by Apache's "worker" multiprocessing module (MPM). There were, however, big caveats with using other MPMs. Since file systems used to be slow, sometimes awfully so, in the old days, and since the classic CGI approach of starting an executable from the file system, supplying it with information through environment variables and standard input and capturing its standard output was a security nightmare - even without thinking about shared hosting - nifty programmers included full languages interpreters inside Apache modules. mod_perl and mod_php became the big thing, the latter coming to dominate the web after a few years. These interpreters, though, often had memory leaks and issues with thread isolation, meaning at best that an error in one thread tore down numerous other sessions and at worst that the server had a propensity for information leaks, remote code execution and privilege escalation attacks, the former security nightmare squared. Thus, these tightly integrated interpreters more or less locked their users into the classic prefork approach where every instance is its own, basically independent process. With PHP as the market leader not evolving in that regard, things were frozen for quite some time. This was when Nginx conquered the market, first by serving static HTML and associated resources with lightning speed (CMSes generating static HTML were still a big thing for a while), but soon by taking care of all the static stuff while handling the dynamic things off to Apache and caching parts of its responses in memory. Finally, though, PHP finally got a fresh boost and grew stable enough for its engine to re-use interpreter instances. It was easier to contain things inside an interpreter-only process instead of dealing with all the server peculiarities, so FastCGI daemons finally became stable, known and used, and suddenly the need to have the language interpreter contained in the web server fell away. Apache got leaner and Nginx more flexible. Caching servers like Varnish became popular since it suddenly was relatively easy to build a fast, nice, layered caching solution with a combination of Nginx, Varnish and a full fledged web server like Apache or IIS, able to serve thousands of highly dynamic and media rich pages per minute. About that time, SSL grew in importance too, and hosting providers learned to love Nginx as a means to route domains to changing backends and provide fast and easily configurable SSL endpoint termination. Over the last years, Nginx got other features like generic TCP protocol load balancing that offset it from other servers and make it more into a one-stop solution for modern web applications. It does boost its popularity that Nginx is often the first (or the first major) web server to ship evolving technologies, making the front pages and pulling in early adopters, http/2 being one of the most prominent examples there.
    10 points
  2. In the last few weeks... or almost months... I worked on a project for a restaurant. Sad to say that the company behind the restaurant went out of business before the project could be finished - or was paid or the website ever saw the light of day. As there is kind of a lot of work in it... but yet a lot of customization... I decided to create a site profile of it and make it public as ProcessWire Barebone Site Profile. Right now I'm stripping out every client detail and finish some functions and details that were planned a few weeks back so you can build right on top of it, if you like or want. Maybe it's only a playground for someone or an inspiration for how to f**k up data-mangement... but hey... better than a ZIP file on a backup drive. ? As the project was and is super tailor-made to that client, it may not work for a lot of restaurants out there, but nonetheless I don't want miss the opportunity to offer a foundation for upcoming restaurant projects you may face. The project had a huge focus on dishes, beer and wine variations so those templates are kind of feature-rich. You might want to rip out some of it as most restaurants don't focus that much on those details. Important details I want to be honest and clear about: this profile does NOT include a highly polished and usable frontend as the design is paid and therefore can't be made public the frontend will be a collection of re-usable snippets to show every possible detail of each page, item and whatever the whole site profile will be without any support - future updates aren't planned right now the site profile will be released in single-language setup only (english) existing translations (as seen in parts of the screenshots) will be changed to english existing data, for example dishes, will be replaced with demo content if you, one of your clients or anyone else wants to use this profile - feel free to do so Pro Modules were already stripped out of it only public modules were used and are included with the profile itself the full module list will soon be published on Github the full README will soon be published on Github the full LICENSE will soon be published on Github So... yes... that's just a short preview of an upcoming site profile. As mentioned before, the site profile will most likely never ever receive any future updates, so it will be available on Github only. It's not planned to publish this as a full featured site profile, yet. More details, screenshots and the site profile itself (of course) will be available soon. Questions? Please feel free to ask. Github Repository: https://github.com/webmanufaktur/pwbrestaurant
    3 points
  3. @teppo's argument here is solid and I thought... cool... just add some details to those links with CSS-magic: a[target=_blank]:after { content: " (opens in a new window)"; } BUT... not all screenreaders support that feature... all I found was kind of a mixed result. https://www.powermapper.com/tests/screen-readers/content/css-generated-content/ Just in case for those who had a similar idea. ?
    2 points
  4. Hey folks! I've been a bit quiet here about Wireframe, but the thing is that I've finally got a couple of "real sites" (as in not my own projects) to implement it on, and that has brought up some new requirements and a few issues. I haven't had the time to dig into a whole lot of really new stuff (components and such), but I have made a lot of other updates to the framework. As such, Wireframe 0.6 was released late yesterday. (A release on Friday the 13th, consisting of a total of 13 commits – not being superstitious here.) I've been updating the docs at wireframe-framework.com as well, but some parts remain a bit outdated. A lot of what happened in this version doesn't really affect the use of Wireframe, but some things do – and one change may even break some existing implementations: It used to be possible to set the view file or layout from Controller with $this->view->layout = "some-layout" or $this->view->view = "json". This has been deprecated and removed in favour of a setter/getter API: $this->view->setLayout("some-layout") and $this->view->setView("json"), and getLayout() / getView() for reading the value. In addition to some obvious benefits from type hinting etc. this also makes it harder to accidentally change something when you thought you were just passing a variable to the View ? There are now three ways to perform actions and pass data from a Controller to the View: Controller::init() (triggered as soon as the Controller class is loaded), public methods (which are made accessible in the View as $this->method_name), and a new addition Controller::render() (called right before the page is rendered). In most cases where one might've used the init() method before, render() is a better choice, as there's less chance that code will get executed "unnecessarily". In addition to existing Page::layout() and Page::view() methods there are now specific setters/getters: Page::setLayout("layout-name"), Page::getLayout(), etc. Original "unified setter+getter" methods may actually get removed at some point, as they're often ambiguous and make code arguably less readable. Quite a few other changes as well, some of which improve performance and others that make the API more polished. Some bug fixes too, though those mostly apply to what I'd consider "border cases" ? Here's the full changelog for Wireframe 0.6.0: ### Added - New Page methods Page::getLayout(), Page::setLayout(), Page::getView(), and Page::setView(). - New Controller::render() method, executed right before a page is actually rendered. - New ViewData class for storing (internal) data required by the View class. - New getter/setter methods for ViewData properties for the View class. - New method Wireframe::getConfig() for getting current config settings. - New method ViewPlaceholders::has() for checking if a placeholder has already been populated. ### Changed - Various View-related features moved from Wireframe module and ViewPlaceholders class to the View class. - Removed access to local get* and set* methods via the PHP's magic setter method __set() and getter method __get() in the View class. - Redirect feature no longer fails if provided with a WireArray data type; in these cases the first item is used as the redirect target. - Improvements to PHPDoc comments. ### Fixed - An issue with Config class where the "all directories exist" message was sometimes displayed unintentionally. - An issue where View Placeholder values might've been overwritten because existence of earlier value was checked inproperly. - An issue where empty / null view file would be automatically replaced with value "default". On a related note, for this version I decided to give sonarcloud a try. For those who don't know it, it's a sort of a code quality inspector, and it's free for public projects. It didn't have a whole lot to complain about at this point, but it's good to have some sort of validation in place just in case. I will also be digging deeper into reported "code smells", as some of them definitely make sense to me ?
    2 points
  5. In this tutorial I want to write about handling special cases and change requests by clients gracefully without introducing code bloat or degrading code quality and maintainability. I'll use a site's navigation menu as an example, as it's relatable and pretty much every site has one. I'll give some examples of real situations and change requests I encountered during projects, and describe multiple approaches to handling them. However, this post is also about the general mindset I find useful for ProcessWire development, which is more about how to handle special cases and still keep your code clean by making the special case a normal one. The problem: Special cases everywhere Since ProcessWire has a hierarchical page tree by default, as a developer you'll usually write a function or loop that iterates over all children of the homepage and displays a list of titles with links. If the site is a bit more complex, maybe you additionally loop over all grandchildren and display those in drop-down menus as well, or you even use a recursive function to iterate over an arbitrary amount of nested child pages. Something like this: function buildRecursiveMenu(Page $root): string { $markup = ['<ul class="navigation">']; foreach ($root->children() as $child) { $link = '<a class="navigation__link" href="' . $child->url() . '">' . $child->title . '</a>'; $children = $child->hasChildren() ? buildRecursiveMenu($child) : ''; $markup[] = "<li class="navigation__item">{$link}{$children}</li>"; } $markup[] = '</ul>'; return implode(PHP_EOL, $markup); } But then the requests for special cases come rolling in. For example, those are some of the requests I've gotten from clients on my projects (by the way, I'm not saying the clients were wrong or unreasonable in any of those cases - it's simply something I needed to handle in a sensible way): The homepage has the company's name as it's title, but the menu link in the navigation should just say "Home". The first page in a drop-down menu should be the top-level page containing the drop-down menu. This was requested because the first click on the top-level item opens the sub-navigation instead of navigating to that page (espcially on touch devices, such as iPads, where you don't have a hover state!), so some visitors might not realize it's a page itself. Some top-level pages should be displayed in a drop-down menu of another top-level page, but the position in the page tree can't change because of the template family settings. The menu needs to contain some special links to external URLs. For one especially long drop-down menu, the items should be sorted into categories with subheadings based on a taxonomy field. In general, my solutions to those requests fall into three categories, which I'll try to elaborate on, including their respective benefits and downsides: Checking for the special case / condition in the code and changing the output accordingly (usually with hard-coded values). Separating the navigation menu from the page tree completely and building a custom solution. Utilizing the Content Management Framework by adding fields, templates and pages that represent special states or settings. Handling it in the code This is the simplest solution, and often the first thing that comes to mind. For example, the first request (listing the homepage as "Home" instead of it's title in the navigation) can be solved by simply checking the template or ID of the current page inside the menu builder function, and changing the output accordingly: // ... $title = $child->template->name === 'home' ? 'Home' : $child->title; $link = '<a class="navigation__link" href="' . $child->url() . '">' . $title . '</a>'; // ... This is definitely the fastest solution. However, there are multiple downsides. Most notably, it's harder to maintain, as each of those special cases increases the complexity of the menu builder function, and makes it harder to change. As you add more special conditions, it becomes exponentially harder to keep changing it. This is the breeding ground for bugs. And it's much harder to read, so it takes longer for another developer to pick up where you left (or, as is often cited, for yourself in six months). Also, now we have a hard-coded value inside the template, that only someone with access to and knowledge of the template files can change. If the client want's the link to say "Homepage" instead of "Home" at some point, they won't be able to change it without the developer. Also, each special case that is hidden in the code makes it harder for the client to understand what's going on in terms of template logic - thus increasing your workload in editorial support. That said, there are definitely some times where I would go with this approach. Specifically: For smaller projects that you know won't need to scale or be maintained long-term. If you are the only developer, and/or only developers will edit the site, with no "non-technical" folk involved. For rapid prototyping ("We'll change it later") Building a custom solution My initial assumption was that the main navigation is generated based on the page tree inside ProcessWire. But of course this isn't set in stone. You can just as easily forgo using the page tree hierarchy at all, and instead build a custom menu system. For example, you could add a nested repeater where you can add pages or links on a general settings page, and generate the menu based on that. There are also modules for this approach, such as the Menu Builder by @kongondo. This approach is not the quickest, but gives the most power to the editors of your site. They have full control over which pages to show and where. However, with great power comes great responsibility, as now each change to the menu must be performed manually. For example, when a new page is added, it won't be visible in the menu automatically. This is very likely to create a disconnect between the page tree and the menu (which may be what you want, after all). You may get ghost pages that are not accessible from the homepage at all, or the client may forgot to unpublish pages they don't want to have any more after they've removed them from the menu. I would only go with this approach if there are so many special cases that there hardly is a "normal case". However, even then it might not be the best solution. The direct relationship between the page tree, the menu structure and page paths are one of the strongest features of ProcessWire in my opinion. If many pages need to be placed in special locations without much structure in regards to what templates go where, maybe you only need to loosen up the template family settings. I have built one site without any template family restrictions at all - any page of any template can go anywhere. It's definitely a different mindset, but in this case it worked well, because it allowed the client to build custom sections with different page types grouped together. It's a trade-off, as it is so often, between flexibility and workload. Weigh those options carefully before you choose this solution! Utilizing the CMF This is the middle ground between the two options above. Instead of building a completely custom solution, you keep with the basic idea of generating a hierarchical menu based on the page tree, but add fields and templates that allow the editor to adjust how and where individual pages are displayed, or to add custom content to the menu. of course, you will still write some additional code, but instead of having hard-coded values or conditions in the template, you expose those to the client, thereby making the special case one of the normal cases. The resulting code is often more resilient to changing requirements, as it can not one handle that specific case that the client requested, but also every future change request of the same type. The key is to add fields that enable the client to overwrite the default behaviour, while still having sensible defaults that don't require special attention from the editor in most cases. I'll give some more examples for this one, as I think it's usually the best option. Example 1: Menu display options This is probably the first thing you thought of for the very first change request I mentioned (displaying the homepage with a different title). Instead of hard-coding the title "Home" in the template, you add a field menu_title that will overwrite the normal title, if set. This is definitely cleaner than the hard-coded value, since it allows the client to overwrite the title of any page in the menu. I'll only say this much in terms of downsides: Maybe the menu title isn't really what the client wanted - instead, perhaps they feel limited because the title is also displayed as the headline (h1) of the page. In this case, the sensible solution would be an additional headline field that will overwrite the h1, instead of the menu_title field. Which fields are really needed is an important consideration, because you don't want to end up with too many. If each page has fields for the title, a headline, a menu title and an SEO-title, it's much more complicated than it needs to be, and you will have a hard time explaining to the client what each field is used for. Another example in this category would be an option to "Hide this page in the menu". This could be accomplished by hiding the page using the inbuilt "hidden" status as well, but if it's hidden it won't show up in other listings as well, so separating the menu display from the hidden status might be a good idea if your site has lots of page listings. Example 2: "Menu link" template One solution that is quite flexible in allowing for custom links to pages or external URLs is creating a menu-link template that can be placed anywhere in the page tree. This templates can have fields for the menu title, target page and/or external target URL. This way, you can link to another top-level page or an external service inside a drop-down menu, by placing a Menu Link page at the appropriate position. This is also a clean solution, because the navigation menu will still reflect the page tree, making the custom links visible and easily editable by the editors. A minor downside is that those templates are non-semantical in the sense that they aren't pages with content of their own. You'll need to make sure not to display them in listings or in other places, as they aren't viewable. It may also require loosening up strict family rules - for example, allowing for Menu Link pages to be placed below the news index page, which normally can only hold news pages. Example 3: Drop-down menu override This one is a more radical solution to override drop-down menus. You add a repeater field to top-level pages, similar to the one mentioned as a custom solution, where you can add multiple links to internal pages or URLs. If the repeater is empty, the drop-down menu is generated normally, based on the sub-pages in the page tree. But if the repeater contains some links, it completely overrides the drop-down menu. It's similar to the fully custom solution in that as soon as you override a sub-menu for a top-level page, you have to manually manage it in case the page structure changes. But you can make that decision for each top-level page individually, so you can leave some of them as is and only have to worry about the ones that you have overwritten. Again, this offers sensible defaults with good customizability. A downside is that the mixed approach may confuse the client, if some changes to the page tree are reflected in the drop-down menu directly, while others don't seem to have any effect (especially if you have multiple editors working on a site). Finding the right solution So how do you choose between the approaches? It depends on the client, the requirements, and on what special cases you expect and want to handle. Sometimes, a special request can be turned down by explaining how it would complicate editorial workflows or have a negative impact on SEO (for example, if you risk having some pages not accessible from the homepage at all). Also, make sure you understand the actual reason behind a change request, instead of just blindly implementing the suggestion by the client. Often, clients will suggest solutions without telling you what the actual problem is they're trying to solve. For example: In one case, I implemented the drop-down override mentioned in example three. However, what the client really wanted was to have the top-level page as the first item in the drop-down menu (see the example requests mentioned above). So they ended up overwriting every single drop-down menu, making the menu harder to maintain. In this case, it would have been better to go with a more specialized solution, such as adding a checkbox option, or even handling it in the code, since it would have been consistent throughout the menu. Another example was mentioned above: If the client requests an additional "Menu title" field, maybe what they really need is a "Headline" field. I recommend reading Articulating Design Decisions by Tom Greever; it includes some chapters on listening to the client, finding out the real reason behind a change request, and responding appropriately. It's written from a design perspective, but is applicable to development as well, and since UX becomes more important by the day, the lines between the disciplines are blurred anyway. Conclusion I realize now this reads more like a podcast (or worse, a rant) than an actual tutorial, but hopefully I got my point across. ProcessWire is at is greatest if you utilize it as a Content Management Framework, creating options and interfaces that allow for customizability while retaining usability for the client / editor. I usually try to hit a sweet spot where the editors have maximum control over the relevant aspects of their site, while requiring minimal work on their part by providing sensible defaults. Above, I listed some examples of requests I've gotten and different solutions I came up with to handle those with custom fields or templates. Though in some cases the requirements call for a custom solution or a quick hack in the template code as well! What are some of the special requests you got? How did you solve them? I'd love to get some insights and examples from you. Thanks for reading!
    1 point
  6. Sure... but my composer knowledge is quite limited. All I know is how to install something with it. ? I guess @teppo has more knowledge here and maybe there is a solution or something.
    1 point
  7. These columns are sorted in 1.5.56. However, instead of text, I've gone with varchar(512), which should be more than long enough. :)
    1 point
  8. So true. It's a nice idea, but CSS content is indeed problematic. Sometimes you want to add otherwise meaningless (visual) content (like icons) with CSS content and it turns out that some screen readers read them out loud, which can be quite confusing. There's no aria-hidden for CSS content, so reliably hiding said content from screen readers can be a real hassle. On the other hand there are times when you actually want to provide content for screen readers with this technique – yet it turns out that some of them will happily disregard it. In my experience it's almost never a good idea to add content with CSS ?
    1 point
  9. Hey @teppo – no problem at all ?Probably a very good idea; happy for you to submit a merge request. Thanks!
    1 point
  10. No blog post this week because I don’t have anything new or interesting to write about just yet (unless you like to hear about repairing fridges and clothes dryers). But I’ve been focused primarily on completing the FormBuilder updates that I mentioned in last week’s blog post, and it’s looking very good, though I’ve still got a little further to go with it. When it comes to multi-page forms, I’m trying to cover all of the exceptional cases where sessions expire, cookies clear, etc., and want to make sure a form-in-progress continues to work through all these situations. Multi-page forms can be potentially long and people can invest lots of time in them (relative to regular forms), so trying to make it all as resilient as possible. This takes lots of time developing and testing, so that’s what I’ve been doing. I’ll be doing some of that next week too, though also have been planning to dig back into the core issues repo and start working through some of those in preparation for a new master version this Fall. Hope you all have a great weekend!
    1 point
  11. @ryan maybe garlic.js is of some help...
    1 point
  12. @pwired I'm not sure if you really know what or about what you are talking? But I definetly know that I really dislike such sort of posts. Please revert back to kindly posts only. And, if you want to answer me to this post, please do it via PM to me. (I'm into weekend now and only can answer on monday next week, but will definetly do.)
    1 point
  13. I may have missed some of the points made here (sorry in advance), but regarding links opening in new tab/window: please keep in mind that this is a problem from accessibility point of view. For typical desktop users with decent eye sight etc. this is usually not a problem – and in fact it may be nice in some cases that a link automatically opens in a new tab – but for most screen readers (apps, that is – and thus their users as well) it's a real problem. From what I've heard/read, it can entirely mess up their understanding of the context. WCAG advises that if you're about to open a link in a new window, you should provide a clear signal that this is going to happen. This can, for an example, be a note at the end of the link text: "(opens in a new window)". If you care about accessibility, think twice before you use target="_blank". And for those cases where the client requests this, be sure to inform them that it will likely cause issues for some users. If the client still wants to go with it then by all means do it, but I've found this argument to resonate quite well with most sensible people ?
    1 point
  14. Hallo everyone ? I have just release AdminThemeBoss 0.6.0: Introducing, a refined, more colorfull and streamlined expirience: Added: more colors ? Added: improved notifications ? Added: improved dropdows ⬇️ Added: improved integration with third party modules like ProDrafts, AOS and ASM… ? Under the hood improvements on how settings are saved and applied Fixed: Some minor bugs ??
    1 point
  15. I wonder how all these years I never heard of John Frusciante's solo records... until a recent trip to Croatia, where the best cook in the village played some of his stuff... one of the most laid-back places I've ever been to at the Adriatic coast... And then there's this gem I discovered just 2 days ago:
    1 point
  16. I also use different ways for implementing menus, depending on what I find appropriate. Here is a repeater based, one level customizable menu, where hiding fields on conditions makes it possible to switch between "custom" and page picker options:
    1 point
  17. Here's a basic example of how you could save files to a PW page via a front-end page using tus-php and Uppy. 1. Install tus-php via Composer. 2. Create a PW template that will provide the tus-php server endpoint - in this example the template is named "uppy". In the template Files tab, disable any automatically appended template file. In the template URLs tab, allow URL segments. If using Tracy Debugger, disable the debug bar in the front-end for this template because we don't want any output from Tracy being included in the response. The contents of the uppy.php template file: <?php namespace ProcessWire; // Create PW temp directory $td = $files->tempDir('uppy'); $td_path = (string) $td; // Create TusPhp server $server = new \TusPhp\Tus\Server(); // Set path to endpoint - no trailing slash here $server->setApiPath('/uppy'); // Set upload directory $server->setUploadDir($td_path); // Listener function for when an upload is completed $server->event()->addListener('tus-server.upload.complete', function(\TusPhp\Events\TusEvent $event) { // Get path of uploaded file $file_path = $event->getFile()->getFilePath(); // Add uploaded file to "files" field on Home page $p = wire('pages')->get(1); $p->of(false); $p->files->add($file_path); $p->save('files'); }); // Send response $response = $server->serve(); $response->send(); // Exit from current PHP process // Could probably use PW halt here as an alternative // return $this->halt(); exit(0); 3. Create a page using the template - in this example the page is at url http://1testing.oo/uppy/ 4. Add the Uppy assets, JS and markup to the template of the front-end page that you will upload files from. Markup Regions are used in this example. <pw-region id="scripts"> <script src="https://transloadit.edgly.net/releases/uppy/v1.4.0/uppy.min.js"></script> <script> const uppy = Uppy.Core({debug: true, autoProceed: false}) .use(Uppy.Dashboard, {target: '#uppy-box', inline: true}) .use(Uppy.Tus, {endpoint: 'http://1testing.oo/uppy/', limit:10}); </script> </pw-region><!--#scripts--> <pw-region id="stylesheets"> <link href="https://transloadit.edgly.net/releases/uppy/v1.4.0/uppy.min.css" rel="stylesheet"> </pw-region><!--#stylesheets--> <div id="body"> <div id="uppy-box"></div> </div><!--#body--> 5. Upload files via the front-end and see that they are added to the "files" field on the Home page.
    1 point
  18. A few months back I gave Linux another chance. I was looking for something that could improve my workflow in some kind or the other and yes... I'm still on Linux. Tried a lot of distros and windows managers since then - right now I'm super happy with Manjaro and the i3 (i3-gaps) window manager. Works pretty pretty well so far.
    1 point
×
×
  • Create New...