1. 13 Feb, 2020 3 commits
  2. 12 Feb, 2020 2 commits
    • Vincent's avatar
      Prevent note re-renders when adding a new one · 39497c0f
      Vincent authored
      Because the parent components of the note elements also maintained
      the form state for the "new note" field, every time that field
      changed, the entire component and its child note components would
      also re-render. By splitting it up in a separate component, React
      can now just re-render that notes form field, which is way more
      performant with large notes.
    • Vincent's avatar
      Fix new notes not getting rendered · ad41aa91
      Vincent authored
  3. 06 Feb, 2020 7 commits
  4. 09 Jan, 2020 1 commit
  5. 08 Jan, 2020 10 commits
  6. 04 Jan, 2020 3 commits
  7. 28 Nov, 2019 1 commit
  8. 18 Nov, 2019 1 commit
  9. 14 Nov, 2019 1 commit
  10. 04 Nov, 2019 11 commits
    • Vincent's avatar
      Render text notes as Markdown · 52feb125
      Vincent authored
      Markdown syntax is intended to be both human-readable, and
      parseable to apply basic formatting. Therefore, we can render
      regular plain text content as Markdown to display it a little
      prettier than a simple `<pre>` element.
    • Vincent's avatar
      Deploy the Notepod app · 440defca
      Vincent authored
    • Vincent's avatar
      Add relevant information to the README · f8c25604
      Vincent authored
    • Vincent's avatar
      Deleting notes · 7db55512
      Vincent authored
      This, too, should not be too surprising: although the
      `removeSubject()` method is new, its purpose should be clear.
    • Vincent's avatar
      Allow aborting an edit · e8bf2451
      Vincent authored
    • Vincent's avatar
      Add ability to edit a Note · 06031f70
      Vincent authored
      You should have the tools to do what we're doing now: add the
      ability to edit an existing Note.
    • Vincent's avatar
      Adding notes · 41d805da
      Vincent authored
      This final step is mostly to tie things together: it adds the
      ability to add new notes and view existing ones. From here, you
      should know enough to expand on the functionality: you could add
      the ability to add a title (a schema:headline?) to the note, or
      add other metadata such as the author (a schema:creator), or just
      take the things you've learned now and build a different type of
      web app altogether!
      The relevant code in this commit is in `addNote`. Given a note (a
      string), it initialises a new Subject with the note as its
      contents (through schema:text) and the current time as
      schema:dateCreated. Then, the <NotesList> component can use that
      date to sort the user's notes by date, from newest to oldest.
      So that's it for now! Study the code, start building your own web
      apps (you can usually find Vocabularies for your use cases at
      Linked Open Vocabularies [1]), and if you have questions, make
      sure to ask them at the Solid Forum [2].
      [1] https://lov.linkeddata.es/dataset/lov/
      [2] https://forum.solidproject.org/
    • Vincent's avatar
      Setting up our data model · 900cbbb2
      Vincent authored
      Now it's time to start working on some actual functionality: we'll
      be making an app that allows people to keep notes. With this
      commit, we'll prepare their Pod for our data model - much like how
      you might prepare database tables in a traditional application
      with a custom back-end.
      At a high level, this involves the following steps:
      1. Check if there exists a Document to track notes in.
      2. If it does not exist, create it.
      3. Fetch that Document.
      So to start with the first step: in which Document should we track
      notes? The answer lies in the concept of a _Public Type Index_.
      The Public Type Index is itself a publicly accessible Document
      stored in the user's Pod. This Document contains a list of links to
      other Documents, along with the type of data that is to be included
      in those Documents. To store notes, the data type we will use is
      the `TextDigitalDocument`, defined by Schema.org [1]. Every
      time the user saves a note, we will store it as a
      If the Document containing these notes was located in the
      user's Pod at `/public/notes.ttl`, their Public Type Index could
      refer to it like this:
      | #notes | rdf:type       | solid:TypeRegistration     |
      | #notes | solid:forClass | schema:TextDigitalDocument |
      | #notes | solid:instance | /public/notes.ttl          |
      The above Type Index includes one Type Registration, identified by
      `#notes`, that registers `/public/notes.ttl` for the data type
      So how do we find the user's Public Type Index? It's usually listed
      in their profile, i.e. the Document accessible at their WebID:
      | #me | solid:publicTypeIndex | /settings/publicTypeIndex.ttl |
      This is a pattern you'll often encounter when writing Solid apps:
      you start with the user's WebID, read the profile Document located
      there, and from there you can find the data you need.
      So that's exactly what we do in this commit. In the `<Dashboard>`
      component, we want to list the number of movies the user has seen
      so far. Working backwards from there:
      1. `<Dashboard>` calls the `useNotesList` Hook [2] to initiate
         the process of fetching existing reviews.
      2. `useNotesList` retrieves a list of _every_ note the user
         has listed. `getNotes` can then filter the resulting
         Document to return only those that apply to elements of type
      3. `useNotesList` calls `usePublicTypeIndex` to get the Public
         Type Index. In there, it finds the URL of the Document
         containing notes, and returns that Document.
      4. `usePublicTypeIndex` is a small Hook that calls
          `fetchPublicTypeIndex`, which retrieves the URL of the Public
         Type Index from the user's profile Document and fetches it.
      This is all well and good, but there's one potential snag: what
      if the Public Type Index does not list a Document to track
      notes in? To deal with this, `useNotesList` has one more
      trick up its sleeve: if it cannot find a Document that lists
      reviews, it calls `initialiseNotesList`, which creates a new
      file at `/public/notes.ttl` in the user's Pod, and adds it to
      their Public Type Index.
      If all the above sounds awfully complicated: that's because it is.
      Work is underway to make this easier in the future, allowing you
      to point a library to a data model, and having the library make
      sure that the user's Pod is prepared to handle that. For more
      information, see [3]. For now, though, you'll have to live with
      these convoluted instructions.
      [1] https://schema.org/TextDigitalDocument
      [2] https://reactjs.org/docs/hooks-intro.html
      [3] https://ruben.verborgh.org/blog/2019/06/17/shaping-linked-data-apps/
    • Vincent's avatar
      Accessing our first bits of data! · 5c534abd
      Vincent authored
      Now we're getting started! In this commit, we fetch our first bit
      of data: the name of the current user. To explain how that works,
      I'll need to give you a crash course on Linked Data - but don't
      worry, I'll limit myself only to what is absolutely necessary to
      understand what's going on.
      In traditional back-ends, data is usually stored in database
      tables. When your back-end is a Solid Pod, however, data is stored
      in _Documents_ living at a certain URL - you can think of these as
      being similar to HTML documents living at a URL.
      When a person logs in to our app, we receive a _WebID_. A WebID can
      be used both to identify that user -since no other user shares that
      URL-, and as a pointer to a Document with information relevant to
      that user.
      My WebID is `https://vincentt.inrupt.net/profile/card#me`. That
      means that there is a Document at
      `https://vincentt.inrupt.net/profile/card` that contains data
      relevant to me.
      The most important thing to understand when writing a Solid app is
      how that data is represented. In a nutshell, a Document contains a
      list of relationships between things. For example, the Document
      referred to by my WebID contain the following relationships:
      | <some person> | has name  | Vincent   |
      | <some person> | works at  | Inrupt    |
      | <some person> | job title | Developer |
      (The common terminology is: every row contains a _Statement_, with
      in the first column the Statement's _Subject_, in the second column
      a _Predicate_, and the _Object_ in the third column.)
      You might notice that the table above is a description of
      `<some person>`: it's someone whose name is Vincent, who works at
      Inrupt and is a developer. However, `<some person>` is not really
      usable as a stable and unique identifier: for all we know, someone
      else might have a Document elsewhere that uses the exact same
      identifier. To solve this problem, Solid uses URLs as unique
      identifiers: after all, the Document that describes the entries
      already has a URL, and we can be sure that no other Document uses
      the same one.
      And like specific elements in an HTML document can be referred to
      by appending their ID to the document's URL, we can give elements
      we want to describe in a Document a unique identifier. As you might
      have been able to guess, instead of `<some person>` we can use
      `me` - hence the WebID
      So now we might consider my Document to look as follows:
      | #me | has name  | Vincent   |
      | #me | works at  | Inrupt    |
      | #me | job title | Developer |
      But there's one more thing to consider: interoperability. An
      important tenet of Solid is being able to give multiple apps access
      to the same data: if I enter my name at service A, I don't want to
      have to re-enter it at service B. But if one service uses "name" to
      refer to a person's full name, whereas the other uses it to refer
      to a person's last name only, that would nip interoperbility in
      the bud.
      What's needed here is unique terms that have an agreed-upon
      definition. And just like we can have a Document describing me, we
      could also make Documents describing a term. And in fact, many
      people have done exactly that, for many different terms you might
      want to use. These Documents are called _Vocabularies_, and there's
      one for things you might want to put on a business card at
      http://www.w3.org/2006/vcard/ns — the _vCard_ Vocabulary. It
      contains Statements along the lines of:
      (As a shorthand for `http://www.w3.org/2006/vcard/ns#role`, we
      will use `vcard:role`.)
      So now we can use `vcard:role`, and be relatively confident that
      every other app using it will use it in the way described at that
      URL. We can combine terms from different Vocabularies, e.g. the
      FOAF ("Friend of a friend") vocabulary has a term to refer to a
      person's name at `http://xmlns.com/foaf/0.1/name`. My Document
      could thus look something like this:
      | #me | foaf:name               | Vincent   |
      | #me | vcard:organization-name | Inrupt    |
      | #me | vcard:role              | Developer |
      Everything that needs to be uniquely defined has a URL, with some
      _Literal_ values for the rest ("Vincent", "Inrupt", "Developer").
      You could imagine "Inrupt" to be replaced by a URL as well,
      pointing to a Document describing the organisation itself - but for
      the intents and purposes of this tutorial, we will leave it at this.
      (It might give you some insight into why all this is referred to as
      "Linked Data" though!)
      A Document can describe multiple things. As an example, my Document
      could also have an entry for my address, and then link that to me:
      | #me       | foaf:name               | Vincent             |
      | #me       | vcard:organization-name | Inrupt              |
      | #me       | vcard:role              | Developer           |
      | #me       | vcard:hasAddress        | #address1           |
      | #address1 | vcard:street-address    | 155 Country Lane    |
      | #address1 | vcard:region            | Cottingshire County |
      Phew! That should cover about all the Linked Data theory you should
      know to start working with Solid. So now let's see what's actually
      happening in this commit.
      The only user-visible change in this commit is that their name will
      be shown when they are logged in, and a foaf:name is present in
      their profile Document. So how is this done?
      The magic is in the newly added `fetchProfile` function. This
      function does two things:
      First, it requests the user's WebID from `solid-auth-client`. When
      the user has logged in using the authentication mechanism we added
      in an earlier commit, this will return their WebID, i.e. the URL to
      a Document container a representation of the user.
      Then, with the WebID in hand, it uses a library called _Tripledoc_
      to retrieve that document's content. Then, `document.getSubject`
      returns a representation of all the Statements in that document
      that have the user's WebID as the Subject - so in the example table
      above, the rows with `#me` in the first column rather than
      `useProfile` is then called in the `<Dashboard>` component.
      Although `useProfile` provides access to all Statements about the
      user in their profile, we're currently only really interested in
      their name. We're going to use the `name` predicate of FOAF for
      this, so we're looking for a Statement like:
      | #me | http://xmlns.com/foaf/0.1/name | Vincent |
      Since `useProfile` returned all Statements with `#me` as the
      Subject, the Dashboard has access to it through `profile`. We can
      access the name by calling:
          const name = profile.getString('http://xmlns.com/foaf/0.1/name');
      That returns a the user's name, if set to a literal string value,
      rather than a URL that points to a different Document.
      There are a number of Vocabularies that define terms used in many
      types of apps. To save you from having to copy-paste the URL of a
      Vocabulary time and time again, the `rdf-namespaces` package
      includes some useful helper objects for common Vocabularies —
      such as FOAF. Thus, the above can be shortened to:
          import { foaf } from 'rdf-namespaces';
          const name = profile.getString(foaf.name);
      Finally, one disadvantage of Solid, as a developer, is that there
      are no guarantees about the data you want to access: you cannot be
      sure that a piece of data exists. The user might not have provided
      their name, or used a different Vocabulary to represent it. Thus,
      we should take into account that `profile.getString` might return
    • Vincent's avatar
      Adding basic authentication functionality · f42f8ae6
      Vincent authored
      To be able to write to a person's Pod, they need to log in and
      authorise our app. To do this, we use the React components in
      Although it unfortunately comes with a bunch of stuff we will not
      be using, the authentication components are easy enough to use:
      wrap components that should be visible to people who are not
      logged in inside a <LoggedOut> component, and those that should
      only be visible to those who are logged in inside a <LoggedIn>.
      Actual authentication is initiated with a <LoginButton>.
      Unfortunately, at this time this involves including an HTML blob
      verbatim as downloaded from
      (this is linked to from the README of @solid/react). In the future,
      this will hopefully be properly packaged.
    • Vincent's avatar
      Adding a CSS framework · 6ab33377
      Vincent authored
      This commit is not specific to building a Solid app, but it allows
      us to use the Bulma CSS framework: https://bulma.io
      This will allow us to use some basic styling without having to
      focus on it too much - distracting from the tutorial.