Using a fully Office 365 stack - my thoughts

I work at a highschool in Aptearoa that is considered a “Microsoft school.” Depending on the type of infrastructure that the respective school decides to use it will be dubbed as “Microsoft school”, “Google school”, “Schoology school” and so on. Having avoided Microsoft products for the past few years, going back to using them for work has been an interesting experience.

I think that Microsoft has come a long way over the past few years since Satya Nadella became CEO. It’s clear that their new focus is on the cloud and cloud product offerings, and Office 365 is no exception. I really appreciate some of the accessibility features built in by default with tools like OneNote having a dictation mode, or being able to navigate something like Teams entirely via the keyboard. For teaching students who are all unique and different, these types of design choices make it much easier for someone like me to implement UDL into my teaching practice.

There are a few rough spots though. OneNote frequently likes to crash, and has a very weird way of rendering text where the top half of letters get cut off by the sentence above. Teams can be buggy as well, with window resizing sometimes causing the app to freeze up completely as well as files occasionally refusing to sync with my filesystem on OneDrive. These bugs don’t happen often and are still miles better than some of the monstrous legacy software tools used by some schools in Aotearoa, but there’s definitely room for improvement.

Still, I would always advocate for a stack that is libre, open and transparent by default over something proprietary. If I had the choice, I would use and recommend software such as Standard Notes for note-taking, Matrix for communication and Syncthing for cross-system file synchronisation. Keep the user in control of their data!

Stay safe,
T.

Bits and pieces

Quite a bit had happened in the last few weeks but I haven’t had the time to properly reflect on it all. Hopefully I can do this soon, but in the meantime there are a few things on the horizon:

  • Merveilles is currently thinking about creating another jam where everyone puts together a drawing/graphic for a zine using PostScript

  • starting to focus more on systems-level programming, especially with languages like C and Go to create my own little programs

  • developing a greater understanding and practice of UDL with how I approach design

Looking forward to processing this all once everything settles down. Until then, I’ll be spinning in the air like an omelette

T.

It's time to reflect and rebuild

Construction skyline

I only recently got the chance to read Marc Andreesen’s widely discussed post titled “It’s time to build”. In the post, Andreesen describes how due to a failure of action people around the world were totally caught off-guard by the Covid-19 pandemic. The tact of the post is to say that old methods of doing things is holding back progress, and this is an issue that has been prolonged by both sides of the political spectrum - the right not aggressively investing in new industries and technologies (rather sticking to what they know), and the left holding a bias to “protect” public institutions (rather than prove that they are superior to their private counterparts).

I sympathise with the general message Andreesen is addressing here. While I don’t necessarily agree with all his points regarding the public sector, especially with regards to healthcare, other aspects I can favour more and work with.

For example, the public transport system here in Auckland is an absolute mess. The decisions made by public bodies is often questionable at best and downright laughable at worst (an example being taking over 20 years to build a railway from the airport to the CBD). Housing has also been a great issue, with prices skyrocketing due to limited supply and speculation, not to mention a lack of a capital gains tax preventing those with massive properties portfolios to use houses solely for investment purposes.

As a citizen it is frustrating to live with all of this. I have voted for politicians based on their policies regarding housing and then been totally left behind when it came to deliver. Half of the MPs who supposedly represent me have their own multi-million dollar property portfolios so implementing a CGT would hurt their bottom line too much.

In answering Andreesen’s call to build, I say we should take some time to reflect first. Let’s think about the mistakes we have made before steamrolling ahead with building new things. Maybe it isn’t even a matter of building new things at all, but rather fixing what we already have and making that better.

Don’t drown in doubt,

T.

Use VS Code anywhere

I am a big fan of VS Code. When I am working on a project I normally work between Micro and VSC, but have found VSC to be fantastic for anything web related. Despite it being built on top of Electron (which has a stereotype of being bloated and slow), Microsoft has clearly done a lot of work in optimising the speed and performance of this editor. In my opinion it has completely superceeded its predecessors like Atom and Brackets, both of which are also built using Electron.

Last night a stumbled upon code-server, a program that allows one to run VSC on essentially any device with a web browser. VSC is rendered and run on your own server, allowing it to do all of the heavy lifting (test, compiling of code etc) and giving you access to a full IDE from any device (even a phone!). This would have many benefits for developers, including saving battery power on their portable machines if they are on-the-go, using public computers to access work remotely and unifying all dev work into a centralised environment.

Development on a phone using this method would still be a bit cumbersome, as typing on a touchscreen would be much harder compared to a physical keyboard for longer dev sessions. However, on a device like an iPad which has more screen realestate and supports Bluetooth keyboards it would make for a nifty portable dev environment!

VS Code running inside macOS Safari

I am going to try testing out deploying my own code-server instance and will update this post after giving it a go!

(UPDATE 9/8/2020) - After having used code-server for a few weeks now, I think this would be excellent for any developing working from machine to machine. I personally don’t really need this as I normally am just using my own machine, but I’m still glad this exists!

T.

Notion, supercharged

I spent the day today creating a database of designers from around the world on Notion, a personal wiki/info management software.

I’ve used Notion before for managing notes and files, but found it was a bit cumbersome when trying to share content or pages. While it was possible, there was no way to map custom memorable domains to pages (instead it would be default Notion links with a large generated string as the URL). If I wanted to share something with someone I would have to send them the link, which would then need to be pinned or bookmarked if used as a reference.

Enter the magic of Cloudflare Workers. With a bit of Javascript that is deployed serverlessly, I am able to map Notion pages to any domains I own! No more having to juggle long URLs, people just need one simple link.

Cloudflare does all of the heavy lifting. Essentially, their servers read and scrape any content that you have published publically on Notion. It then reformats this as HTML and publishes it behind a domain of your choosing! Since all traffic goed through Notion and Cloudflare, everything is encrypted and secured at every level.

Alter the JS below to add your own domain or subdomain, then paste the script in Cloudflare and deploy!


const MY_DOMAIN = 'example.com';

/*
* Enter your Notion URL slug to page ID mapping
*/
const SLUG_TO_PAGE = {
  '': '771ef38657244c27b9389734a9cbff44',
};

const PAGE_TITLE = '';
const PAGE_DESCRIPTION = '';

const GOOGLE_FONT = '';

const PAGE_TO_SLUG = {};
const slugs = [];
const pages = [];
Object.keys(SLUG_TO_PAGE).forEach(slug => {
  const page = SLUG_TO_PAGE[slug];
  slugs.push(slug);
  pages.push(page);
  PAGE_TO_SLUG[page] = slug;
});

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request));
});

function generateSitemap() {
  let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
  slugs.forEach(
    (slug) =>
      (sitemap +=
        '<url><loc>https://' + MY_DOMAIN + '/' + slug + '</loc></url>')
  );
  sitemap += '</urlset>';
  return sitemap;
}

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type',
};

function handleOptions(request) {
  if (request.headers.get('Origin') !== null &&
    request.headers.get('Access-Control-Request-Method') !== null &&
    request.headers.get('Access-Control-Request-Headers') !== null) {
    // Handle CORS pre-flight request.
    return new Response(null, {
      headers: corsHeaders
    });
  } else {
    // Handle standard OPTIONS request.
    return new Response(null, {
      headers: {
        'Allow': 'GET, HEAD, POST, PUT, OPTIONS',
      }
    });
  }
}

async function fetchAndApply(request) {
  if (request.method === 'OPTIONS') {
    return handleOptions(request);
  }
  let url = new URL(request.url);
  if (url.pathname === '/robots.txt') {
    return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml');
  }
  if (url.pathname === '/sitemap.xml') {
    let response = new Response(generateSitemap());
    response.headers.set('content-type', 'application/xml');
    return response;
  }
  const notionUrl = 'https://www.notion.so' + url.pathname;
  let response;
  if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) {
    response = await fetch(notionUrl);
    let body = await response.text();
    response = new Response(body.replace(/www.notion.so/g, MY_DOMAIN).replace(/notion.so/g, MY_DOMAIN), response);
    response.headers.set('Content-Type', 'application/x-javascript');
  } else if ((url.pathname.startsWith('/api'))) {
    // Forward API
    response = await fetch(notionUrl, {
      body: request.body,
      headers: {
        'content-type': 'application/json;charset=UTF-8',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
      },
      method: 'POST',
    });
    response = new Response(response.body, response);
    response.headers.set('Access-Control-Allow-Origin', '*');
  } else if (slugs.indexOf(url.pathname.slice(1)) > -1) {
    const pageId = SLUG_TO_PAGE[url.pathname.slice(1)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 301);
  } else {
    response = await fetch(notionUrl, {
      body: request.body,
      headers: request.headers,
      method: request.method,
    });
    response = new Response(response.body, response);
    response.headers.delete('Content-Security-Policy');
    response.headers.delete('X-Content-Security-Policy');
  }

  return appendJavascript(response, SLUG_TO_PAGE);
}

class MetaRewriter {
  element(element) {
    if (PAGE_TITLE !== '') {
      if (element.getAttribute('property') === 'og:title'
        || element.getAttribute('name') === 'twitter:title') {
        element.setAttribute('content', PAGE_TITLE);
      }
      if (element.tagName === 'title') {
        element.setInnerContent(PAGE_TITLE);
      }
    }
    if (PAGE_DESCRIPTION !== '') {
      if (element.getAttribute('name') === 'description'
        || element.getAttribute('property') === 'og:description'
        || element.getAttribute('name') === 'twitter:description') {
        element.setAttribute('content', PAGE_DESCRIPTION);
      }
    }
    if (element.getAttribute('property') === 'og:url'
      || element.getAttribute('name') === 'twitter:url') {
      element.setAttribute('content', MY_DOMAIN);
    }
    if (element.getAttribute('name') === 'apple-itunes-app') {
      element.remove();
    }
  }
}

class HeadRewriter {
  element(element) {
    if (GOOGLE_FONT !== '') {
      element.append(`<link href="https://fonts.googleapis.com/css?family=${GOOGLE_FONT.replace(' ', '+')}:Regular,Bold,Italic&display=swap" rel="stylesheet">
      <style>* { font-family: "${GOOGLE_FONT}" !important; }</style>`, {
       html: true
      });
    }
    element.append(`<style>
    div.notion-topbar > div > div:nth-child(3) { display: none !important; }
    div.notion-topbar > div > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(5) { display: none !important; }
    div.notion-topbar > div > div:nth-child(6) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    </style>`, {
      html: true
    })
  }
}

class BodyRewriter {
  constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
  }
  element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    const PAGE_TO_SLUG = {};
    const slugs = [];
    const pages = [];
    let redirected = false;
    Object.keys(SLUG_TO_PAGE).forEach(slug => {
      const page = SLUG_TO_PAGE[slug];
      slugs.push(slug);
      pages.push(page);
      PAGE_TO_SLUG[page] = slug;
    });
    function getPage() {
      return location.pathname.slice(-32);
    }
    function getSlug() {
      return location.pathname.slice(1);
    }
    function updateSlug() {
      const slug = PAGE_TO_SLUG[getPage()];
      if (slug != null) {
        history.replaceState(history.state, '', '/' + slug);
      }
    }
    const observer = new MutationObserver(function() {
      if (redirected) return;
      const nav = document.querySelector('.notion-topbar');
      const mobileNav = document.querySelector('.notion-topbar-mobile');
      if (nav && nav.firstChild && nav.firstChild.firstChild
        || mobileNav && mobileNav.firstChild) {
        redirected = true;
        updateSlug();
        const onpopstate = window.onpopstate;
        window.onpopstate = function() {
          if (slugs.includes(getSlug())) {
            const page = SLUG_TO_PAGE[getSlug()];
            if (page) {
              history.replaceState(history.state, 'bypass', '/' + page);
            }
          }
          onpopstate.apply(this, [].slice.call(arguments));
          updateSlug();
        };
      }
    });
    observer.observe(document.querySelector('#notion-app'), {
      childList: true,
      subtree: true,
    });
    const replaceState = window.history.replaceState;
    window.history.replaceState = function(state) {
      if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return;
      return replaceState.apply(window.history, arguments);
    };
    const pushState = window.history.pushState;
    window.history.pushState = function(state) {
      const dest = new URL(location.protocol + location.host + arguments[2]);
      const id = dest.pathname.slice(-32);
      if (pages.includes(id)) {
        arguments[2] = '/' + PAGE_TO_SLUG[id];
      }
      return pushState.apply(window.history, arguments);
    };
    const open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function() {
      arguments[1] = arguments[1].replace('${MY_DOMAIN}', 'www.notion.so');
      return open.apply(this, [].slice.call(arguments));
    };
  </script>${CUSTOM_SCRIPT}`, {
      html: true
    });
  }
}

async function appendJavascript(res, SLUG_TO_PAGE) {
  return new HTMLRewriter()
    .on('title', new MetaRewriter())
    .on('meta', new MetaRewriter())
    .on('head', new HeadRewriter())
    .on('body', new BodyRewriter(SLUG_TO_PAGE))
    .transform(res);
}

I hope you found this little hack useful!

May the road rise to meet you,

T.

A new role

Mars the Rabbit in her house

I am due to begin a new role as a highschool teacher in a few weeks time.

This will be my first ever time formally teaching in a secondary school. Prior to this all experience I had was just training as part of my Teaching Qualification programme, first at Lynfield College and then at Auckland Grammar School.

I’m really looking forward to teaching. While I am nervous about getting started I feel as though I have a lot to give and make. Over the next few weeks I intend on preparing myself as much as I can, as well as generating teaching resources that myself and others can use.

I’m excited to share what is to come with you all.

From my elbow to yours,

T.

Obsidian - an external rhizomatic brain

Earlier this week I started using Obsidian, an offline tool similar to Roam that links and networks ideas.

Obsidian on a glossary of design terms page

I have used a variety of tools in the past for note-taking. I’ve used Standard Notes, Left, and plain notebooks to keep a record of knowledge and things that I need to remember. Without recording information in some form, I find that I am constantly forgetting things that I’ve learnt and that recall is very difficult.

Obsidian in graph mode, showing connections like a spider web

Enter Obsidian. Like Roam, it utilises the Zettelkasten method of note-taking. Notes and thoughts are structured laterally and making connections between things is actively encouraged. The way this operates is much like how HTML documents function, only there is also an added visual graph that allows for more of a rhizomatic navigation of ideas.

Obsidian files all stored locally as Markdown documents

I am currently using this tool to record and gather information on web development and design terminology. There are already links starting to open up between the two, and potential links that I wouldn’t have otherwise thought of unless I’d seen the two ideas side-by-side. This is quite possibly the most creative form of information management that I’ve ever come across, one that I intend on using for my own projects.

Sincerely,
T.

The snail goes back to making

Mars the Rabbit looking into the camera

Life has been busy.

I am always looking for new and different ways of improving my life, both the quality of it and how to better myself in terms of character and skill for the future. I’ve been so dead-set in getting myself stronger so that I can have a better job, have more money, own more things and so on. One of my greatest fears is to become complacent and set in my ways.

However, the danger of being so strung up about constant improvement and renewal is forgetting what you already have. In my case, this has been the skills that I have already picked up and worked hard on while at university. I have neglected my creative practice for the longest time, in part because of how things ended up when I finished (will cover this in more detail some other time) and a general disillusionment with academia at large. After I had graduated I didn’t want anthing to do with that world anymore, and as a result took to doing different things and just trying to enjoy my life as I left all the heavy shit behind.

Despite all this, only recently have I realised that I really miss making things. Some of my favourite memories are of making different things just for fun and experimentation, not for a presentation or audience but just the process was all that mattered for me. Whether that was performing for the camera or out to the silent void that is the Internet, these are some of my most special moments.

After what I would consider to have been an extended hiatus, I have gone back to making things again. I don’t really have a plan with any of it yet, but I have thought about revisiting things that I have already worked with to try and mine them for new meaning. My little Thinkpad isn’t really built for image editing and video, but that’s precisely what I’ve been doing over the last few weeks. It’s a gentle process of trying out different things but not pushing the machine too hard to risk overheating/kernel failure!

I’ve abandoned using the Adobe Creative Suite for my work. I now consider it malware insofar as it traps users into contracts and requiring the payment of cancellation fees, uses a shit ton of local resources just to install and remove their apps, and requires a constant Internet connection to phone home to Adobe servers (if it cannot do this every 7 days you are locked out of the CC Suite). While their software has now become the industry standard, I don’t think an individual like me really needs something as convoluted as this. Now, I hop between a mixture of Kdenlive, Krita, Dotgrid and Inkscape (all free and open source software!)

At the moment I’ve been trying to make a video using my notes and scraps from the past month during lockdown. Not sure where it’s headed but it’s getting me excited in any case.

From my screen to yours,
T.

Status report

Many big developments as of late.

Sophie and I have moved into our new place. This is the first time I’ve ever felt that there’s been a space that has been truly ours and a result of what we’ve worked for.

Interior view of our place

The sense of responsibility over ones space is awesome. We have been able to furnish and organise all of our contents exactly how we want, with no worry of what others will think because it doesn’t matter! I feel as though a great sense of potential lies before us in this house, and of what the future holds in general.

Being back at work has proved to be mostly fine. I still wish I was spending more time at home working on my own projects but I shouldn’t complain - I am thankful to have work and it’s been good to see my co-workers again. Not much progress on my programming practice either but am hoping to work on this over the next few weeks.

T.

Back at it

I went back to work this week, and it has been absolutely hectic so far.

Before Level 2 went into effect I guessed that people would react one of two ways:

One, that they would be afraid to venture out and continue to stay at home where possible, or

Two, that everyone would go back out en masse and rush to the mall, park, KFC and so on.

It seems the latter has turned out to be result of this shift. I wonder how long it will be until the lockdown will feel like a distant memory. At the same time, I can’t help but feel we have so much more to learn from this and are still incredibly unprepared.

T.