├── .github
└── workflows
│ ├── ci-cd.yml
│ ├── contacts-ci-cd.yml
│ ├── manual-deploy.yml
│ └── service-worker-ci-cd.yml
├── .gitignore
├── .prettierrc.json
├── CONTRIBUTING.md
├── LICENSE
├── Readme.md
├── apps
├── .github
│ └── workflows
│ │ └── playwright.yml
├── .gitignore
├── Makefile
├── Readme.md
├── build.sh
├── index.html
├── package-lock.json
├── package.json
├── playwright-app.config.ts
├── playwright-base.config.ts
├── playwright-elements.config.ts
├── pos-app-browser.manifest.json
├── service-worker.js
├── test-solid-server
│ └── data
│ │ ├── .internal
│ │ ├── accounts
│ │ │ ├── data
│ │ │ │ └── a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe$.json
│ │ │ └── index
│ │ │ │ ├── owner
│ │ │ │ └── 7cd9ce45-8ea7-4dd6-9fa7-a3998462c827$.json
│ │ │ │ ├── password
│ │ │ │ ├── 9998b9b2-b899-41b2-8e46-70d49671adcd$.json
│ │ │ │ └── email
│ │ │ │ │ └── alice@mail.example$.json
│ │ │ │ ├── pod
│ │ │ │ ├── 53059874-1474-4c58-8e0d-db041582666b$.json
│ │ │ │ └── baseUrl
│ │ │ │ │ └── http%3A%2F%2Flocalhost%3A4000%2Falice%2F$.json
│ │ │ │ └── webIdLink
│ │ │ │ ├── aaefa71e-51a8-494d-bbbe-6e6020cbf6db$.json
│ │ │ │ └── webId
│ │ │ │ └── http%3A%2F%2Flocalhost%3A4000%2Falice%2Fprofile%2Fcard#me$.json
│ │ ├── idp
│ │ │ └── keys
│ │ │ │ ├── cookie-secret$.json
│ │ │ │ └── jwks$.json
│ │ └── setup
│ │ │ ├── current-base-url$.json
│ │ │ ├── current-server-version$.json
│ │ │ ├── setupCompleted-2.0$.json
│ │ │ └── v6-migration$.json
│ │ └── alice
│ │ ├── .acl
│ │ ├── .meta
│ │ ├── container
│ │ ├── .acl
│ │ ├── another-sub-container
│ │ │ └── .keep-me
│ │ ├── readme.md
│ │ ├── resource$.ttl
│ │ └── sub-container
│ │ │ └── .keep-me
│ │ ├── make-findable
│ │ └── banana$.ttl
│ │ ├── profile
│ │ ├── card$.ttl
│ │ └── card.acl
│ │ ├── public
│ │ ├── .acl
│ │ └── generic
│ │ │ ├── resource$.ttl
│ │ │ └── thing.png
│ │ └── settings
│ │ ├── prefs.ttl
│ │ └── privateLabelIndex.ttl
└── tests
│ ├── actions
│ └── signIn.ts
│ ├── add-literal-value.spec.ts
│ ├── add-new-thing.spec.ts
│ ├── fixtures
│ └── credentials.ts
│ ├── generic.spec.ts
│ ├── ldp-container.spec.ts
│ ├── person.spec.ts
│ ├── reading-while-offline.spec.ts
│ └── text-search.spec.ts
├── assets
├── logo-text-white.svg
├── logo-text.svg
├── logo.png
└── logo.svg
├── contacts
├── .editorconfig
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── jest-setup.ts
├── jest.config.js
├── package.json
├── readme.md
├── src
│ ├── components.d.ts
│ ├── components
│ │ ├── address-book-page
│ │ │ ├── address-book-page.css
│ │ │ ├── address-book-page.spec.tsx
│ │ │ ├── address-book-page.tsx
│ │ │ └── readme.md
│ │ ├── app.tsx
│ │ ├── contact-details
│ │ │ ├── contact-details.css
│ │ │ ├── contact-details.spec.tsx
│ │ │ ├── contact-details.tsx
│ │ │ ├── email-addresses
│ │ │ │ ├── email-addresses.css
│ │ │ │ ├── email-addresses.spec.tsx
│ │ │ │ ├── email-addresses.tsx
│ │ │ │ └── readme.md
│ │ │ ├── group-details
│ │ │ │ ├── group-details.tsx
│ │ │ │ └── readme.md
│ │ │ ├── phone-numbers
│ │ │ │ ├── phone-numbers.css
│ │ │ │ ├── phone-numbers.spec.tsx
│ │ │ │ ├── phone-numbers.tsx
│ │ │ │ └── readme.md
│ │ │ └── readme.md
│ │ ├── contact-list
│ │ │ ├── contact-list.css
│ │ │ ├── contact-list.spec.tsx
│ │ │ ├── contact-list.tsx
│ │ │ └── readme.md
│ │ ├── contact.tsx
│ │ ├── create-new-contact-form
│ │ │ ├── create-new-contact-form.css
│ │ │ ├── create-new-contact-form.tsx
│ │ │ ├── readme.md
│ │ │ └── test
│ │ │ │ └── create-new-contact-form.spec.tsx
│ │ ├── create-new-contact
│ │ │ ├── create-new-contact.css
│ │ │ ├── create-new-contact.tsx
│ │ │ ├── readme.md
│ │ │ └── test
│ │ │ │ └── create-new-contact.spec.tsx
│ │ ├── group-list
│ │ │ ├── group-list.css
│ │ │ ├── group-list.spec.tsx
│ │ │ ├── group-list.tsx
│ │ │ └── readme.md
│ │ ├── group.tsx
│ │ ├── list-address-books
│ │ │ ├── list-address-books.css
│ │ │ ├── list-address-books.tsx
│ │ │ ├── readme.md
│ │ │ └── test
│ │ │ │ └── list-address-book.spec.tsx
│ │ ├── loading-spinner
│ │ │ ├── loading-spinner.css
│ │ │ ├── loading-spinner.tsx
│ │ │ └── readme.md
│ │ ├── open-address-book
│ │ │ ├── open-address-book.css
│ │ │ ├── open-address-book.tsx
│ │ │ ├── readme.md
│ │ │ └── test
│ │ │ │ └── open-address-book.spec.tsx
│ │ ├── readme.md
│ │ ├── router
│ │ │ ├── readme.md
│ │ │ └── router.tsx
│ │ └── welcome-page
│ │ │ ├── readme.md
│ │ │ ├── test
│ │ │ └── welcome-page.spec.tsx
│ │ │ ├── welcome-page.css
│ │ │ └── welcome-page.tsx
│ ├── events
│ │ ├── PodOsModuleAware.ts
│ │ └── usePodOS.ts
│ ├── favicon-32x32.png
│ ├── global.css
│ ├── index.html
│ ├── index.ts
│ ├── manifest.json
│ ├── netlify
│ │ └── _redirects
│ └── utils
│ │ └── debounceTime.ts
├── stencil.config.ts
└── tsconfig.json
├── core
├── .gitignore
├── CHANGELOG.md
├── Readme.md
├── babel.config.json
├── esbuild
│ ├── build-bundle.mjs
│ ├── build-esm.mjs
│ ├── esm-config.mjs
│ └── watch-esm.mjs
├── eslint.config.mjs
├── jest.config.js
├── package.json
├── src
│ ├── PodOs.spec.ts
│ ├── Store.spec.ts
│ ├── Store.ts
│ ├── authentication
│ │ ├── index.ts
│ │ ├── observeSession.spec.ts
│ │ └── observeSession.ts
│ ├── files
│ │ ├── BinaryFile.ts
│ │ ├── BrokenFile.spec.ts
│ │ ├── BrokenFile.ts
│ │ ├── FileFetcher.spec.ts
│ │ ├── FileFetcher.ts
│ │ ├── HttpStatus.spec.ts
│ │ ├── HttpStatus.ts
│ │ ├── SolidFile.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── ldp-container
│ │ ├── LdpContainer.assume.spec.ts
│ │ ├── LdpContainer.contains.spec.ts
│ │ ├── LdpContainer.ts
│ │ └── index.ts
│ ├── modules
│ │ └── contacts.ts
│ ├── namespaces
│ │ └── index.ts
│ ├── offline-cache
│ │ ├── OfflineCache.ts
│ │ ├── OfflineCapableFetcher.spec.ts
│ │ ├── OfflineCapableFetcher.ts
│ │ ├── OnlineStatus.ts
│ │ ├── index.ts
│ │ └── rdflib.d.ts
│ ├── profile
│ │ ├── WebIdProfile.spec.ts
│ │ ├── WebIdProfile.ts
│ │ └── index.ts
│ ├── rdf-document
│ │ ├── RdfDocument.assume.spec.ts
│ │ ├── RdfDocument.subjects.spec.ts
│ │ ├── RdfDocument.ts
│ │ └── index.ts
│ ├── search
│ │ ├── LabelIndex.spec.ts
│ │ ├── LabelIndex.ts
│ │ ├── SearchGateway.integration.spec.ts
│ │ ├── SearchGateway.ts
│ │ ├── SearchIndex.spec.ts
│ │ ├── SearchIndex.ts
│ │ ├── addToLabelIndex.spec.ts
│ │ ├── addToLabelIndex.ts
│ │ ├── createDefaultLabelIndex.spec.ts
│ │ ├── createDefaultLabelIndex.ts
│ │ └── index.ts
│ ├── terms
│ │ ├── Term.ts
│ │ ├── createListOfTerms.spec.ts
│ │ ├── createListOfTerms.ts
│ │ ├── index.ts
│ │ ├── listKnownTerms.spec.ts
│ │ └── listKnownTerms.ts
│ ├── thing
│ │ ├── Thing.anyValue.spec.ts
│ │ ├── Thing.description.spec.ts
│ │ ├── Thing.label.spec.ts
│ │ ├── Thing.literals.spec.ts
│ │ ├── Thing.picture.spec.ts
│ │ ├── Thing.relations.spec.ts
│ │ ├── Thing.reverseRelations.spec.ts
│ │ ├── Thing.ts
│ │ ├── Thing.types.spec.ts
│ │ ├── accumulateSubjects.spec.ts
│ │ ├── accumulateSubjects.ts
│ │ ├── accumulateValues.spec.ts
│ │ ├── accumulateValues.ts
│ │ ├── index.ts
│ │ ├── isRdfType.spec.ts
│ │ ├── isRdfType.ts
│ │ ├── labelForType.spec.ts
│ │ ├── labelForType.ts
│ │ ├── labelFromUri.spec.ts
│ │ └── labelFromUri.ts
│ └── uri
│ │ ├── UriService.spec.ts
│ │ └── UriService.ts
└── tsconfig.json
├── dev-solid-server
├── .gitignore
├── Readme.md
├── config
│ └── pod-os-dev.json
├── data
│ ├── .internal
│ │ ├── accounts
│ │ │ ├── data
│ │ │ │ ├── 4b953dff-7b43-4fca-89df-54e3150e19bf$.json
│ │ │ │ └── e0fb0b69-e0f0-4d19-abcf-94d15998b142$.json
│ │ │ └── index
│ │ │ │ ├── owner
│ │ │ │ ├── a7b02e31-ac03-44b3-b246-9cadb45f349c$.json
│ │ │ │ └── b49908e4-9a89-4678-8b7f-53557beafa31$.json
│ │ │ │ ├── password
│ │ │ │ ├── 329e144f-8397-40ef-a27f-09a1cbcf3b9c$.json
│ │ │ │ ├── ad8cd822-76aa-47d7-bfec-4771ce2b5cd8$.json
│ │ │ │ └── email
│ │ │ │ │ ├── alice@pod-os.test$.json
│ │ │ │ │ └── bob@pod-os.test$.json
│ │ │ │ ├── pod
│ │ │ │ ├── b718b08a-b19b-43fb-aa0d-449c60caf639$.json
│ │ │ │ ├── baseUrl
│ │ │ │ │ ├── http%3A%2F%2Flocalhost%3A3000%2Falice%2F$.json
│ │ │ │ │ └── http%3A%2F%2Flocalhost%3A3000%2Fbob%2F$.json
│ │ │ │ └── e1bbed34-717c-4880-bdad-aa42eff89301$.json
│ │ │ │ └── webIdLink
│ │ │ │ ├── 973386d0-2e2c-4c8f-8cfa-dee71087c503$.json
│ │ │ │ ├── c7cb8ea5-f48e-4bb6-83f2-5384cfb9505d$.json
│ │ │ │ └── webId
│ │ │ │ ├── http%3A%2F%2Flocalhost%3A3000%2Falice%2Fprofile%2Fcard#me$.json
│ │ │ │ └── http%3A%2F%2Flocalhost%3A3000%2Fbob%2Fprofile%2Fcard#me$.json
│ │ ├── idp
│ │ │ └── keys
│ │ │ │ ├── cookie-secret$.json
│ │ │ │ └── jwks$.json
│ │ └── setup
│ │ │ ├── current-base-url$.json
│ │ │ ├── current-server-version$.json
│ │ │ ├── rootInitialized$.json
│ │ │ └── v6-migration$.json
│ ├── alice
│ │ ├── .acl
│ │ ├── .meta
│ │ ├── README$.markdown
│ │ ├── README.acl
│ │ ├── contacts
│ │ │ ├── Person
│ │ │ │ └── 8xO3yY
│ │ │ │ │ └── index.ttl
│ │ │ ├── groups.ttl
│ │ │ ├── index.ttl
│ │ │ └── people.ttl
│ │ ├── documents
│ │ │ ├── notes.md
│ │ │ ├── page.html
│ │ │ └── test.pdf
│ │ ├── games
│ │ │ ├── .acl
│ │ │ └── minecraft$.ttl
│ │ ├── pages
│ │ │ ├── .acl
│ │ │ └── index.html
│ │ ├── profile
│ │ │ ├── card$.ttl
│ │ │ └── card.acl
│ │ └── settings
│ │ │ ├── hugePrivateLabelIndex.ttl
│ │ │ ├── prefs.ttl
│ │ │ └── privateLabelIndex.ttl
│ └── bob
│ │ ├── .acl
│ │ ├── .meta
│ │ ├── README$.markdown
│ │ ├── README.acl
│ │ └── profile
│ │ ├── card$.ttl
│ │ └── card.acl
└── package.json
├── docs
├── contributing
│ └── definition-of-done.md
├── decisions
│ ├── 0000-use-markdown-architectural-decision-records.md
│ ├── 0001-use-stenciljs-and-ionic-for-components.md
│ ├── 0002-separate-pod-os-elements-and-core.md
│ ├── 0003-handle-data-with-rdflibjs.md
│ ├── 0004-no-stencil-e2e-tests.md
│ ├── 0005-deploy-immutable-web-app-to-netlify.md
│ ├── 0006-end-to-end-testing-via-playwright.md
│ ├── 0007-introduce-pollen-css.md
│ ├── 0008-full-text-search.md
│ ├── 0009-introduce-rxjs.md
│ ├── 0010-get-rid-of-ionic.md
│ ├── 0011-read-only-offline-access.md
│ └── template.md
├── elements
│ ├── apps
│ │ ├── pos-app-browser
│ │ │ └── readme.md
│ │ ├── pos-app-dashboard
│ │ │ ├── pos-example-resources
│ │ │ │ └── readme.md
│ │ │ ├── pos-getting-started
│ │ │ │ └── readme.md
│ │ │ └── readme.md
│ │ ├── pos-app-document-viewer
│ │ │ └── readme.md
│ │ ├── pos-app-generic
│ │ │ └── readme.md
│ │ ├── pos-app-image-viewer
│ │ │ └── readme.md
│ │ ├── pos-app-ldp-container
│ │ │ └── readme.md
│ │ ├── pos-app-pdf-viewer
│ │ │ └── readme.md
│ │ ├── pos-app-rdf-document
│ │ │ └── readme.md
│ │ └── pos-app-settings
│ │ │ ├── pos-getting-started
│ │ │ └── readme.md
│ │ │ ├── pos-setting-offline-cache
│ │ │ └── readme.md
│ │ │ └── readme.md
│ └── components
│ │ ├── pos-add-literal-value
│ │ └── readme.md
│ │ ├── pos-add-new-thing
│ │ └── readme.md
│ │ ├── pos-app
│ │ └── readme.md
│ │ ├── pos-container-contents
│ │ └── readme.md
│ │ ├── pos-demo-app
│ │ └── readme.md
│ │ ├── pos-description
│ │ └── readme.md
│ │ ├── pos-dialog
│ │ └── readme.md
│ │ ├── pos-document
│ │ └── readme.md
│ │ ├── pos-error-toast
│ │ └── readme.md
│ │ ├── pos-image
│ │ └── readme.md
│ │ ├── pos-internal-router
│ │ └── readme.md
│ │ ├── pos-label
│ │ └── readme.md
│ │ ├── pos-literals
│ │ └── readme.md
│ │ ├── pos-login-form
│ │ └── readme.md
│ │ ├── pos-login
│ │ └── readme.md
│ │ ├── pos-make-findable
│ │ └── readme.md
│ │ ├── pos-navigation-bar
│ │ └── readme.md
│ │ ├── pos-new-thing-form
│ │ └── readme.md
│ │ ├── pos-pdf
│ │ └── readme.md
│ │ ├── pos-picture
│ │ └── readme.md
│ │ ├── pos-predicate
│ │ └── readme.md
│ │ ├── pos-relations
│ │ └── readme.md
│ │ ├── pos-resource
│ │ └── readme.md
│ │ ├── pos-reverse-relations
│ │ └── readme.md
│ │ ├── pos-rich-link
│ │ └── readme.md
│ │ ├── pos-router
│ │ └── readme.md
│ │ ├── pos-select-term
│ │ └── readme.md
│ │ ├── pos-subjects
│ │ └── readme.md
│ │ ├── pos-type-badges
│ │ └── readme.md
│ │ ├── pos-type-router
│ │ └── readme.md
│ │ └── pos-value
│ │ └── readme.md
└── features
│ └── full-text-search.md
├── elements
├── .editorconfig
├── .gitignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── jest-setup.ts
├── jest.config.js
├── package.json
├── readme.md
├── src
│ ├── apps
│ │ ├── pos-app-browser
│ │ │ ├── pos-app-browser.css
│ │ │ ├── pos-app-browser.spec.tsx
│ │ │ └── pos-app-browser.tsx
│ │ ├── pos-app-dashboard
│ │ │ ├── pos-app-dashboard.css
│ │ │ ├── pos-app-dashboard.tsx
│ │ │ ├── pos-example-resources
│ │ │ │ ├── pos-example-resources.css
│ │ │ │ └── pos-example-resources.tsx
│ │ │ └── pos-getting-started
│ │ │ │ ├── pos-getting-started.css
│ │ │ │ └── pos-getting-started.tsx
│ │ ├── pos-app-document-viewer
│ │ │ ├── pos-app-document-viewer.spec.tsx
│ │ │ └── pos-app-document-viewer.tsx
│ │ ├── pos-app-generic
│ │ │ ├── pos-app-generic.css
│ │ │ └── pos-app-generic.tsx
│ │ ├── pos-app-image-viewer
│ │ │ ├── pos-app-image-viewer.spec.tsx
│ │ │ └── pos-app-image-viewer.tsx
│ │ ├── pos-app-ldp-container
│ │ │ └── pos-app-ldp-container.tsx
│ │ ├── pos-app-rdf-document
│ │ │ └── pos-app-rdf-document.tsx
│ │ └── pos-app-settings
│ │ │ ├── pos-app-settings.css
│ │ │ ├── pos-app-settings.tsx
│ │ │ └── pos-setting-offline-cache
│ │ │ ├── pos-setting-offline-cache.css
│ │ │ ├── pos-setting-offline-cache.spec.tsx
│ │ │ └── pos-setting-offline-cache.tsx
│ ├── cache
│ │ ├── IndexDbOfflineCache.spec.ts
│ │ ├── IndexedDbOfflineCache.ts
│ │ └── NavigatorOnlineStatus.ts
│ ├── components.d.ts
│ ├── components
│ │ ├── broken-file
│ │ │ ├── BrokenFile.spec.tsx
│ │ │ └── BrokenFile.tsx
│ │ ├── events
│ │ │ ├── PodOsAware.ts
│ │ │ └── ResourceAware.ts
│ │ ├── pos-add-literal-value
│ │ │ ├── pos-add-literal-value.css
│ │ │ ├── pos-add-literal-value.tsx
│ │ │ └── test
│ │ │ │ └── pos-add-literal-value.spec.tsx
│ │ ├── pos-add-new-thing
│ │ │ ├── pos-add-new-thing.css
│ │ │ ├── pos-add-new-thing.tsx
│ │ │ └── test
│ │ │ │ └── pos-add-new-thing.spec.tsx
│ │ ├── pos-app
│ │ │ ├── pos-app.spec.tsx
│ │ │ └── pos-app.tsx
│ │ ├── pos-container-contents
│ │ │ ├── pos-container-contents.css
│ │ │ ├── pos-container-contents.spec.tsx
│ │ │ ├── pos-container-contents.tsx
│ │ │ ├── pos-container-item.spec.tsx
│ │ │ ├── pos-container-item.tsx
│ │ │ ├── selectIconForTypes.spec.tsx
│ │ │ └── selectIconForTypes.ts
│ │ ├── pos-description
│ │ │ ├── pos-description.spec.tsx
│ │ │ └── pos-description.tsx
│ │ ├── pos-dialog
│ │ │ ├── pos-dialog.css
│ │ │ ├── pos-dialog.tsx
│ │ │ └── test
│ │ │ │ └── pos-dialog.spec.tsx
│ │ ├── pos-document
│ │ │ ├── pos-document.css
│ │ │ ├── pos-document.spec.tsx
│ │ │ └── pos-document.tsx
│ │ ├── pos-error-toast
│ │ │ ├── pos-error-toast.tsx
│ │ │ └── test
│ │ │ │ └── pos-error-toast.spec.tsx
│ │ ├── pos-image
│ │ │ ├── pos-image.css
│ │ │ ├── pos-image.integration.spec.tsx
│ │ │ ├── pos-image.spec.tsx
│ │ │ └── pos-image.tsx
│ │ ├── pos-internal-router
│ │ │ ├── pos-internal-router.spec.tsx
│ │ │ └── pos-internal-router.tsx
│ │ ├── pos-label
│ │ │ ├── pos-label.integration.spec.tsx
│ │ │ ├── pos-label.spec.tsx
│ │ │ └── pos-label.tsx
│ │ ├── pos-literals
│ │ │ ├── pos-literals.css
│ │ │ ├── pos-literals.spec.tsx
│ │ │ └── pos-literals.tsx
│ │ ├── pos-login-form
│ │ │ ├── pos-login-form.css
│ │ │ ├── pos-login-form.tsx
│ │ │ └── test
│ │ │ │ └── pos-login-form.spec.tsx
│ │ ├── pos-login
│ │ │ ├── pos-login.css
│ │ │ ├── pos-login.integration.spec.tsx
│ │ │ ├── pos-login.spec.tsx
│ │ │ └── pos-login.tsx
│ │ ├── pos-make-findable
│ │ │ ├── pos-make-findable.css
│ │ │ ├── pos-make-findable.spec.tsx
│ │ │ └── pos-make-findable.tsx
│ │ ├── pos-navigation-bar
│ │ │ ├── pos-navigation-bar.css
│ │ │ ├── pos-navigation-bar.integration.spec.tsx
│ │ │ ├── pos-navigation-bar.spec.tsx
│ │ │ └── pos-navigation-bar.tsx
│ │ ├── pos-new-thing-form
│ │ │ ├── pos-new-thing-form.css
│ │ │ ├── pos-new-thing-form.tsx
│ │ │ └── test
│ │ │ │ └── pos-new-thing-form.spec.tsx
│ │ ├── pos-picture
│ │ │ ├── pos-picture.css
│ │ │ ├── pos-picture.integration.spec.tsx
│ │ │ ├── pos-picture.spec.tsx
│ │ │ └── pos-picture.tsx
│ │ ├── pos-predicate
│ │ │ ├── pos-predicate.css
│ │ │ ├── pos-predicate.spec.tsx
│ │ │ └── pos-predicate.tsx
│ │ ├── pos-relations
│ │ │ ├── pos-relations.css
│ │ │ ├── pos-relations.spec.tsx
│ │ │ └── pos-relations.tsx
│ │ ├── pos-resource
│ │ │ ├── pos-resource.integration.spec.tsx
│ │ │ ├── pos-resource.spec.tsx
│ │ │ └── pos-resource.tsx
│ │ ├── pos-reverse-relations
│ │ │ ├── pos-reverse-relations.css
│ │ │ ├── pos-reverse-relations.spec.tsx
│ │ │ └── pos-reverse-relations.tsx
│ │ ├── pos-rich-link
│ │ │ ├── pos-rich-link.css
│ │ │ ├── pos-rich-link.spec.tsx
│ │ │ └── pos-rich-link.tsx
│ │ ├── pos-router
│ │ │ ├── pos-router.css
│ │ │ ├── pos-router.spec.tsx
│ │ │ └── pos-router.tsx
│ │ ├── pos-select-term
│ │ │ ├── pos-select-term.css
│ │ │ ├── pos-select-term.tsx
│ │ │ └── test
│ │ │ │ └── pos-select-term.spec.tsx
│ │ ├── pos-subjects
│ │ │ ├── pos-subjects.css
│ │ │ ├── pos-subjects.spec.tsx
│ │ │ └── pos-subjects.tsx
│ │ ├── pos-type-badges
│ │ │ ├── pos-type-badges.css
│ │ │ ├── pos-type-badges.spec.tsx
│ │ │ └── pos-type-badges.tsx
│ │ ├── pos-type-router
│ │ │ ├── pos-type-router.spec.tsx
│ │ │ ├── pos-type-router.tsx
│ │ │ ├── selectAppForTypes.spec.ts
│ │ │ └── selectAppForTypes.ts
│ │ └── pos-value
│ │ │ ├── pos-value.spec.tsx
│ │ │ └── pos-value.tsx
│ ├── global.css
│ ├── global.ts
│ ├── index.html
│ ├── index.ts
│ ├── pod-index.html
│ ├── pod-os.ts
│ ├── registerSW.js
│ ├── service-worker-localhost.js
│ ├── store
│ │ ├── session.ts
│ │ ├── settings.spec.ts
│ │ └── settings.ts
│ ├── test
│ │ ├── TestComponent.tsx
│ │ ├── mockPodOS.ts
│ │ ├── mockSessionStore.ts
│ │ ├── pressKey.ts
│ │ └── renderFunctionalComponent.tsx
│ └── window.ts
├── stencil.config.ts
└── tsconfig.json
├── package-lock.json
├── package.json
├── service-worker
├── .gitignore
├── CHANGELOG.md
├── babel.config.json
├── esbuild
│ └── build-bundle.mjs
├── eslint.config.mjs
├── package.json
├── readme.md
├── src
│ ├── index.ts
│ ├── setupServiceWorker.spec.ts
│ └── setupServiceWorker.ts
└── tsconfig.json
└── storybook
├── .storybook
├── main.js
├── preview-head.html
└── preview.js
├── Readme.md
├── package-lock.json
├── package.json
└── stories
├── 10_pos-predicate.stories.mdx
├── 1_pos-resource.stories.mdx
├── 4_pos-literals.stories.mdx
├── 5_pos-relations.stories.mdx
├── 6_pos-reverse-relations.stories.mdx
├── 7_pos-image.stories.mdx
├── 8_pos-picture.stories.mdx
├── 9_pos-type-badges.stories.mdx
├── composition
└── 1_resource-composition.stories.mdx
├── documents
└── 1_pos-document.stories.mdx
├── inputs
├── 1_pos-select-term.stories.mdx
├── 2_pos-add-literal-value.stories.mdx
├── 3_pos-add-new-thing.stories.mdx
├── 4_pos-new-thing-form.stories.mdx
└── 5_pos-dialog.stories.mdx
├── ldp-container
└── 1_pos-container-contents.stories.mdx
├── login
├── 1_pos-login.stories.mdx
└── 2_pos-login-form.stories.mdx
├── navigation
├── 0_pos-rich-link.stories.mdx
├── 1_pos-navigation-bar.stories.mdx
└── 2_pos-make-findable.stories.mdx
├── rdf-document
└── 1_pos-subjects.stories.mdx
└── values
├── 1_pos-value.stories.mdx
├── 2_pos-label.stories.mdx
└── 3_pos-description.stories.mdx
/.github/workflows/manual-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Manual Deployment
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: Which version to deploy
8 |
9 | jobs:
10 |
11 | deploy-pod-os-apps:
12 | runs-on: ubuntu-22.04
13 | defaults:
14 | run:
15 | working-directory: ./apps
16 | env:
17 | POD_OS_ELEMENTS_VERSION: ${{ github.event.inputs.version }}
18 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_POD_OS_BROWSER_SITE_ID }}
19 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
20 | steps:
21 | - uses: actions/checkout@v3
22 | - run: echo release apps based on @pod-os/elements version ${POD_OS_ELEMENTS_VERSION}
23 | - name: install dependencies
24 | run: npm ci
25 | - name: Build PodOS Browser
26 | run: make pos-app-browser
27 | - name: Check netlify version & auth status
28 | run: |
29 | npx netlify-cli --version
30 | npx netlify-cli status
31 | - name: Deploy PodOS Browser
32 | run: npx netlify-cli deploy --dir=dist/pos-app-browser --prod
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | gh-pages
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "proseWrap": "always",
3 | "printWidth": 80
4 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # PodOS contributing guide
2 |
3 | Contributions to PodOS are very welcome. To get started please open an [issue](https://github.com/pod-os/PodOS/issues) or start a [discussion](https://github.com/pod-os/PodOS/discussions) with the ideas you have.
4 |
5 | All contributions eventually need to comply with our [Definition of Done](./docs/contributing/definition-of-done.md) to be considered done. But do not worry to much about it, you will get help with that where needed.
6 |
7 | All contributions have to be made under the same [license](./LICENSE) as PodOS.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 PodOS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/apps/.github/workflows/playwright.yml:
--------------------------------------------------------------------------------
1 | name: Playwright Tests
2 | on:
3 | push:
4 | branches: [ main, master ]
5 | pull_request:
6 | branches: [ main, master ]
7 | jobs:
8 | test:
9 | timeout-minutes: 60
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: 16
16 | - name: Install dependencies
17 | run: npm ci
18 | - name: Install Playwright Browsers
19 | run: npx playwright install --with-deps
20 | - name: Run Playwright tests
21 | run: npx playwright test
22 | - uses: actions/upload-artifact@v3
23 | if: always()
24 | with:
25 | name: playwright-report
26 | path: playwright-report/
27 | retention-days: 30
28 |
--------------------------------------------------------------------------------
/apps/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | /test-results/
4 | /playwright-report/
5 | /playwright/.cache/
6 | test-solid-server/data/.internal/idp/adapter
7 | test-solid-server/data/.internal/locks
8 | test-solid-server/data/.internal/accounts/cookies
9 |
--------------------------------------------------------------------------------
/apps/Makefile:
--------------------------------------------------------------------------------
1 |
2 | APPS = pos-app-browser
3 |
4 | $(APPS):
5 | ./build.sh $@
6 |
7 |
8 |
--------------------------------------------------------------------------------
/apps/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PodOS Browser
7 |
11 |
15 |
16 |
17 |
18 | <${POD_OS_APP_NAME} restore-previous-session="true">${POD_OS_APP_NAME}>
19 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/apps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pod-os/apps",
3 | "version": "1.0.0",
4 | "description": "Apps based on PodOS",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "serve dist/pos-app-browser",
8 | "start:solid-server": "community-solid-server --port 4000 --config @css:config/file.json --rootFilePath ./test-solid-server/data",
9 | "test": "playwright test --config playwright-app.config.ts",
10 | "test:elements": "playwright test --config playwright-elements.config.ts",
11 | "test:debug": "playwright test --config playwright-app.config.ts --debug --project=chromium",
12 | "test:elements:debug": "playwright test --config playwright-elements.config.ts --debug --project=chromium",
13 | "test:ui": "npm run test -- --ui",
14 | "test:elements:ui": "npm run test:elements -- --ui",
15 | "build:latest": "make"
16 | },
17 | "keywords": [],
18 | "author": "",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "@playwright/test": "^1.52.0",
22 | "@solid/community-server": "^7.1.7",
23 | "netlify-cli": "^21.2.1",
24 | "pwa-asset-generator": "^8.0.4",
25 | "serve": "^14.2.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/playwright-app.config.ts:
--------------------------------------------------------------------------------
1 | import config from "./playwright-base.config";
2 |
3 | config.use.baseURL = "http://localhost:3000";
4 |
5 | config.webServer = [
6 | {
7 | command: "npm start",
8 | port: 3000,
9 | },
10 | {
11 | command: "npm run start:solid-server",
12 | port: 4000,
13 | },
14 | ];
15 | export default config;
16 |
--------------------------------------------------------------------------------
/apps/playwright-elements.config.ts:
--------------------------------------------------------------------------------
1 | import config from "./playwright-base.config";
2 |
3 | // assumes a running instance of PodOS elements dev server
4 | config.use.baseURL = "http://localhost:3333";
5 |
6 | config.webServer = [
7 | {
8 | command: "npm run start:solid-server",
9 | port: 4000,
10 | },
11 | ];
12 | export default config;
13 |
--------------------------------------------------------------------------------
/apps/pos-app-browser.manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "/",
3 | "name": "PodOS Browser",
4 | "short_name": "Browser",
5 | "description": "Browsing and managing data stored on Solid pods.",
6 | "start_url": "/",
7 | "theme_color": "#008BF8",
8 | "background_color": "#2D2D2D",
9 | "display": "standalone",
10 | "icons": []
11 | }
12 |
--------------------------------------------------------------------------------
/apps/service-worker.js:
--------------------------------------------------------------------------------
1 | importScripts(
2 | "https://cdn.jsdelivr.net/npm/@pod-os/service-worker@0.1.1/lib/index.js",
3 | );
4 |
5 | const CACHE_NAME = "pod-os-static-${POD_OS_ELEMENTS_VERSION}";
6 | const CDN_URL_PATTERN = "https://cdn.jsdelivr.net/npm/@pod-os/";
7 |
8 | PodOsServiceWorker.setupServiceWorker(self, CACHE_NAME, CDN_URL_PATTERN, [
9 | "/",
10 | "./index.html",
11 | ]);
12 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/data/a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/data/a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe","payload":{"linkedLoginsCount":1,"id":"a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe","**password**":{"9998b9b2-b899-41b2-8e46-70d49671adcd":{"email":"alice@mail.example","password":"$2a$10$SrSavUrqMJbjzsgxDYJBRu/EwDoFul12bzi/FrFtzsFfz.HUBD1uq","verified":true,"accountId":"a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe","id":"9998b9b2-b899-41b2-8e46-70d49671adcd"}},"**clientCredentials**":{},"**pod**":{"53059874-1474-4c58-8e0d-db041582666b":{"baseUrl":"http://localhost:4000/alice/","accountId":"a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe","id":"53059874-1474-4c58-8e0d-db041582666b","**owner**":{"7cd9ce45-8ea7-4dd6-9fa7-a3998462c827":{"webId":"http://localhost:4000/alice/profile/card#me","podId":"53059874-1474-4c58-8e0d-db041582666b","visible":false,"id":"7cd9ce45-8ea7-4dd6-9fa7-a3998462c827"}}}},"**webIdLink**":{"aaefa71e-51a8-494d-bbbe-6e6020cbf6db":{"webId":"http://localhost:4000/alice/profile/card#me","accountId":"a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe","id":"aaefa71e-51a8-494d-bbbe-6e6020cbf6db"}},"rememberLogin":true}}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/owner/7cd9ce45-8ea7-4dd6-9fa7-a3998462c827$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/owner/7cd9ce45-8ea7-4dd6-9fa7-a3998462c827","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/password/9998b9b2-b899-41b2-8e46-70d49671adcd$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/9998b9b2-b899-41b2-8e46-70d49671adcd","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/password/email/alice@mail.example$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/email/alice%40mail.example","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/pod/53059874-1474-4c58-8e0d-db041582666b$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/53059874-1474-4c58-8e0d-db041582666b","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A4000%2Falice%2F$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A4000%2Falice%2F","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/webIdLink/aaefa71e-51a8-494d-bbbe-6e6020cbf6db$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/aaefa71e-51a8-494d-bbbe-6e6020cbf6db","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A4000%2Falice%2Fprofile%2Fcard#me$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A4000%2Falice%2Fprofile%2Fcard%23me","payload":["a0b5cd9e-0ec9-4c68-8fac-8a1fb9f83dfe"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/idp/keys/cookie-secret$.json:
--------------------------------------------------------------------------------
1 | {"key":"idp/keys/cookie-secret","payload":["044db519bbf926da002021994068ddca86524fcb63ba04c66a7f92e032ec023377a1f679d960cde22b96129b734b38dcc623168109ef126b23d26ce18901de0e"]}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/idp/keys/jwks$.json:
--------------------------------------------------------------------------------
1 | {"key":"idp/keys/jwks","payload":{"keys":[{"kty":"EC","x":"Qb8QZIqWHS47-4DrkhcDmYfQIW-tGkHMYe6QQC-HWBM","y":"_tgMRQoKkP9Pl6VfiGWT9yMGtdyOUJ7jA1fuujp15Pw","crv":"P-256","d":"lIpzOUIQ67AO31sLcRC2VpnmqKhIkKbHR8LLvvYF61Y","alg":"ES256"}]}}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/setup/current-base-url$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/current-base-url","payload":"http://localhost:4000/"}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/setup/current-server-version$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/current-server-version","payload":"7.1.7"}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/setup/setupCompleted-2.0$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/setupCompleted-2.0","payload":true}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/.internal/setup/v6-migration$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/v6-migration","payload":true}
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/.acl:
--------------------------------------------------------------------------------
1 | # Root ACL resource for the agent account
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The homepage is readable by the public
6 | <#public>
7 | a acl:Authorization;
8 | acl:agentClass foaf:Agent;
9 | acl:accessTo <./>;
10 | acl:mode acl:Read.
11 |
12 | # The owner has full access to every resource in their pod.
13 | # Other agents have no access rights,
14 | # unless specifically authorized in other .acl resources.
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | # Optional owner email, to be used for account recovery:
19 | acl:agent ;
20 | # Set the access to the root storage folder itself
21 | acl:accessTo <./>;
22 | # All resources will inherit this authorization, by default
23 | acl:default <./>;
24 | # The owner has all of the access modes allowed
25 | acl:mode
26 | acl:Read, acl:Write, acl:Control.
27 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/.meta:
--------------------------------------------------------------------------------
1 | a .
2 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/container/.acl:
--------------------------------------------------------------------------------
1 | # Root ACL resource for the agent account
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | <#public>
6 | a acl:Authorization;
7 | acl:agentClass foaf:Agent;
8 | acl:accessTo <./>;
9 | acl:default <./> ;
10 | acl:mode acl:Read.
11 |
12 | <#owner>
13 | a acl:Authorization;
14 | acl:agent ;
15 | acl:accessTo <./>;
16 | acl:default <./>;
17 | acl:mode
18 | acl:Read, acl:Write, acl:Control.
19 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/container/another-sub-container/.keep-me:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/apps/test-solid-server/data/alice/container/another-sub-container/.keep-me
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/container/readme.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/apps/test-solid-server/data/alice/container/readme.md
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/container/resource$.ttl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/apps/test-solid-server/data/alice/container/resource$.ttl
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/container/sub-container/.keep-me:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/apps/test-solid-server/data/alice/container/sub-container/.keep-me
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/make-findable/banana$.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#> .
2 | @prefix schema: .
3 |
4 | :it
5 | schema:name "Banana" ;
6 | .
7 |
8 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/profile/card$.ttl:
--------------------------------------------------------------------------------
1 | @prefix foaf: .
2 | @prefix solid: .
3 | @prefix pim: .
4 |
5 | <>
6 | a foaf:PersonalProfileDocument ;
7 | foaf:maker ;
8 | foaf:primaryTopic .
9 |
10 |
11 | a foaf:Person ;
12 | foaf:name "Alice" ;
13 | solid:oidcIssuer ;
14 | pim:preferencesFile ;
15 | .
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/profile/card.acl:
--------------------------------------------------------------------------------
1 | # ACL resource for the WebID profile document
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The WebID profile is readable by the public.
6 | # This is required for discovery and verification,
7 | # e.g. when checking identity providers.
8 | <#public>
9 | a acl:Authorization;
10 | acl:agentClass foaf:Agent;
11 | acl:accessTo <./card>;
12 | acl:mode acl:Read.
13 |
14 | # The owner has full access to the profile
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | acl:accessTo <./card>;
19 | acl:mode acl:Read, acl:Write, acl:Control.
20 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/public/.acl:
--------------------------------------------------------------------------------
1 | # Root ACL resource for the agent account
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | <#public>
6 | a acl:Authorization;
7 | acl:agentClass foaf:Agent;
8 | acl:accessTo <./>;
9 | acl:default <./> ;
10 | acl:mode acl:Read.
11 |
12 | <#owner>
13 | a acl:Authorization;
14 | acl:agent ;
15 | acl:accessTo <./>;
16 | acl:default <./>;
17 | acl:mode
18 | acl:Read, acl:Write, acl:Control.
19 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/public/generic/resource$.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#> .
2 | @prefix schema: .
3 |
4 | :it
5 | a schema:Thing ;
6 | schema:name "Something" ;
7 | schema:image ;
8 | schema:description "A very generic item" ;
9 | schema:parentItem :parent
10 | .
11 |
12 | :parent
13 | a schema:Thing ;
14 | schema:name "Related Thing" ;
15 | schema:description "This is somehow related to the other thing" ;
16 | .
17 |
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/public/generic/thing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/apps/test-solid-server/data/alice/public/generic/thing.png
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/settings/prefs.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#>.
2 | @prefix solid: .
3 | @prefix c: .
4 |
5 |
6 | c:me
7 | solid:privateLabelIndex ;
8 | .
--------------------------------------------------------------------------------
/apps/test-solid-server/data/alice/settings/privateLabelIndex.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#> .
2 | @prefix rdfs: .
3 |
4 |
5 | rdfs:label "Alice" .
6 |
7 |
8 | rdfs:label "Readme" .
9 |
10 |
11 | rdfs:label "Something" .
12 |
--------------------------------------------------------------------------------
/apps/tests/fixtures/credentials.ts:
--------------------------------------------------------------------------------
1 | import { Credentials } from "../actions/signIn";
2 |
3 | export const alice: Credentials = {
4 | name: "Alice",
5 | email: "alice@mail.example",
6 | password: "alice",
7 | };
8 |
--------------------------------------------------------------------------------
/apps/tests/generic.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | test("show generic information about unknown types of things", async ({
4 | page,
5 | }) => {
6 | // when opening PodOS Browser
7 | await page.goto("/");
8 |
9 | // and navigating to a generic resource
10 | const navigationBar = page.getByPlaceholder("Enter URI");
11 | await navigationBar.fill(
12 | "http://localhost:4000/alice/public/generic/resource#it",
13 | );
14 | await navigationBar.press("Enter");
15 |
16 | // then page shows a heading with the resource name
17 | const heading = page.getByRole("heading");
18 | await expect(heading).toHaveText("Something");
19 |
20 | // and it shows the description of the resource
21 | const overview = page.getByRole("article", { name: "Something" });
22 | await expect(overview).toHaveText(/A very generic item/);
23 |
24 | // and the type of the resource
25 | await expect(overview).toHaveText(/Thing/);
26 |
27 | // and the image
28 | const image = overview.getByAltText("Something");
29 | await expect(image).toHaveAttribute("src", /blob:/);
30 | });
31 |
--------------------------------------------------------------------------------
/apps/tests/person.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | test("show name as heading for a person", async ({ page }) => {
4 | // when opening PodOS Browser
5 | await page.goto("/");
6 |
7 | // and navigating to Alice's WebID
8 | const navigationBar = page.getByPlaceholder("Enter URI");
9 | await navigationBar.fill("http://localhost:4000/alice/profile/card#me");
10 | await navigationBar.press("Enter");
11 |
12 | // then the heading shows Alice's name as heading
13 | const label = await page.getByRole("heading");
14 | await expect(label).toHaveText("Alice");
15 | });
16 |
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/assets/logo.png
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/contacts/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/contacts/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | www/
3 | loader/
4 |
5 | *~
6 | *.sw[mnpcod]
7 | *.log
8 | *.lock
9 | *.tmp
10 | *.tmp.*
11 | log.txt
12 | *.sublime-project
13 | *.sublime-workspace
14 |
15 | .stencil/
16 | .idea/
17 | .vscode/
18 | .sass-cache/
19 | .versions/
20 | node_modules/
21 | $RECYCLE.BIN/
22 |
23 | .DS_Store
24 | Thumbs.db
25 | UserInterfaceState.xcuserstate
26 | .env
27 |
--------------------------------------------------------------------------------
/contacts/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "jsxBracketSameLine": false,
5 | "jsxSingleQuote": false,
6 | "quoteProps": "consistent",
7 | "printWidth": 180,
8 | "semi": true,
9 | "singleQuote": true,
10 | "tabWidth": 2,
11 | "trailingComma": "all",
12 | "useTabs": false
13 | }
14 |
--------------------------------------------------------------------------------
/contacts/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/contacts/jest-setup.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/contacts/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@stencil/core/testing',
3 | setupFilesAfterEnv: ['/jest-setup.ts'],
4 | };
5 |
--------------------------------------------------------------------------------
/contacts/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # PodOS contacts
7 |
8 | A contacts manager for Solid based on PodOS
9 |
10 | Live Version: https://contacts.pod-os.org
--------------------------------------------------------------------------------
/contacts/src/components/app.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h } from '@stencil/core';
2 |
3 | import '@pod-os/elements';
4 |
5 | @Component({
6 | tag: 'pos-contacts-app',
7 | })
8 | export class App {
9 | render() {
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/email-addresses/email-addresses.css:
--------------------------------------------------------------------------------
1 | ul {
2 | list-style-type: none;
3 | display: flex;
4 | flex-direction: column;
5 | gap: var(--size-4);
6 | margin: 0;
7 | }
8 |
9 | section {
10 | display: flex;
11 | justify-content: flex-start;
12 | align-items: flex-start;
13 | }
14 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/email-addresses/email-addresses.tsx:
--------------------------------------------------------------------------------
1 | import { Email } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, h, Prop } from '@stencil/core';
3 |
4 | @Component({
5 | tag: 'pos-contacts-email-addresses',
6 | styleUrl: './email-addresses.css',
7 | shadow: true,
8 | })
9 | export class EmailAddresses {
10 | @Prop()
11 | emailAddresses!: Email[];
12 | render() {
13 | if (this.emailAddresses.length == 0) {
14 | return null;
15 | }
16 | return (
17 |
18 |
19 |
20 | {this.emailAddresses.map(email => (
21 | -
22 | {email.value}
23 |
24 | ))}
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/email-addresses/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-email-addresses
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ----------------------------- | --------- | ----------- | --------- | ----------- |
12 | | `emailAddresses` _(required)_ | -- | | `Email[]` | `undefined` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-contacts-contact-details](..)
20 |
21 | ### Depends on
22 |
23 | - ion-icon
24 |
25 | ### Graph
26 | ```mermaid
27 | graph TD;
28 | pos-contacts-email-addresses --> ion-icon
29 | pos-contacts-contact-details --> pos-contacts-email-addresses
30 | style pos-contacts-email-addresses fill:#f9f,stroke:#333,stroke-width:4px
31 | ```
32 |
33 | ----------------------------------------------
34 |
35 | *Built with [StencilJS](https://stenciljs.com/)*
36 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/group-details/group-details.tsx:
--------------------------------------------------------------------------------
1 | import { ContactsModule } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, h, Host, Prop } from '@stencil/core';
3 |
4 | @Component({
5 | tag: 'pos-contacts-group-details',
6 | })
7 | export class GroupDetails {
8 | @Prop()
9 | uri!: string;
10 |
11 | @Prop()
12 | contactsModule!: ContactsModule;
13 | render() {
14 | return {this.uri};
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/group-details/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-group-details
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ----------------------------- | --------- | ----------- | ---------------- | ----------- |
12 | | `contactsModule` _(required)_ | -- | | `ContactsModule` | `undefined` |
13 | | `uri` _(required)_ | `uri` | | `string` | `undefined` |
14 |
15 |
16 | ## Dependencies
17 |
18 | ### Used by
19 |
20 | - [pos-contacts-address-book-page](../../address-book-page)
21 |
22 | ### Graph
23 | ```mermaid
24 | graph TD;
25 | pos-contacts-address-book-page --> pos-contacts-group-details
26 | style pos-contacts-group-details fill:#f9f,stroke:#333,stroke-width:4px
27 | ```
28 |
29 | ----------------------------------------------
30 |
31 | *Built with [StencilJS](https://stenciljs.com/)*
32 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/phone-numbers/phone-numbers.css:
--------------------------------------------------------------------------------
1 | ul {
2 | list-style-type: none;
3 | display: flex;
4 | flex-direction: column;
5 | gap: var(--size-4);
6 | margin: 0;
7 | }
8 |
9 | section {
10 | display: flex;
11 | justify-content: flex-start;
12 | align-items: flex-start;
13 | }
14 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/phone-numbers/phone-numbers.tsx:
--------------------------------------------------------------------------------
1 | import { PhoneNumber } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, h, Prop } from '@stencil/core';
3 |
4 | @Component({
5 | tag: 'pos-contacts-phone-numbers',
6 | styleUrl: './phone-numbers.css',
7 | shadow: true,
8 | })
9 | export class PhoneNumbers {
10 | @Prop()
11 | phoneNumbers!: PhoneNumber[];
12 |
13 | render() {
14 | if (this.phoneNumbers.length == 0) {
15 | return null;
16 | }
17 | return (
18 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-details/phone-numbers/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-phone-numbers
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | --------------------------- | --------- | ----------- | --------------- | ----------- |
12 | | `phoneNumbers` _(required)_ | -- | | `PhoneNumber[]` | `undefined` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-contacts-contact-details](..)
20 |
21 | ### Depends on
22 |
23 | - ion-icon
24 |
25 | ### Graph
26 | ```mermaid
27 | graph TD;
28 | pos-contacts-phone-numbers --> ion-icon
29 | pos-contacts-contact-details --> pos-contacts-phone-numbers
30 | style pos-contacts-phone-numbers fill:#f9f,stroke:#333,stroke-width:4px
31 | ```
32 |
33 | ----------------------------------------------
34 |
35 | *Built with [StencilJS](https://stenciljs.com/)*
36 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-list/contact-list.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --text-color: var(--color-grey-800);
3 | --hover-color: var(--color-grey-200);
4 | }
5 |
6 | ul {
7 | list-style-type: none;
8 | padding: 0;
9 | margin: 0;
10 | }
11 |
12 | li {
13 | display: flex;
14 | border-bottom: 1px solid var(--color-grey-100);
15 | }
16 |
17 | li:hover {
18 | background-color: var(--hover-color);
19 | }
20 |
21 | a {
22 | color: var(--text-color);
23 | text-decoration: none;
24 | }
25 |
26 | li a {
27 | width: 100%;
28 | padding: var(--size-4) var(--size-10);
29 | }
30 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-list/contact-list.tsx:
--------------------------------------------------------------------------------
1 | import { Contact } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, Event, EventEmitter, h, Prop } from '@stencil/core';
3 |
4 | @Component({
5 | tag: 'pos-contacts-contact-list',
6 | styleUrl: './contact-list.css',
7 | shadow: true,
8 | })
9 | export class ContactList {
10 | @Prop()
11 | contacts!: Contact[];
12 |
13 | @Event({ eventName: 'pod-os-contacts:contact-selected' }) contactSelected: EventEmitter;
14 |
15 | render() {
16 | return (
17 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/contacts/src/components/contact-list/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-contact-list
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ----------------------- | --------- | ----------- | ----------- | ----------- |
12 | | `contacts` _(required)_ | -- | | `Contact[]` | `undefined` |
13 |
14 |
15 | ## Events
16 |
17 | | Event | Description | Type |
18 | | ---------------------------------- | ----------- | ---------------------- |
19 | | `pod-os-contacts:contact-selected` | | `CustomEvent` |
20 |
21 |
22 | ## Dependencies
23 |
24 | ### Used by
25 |
26 | - [pos-contacts-address-book-page](../address-book-page)
27 |
28 | ### Graph
29 | ```mermaid
30 | graph TD;
31 | pos-contacts-address-book-page --> pos-contacts-contact-list
32 | style pos-contacts-contact-list fill:#f9f,stroke:#333,stroke-width:4px
33 | ```
34 |
35 | ----------------------------------------------
36 |
37 | *Built with [StencilJS](https://stenciljs.com/)*
38 |
--------------------------------------------------------------------------------
/contacts/src/components/create-new-contact-form/create-new-contact-form.css:
--------------------------------------------------------------------------------
1 | form {
2 | display: grid;
3 | grid-template-columns: var(--size-16) 1fr;
4 | grid-gap: var(--scale-0);
5 | }
6 |
7 |
8 | label {
9 | grid-column: 1 / 2;
10 | display: flex;
11 | align-items: center;
12 | justify-content: flex-end;
13 | }
14 |
15 | input {
16 | grid-column: 2 / 3;
17 | outline: var(--pos-input-outline);
18 | padding: var(--scale-000);
19 | border: none;
20 | border-radius: var(--radius-xs);
21 | width: var(--size-full);
22 | box-sizing: border-box;
23 | }
24 |
25 | input:focus-within {
26 | outline: var(--pos-input-focus-outline);
27 | }
28 |
29 | input#create {
30 | outline: none;
31 | box-shadow: var(--shadow-sm);
32 | cursor: pointer;
33 | color: var(--pos-primary-text-color);
34 | background-color: var(--pos-primary-color);
35 | }
36 |
37 | input#create:disabled {
38 | cursor: default;
39 | color: var(--pos-disabled-text-color);
40 | background-color: var(--pos-disabled-color);
41 | box-shadow: none
42 | }
43 |
44 | input#create:hover:not(:disabled), input#create:focus {
45 | filter: brightness(110%);
46 | box-shadow: var(--shadow-md);
47 | }
48 |
--------------------------------------------------------------------------------
/contacts/src/components/create-new-contact/create-new-contact.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | justify-content: flex-end;
4 | padding: var(--size-4);
5 | }
6 |
7 | button.create {
8 | display: flex;
9 | gap: var(--size-1);
10 | padding: var(--size-4);
11 | font-weight: var(--weight-bold);
12 | letter-spacing: var(--letter-xl);
13 | background-color: var(--pos-primary-color);
14 | color: var(--pos-primary-text-color);
15 | border-radius: var(--radius-xs);
16 | outline: none;
17 | border: none;
18 | box-shadow: var(--shadow-sm);
19 | cursor: pointer;
20 | }
21 |
22 | button.create:focus-within {
23 | outline: var(--pos-input-focus-outline);
24 | }
25 |
26 | button.create:focus-within, button.create:focus, button.create:hover{
27 | filter: brightness(110%);
28 | box-shadow: var(--shadow-md);
29 | }
30 |
--------------------------------------------------------------------------------
/contacts/src/components/create-new-contact/create-new-contact.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host, Prop } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-contacts-create-new-contact',
5 | styleUrl: 'create-new-contact.css',
6 | shadow: true,
7 | })
8 | export class CreateNewContact {
9 | private dialog: HTMLPosDialogElement;
10 |
11 | @Prop()
12 | addressBookUri!: string;
13 |
14 | render() {
15 | return (
16 |
17 |
21 | (this.dialog = el as HTMLPosDialogElement)}>
22 | Create new contact
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/contacts/src/components/group-list/group-list.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --text-color: var(--color-grey-800);
3 | --hover-color: rgb(230 237 250 / 100%);
4 | }
5 |
6 | ul {
7 | display: flex;
8 | flex-direction: column;
9 | gap: var(--size-1);
10 | padding: 0;
11 | margin: 0;
12 | list-style-type: none;
13 | }
14 |
15 | li {
16 | transition: all 300ms var(--ease-in-out-sine);
17 | display: flex;
18 | margin-left: var(--size-4);
19 | margin-right: var(--size-4);
20 | max-height: 50%;
21 | border-radius: var(--radius-full);
22 | }
23 |
24 | li a {
25 | width: 100%;
26 | padding: var(--size-4) var(--size-10);
27 | }
28 |
29 | li:hover {
30 | background: var(--hover-color)
31 | }
32 |
33 | a {
34 | color: var(--text-color);
35 | text-decoration: none;
36 | }
37 |
38 | a:visited {
39 | color: var(--text-color);
40 | }
41 |
--------------------------------------------------------------------------------
/contacts/src/components/group-list/group-list.tsx:
--------------------------------------------------------------------------------
1 | import { Group } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, Event, EventEmitter, h, Prop } from '@stencil/core';
3 |
4 | @Component({
5 | tag: 'pos-contacts-group-list',
6 | styleUrl: './group-list.css',
7 | shadow: true,
8 | })
9 | export class GroupList {
10 | @Prop()
11 | groups: Group[];
12 |
13 | @Event({ eventName: 'pod-os-contacts:group-selected' }) groupSelected: EventEmitter;
14 |
15 | render() {
16 | return (
17 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/contacts/src/components/group-list/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-group-list
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | -------- | --------- | ----------- | --------- | ----------- |
12 | | `groups` | -- | | `Group[]` | `undefined` |
13 |
14 |
15 | ## Events
16 |
17 | | Event | Description | Type |
18 | | -------------------------------- | ----------- | -------------------- |
19 | | `pod-os-contacts:group-selected` | | `CustomEvent` |
20 |
21 |
22 | ## Dependencies
23 |
24 | ### Used by
25 |
26 | - [pos-contacts-address-book-page](../address-book-page)
27 |
28 | ### Graph
29 | ```mermaid
30 | graph TD;
31 | pos-contacts-address-book-page --> pos-contacts-group-list
32 | style pos-contacts-group-list fill:#f9f,stroke:#333,stroke-width:4px
33 | ```
34 |
35 | ----------------------------------------------
36 |
37 | *Built with [StencilJS](https://stenciljs.com/)*
38 |
--------------------------------------------------------------------------------
/contacts/src/components/group.tsx:
--------------------------------------------------------------------------------
1 | import { ContactsModule, FullGroup } from '@solid-data-modules/contacts-rdflib';
2 | import { Component, h, Host, Prop, State, Watch } from '@stencil/core';
3 | import { href } from 'stencil-router-v2';
4 | @Component({
5 | tag: 'pos-contacts-group',
6 | })
7 | export class Group {
8 | @Prop()
9 | uri: string;
10 |
11 | @Prop()
12 | contactsModule: ContactsModule;
13 |
14 | @State()
15 | group: FullGroup;
16 |
17 | componentWillLoad() {
18 | this.loadGroup();
19 | }
20 |
21 | @Watch('uri')
22 | async loadGroup() {
23 | this.group = await this.contactsModule.readGroup(this.uri);
24 | }
25 |
26 | render() {
27 | return (
28 |
29 | {this.group.name}
30 |
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/contacts/src/components/list-address-books/list-address-books.css:
--------------------------------------------------------------------------------
1 | ul {
2 | margin: 0;
3 | padding: 0;
4 | list-style-type: none;
5 | display: flex;
6 | flex-direction: column;
7 | gap: var(--size-3);
8 | }
9 |
10 | a {
11 | font-size: var(--scale-1);
12 | border-radius: var(--radius-xs);
13 | padding: var(--size-4);
14 | background-color: white;
15 | color: var(--pos-primary-color);
16 | border: 1px solid var(--color-blue-700);
17 | display: flex;
18 | align-items: center;
19 | gap: var(--size-2);
20 | text-decoration: none;
21 | }
22 |
23 | a:hover {
24 | background-color: var(--color-grey-50);
25 | filter: brightness(90%);
26 | }
27 |
28 | .label {
29 | display: flex;
30 | gap: var(--size-2);
31 | align-items: center;
32 | }
33 |
--------------------------------------------------------------------------------
/contacts/src/components/loading-spinner/loading-spinner.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --spinner-size: var(--size-10);
3 | width: var(--spinner-size);
4 | height: var(--spinner-size);
5 | fill: var(--color-blue-700);
6 | opacity: 50%;
7 | animation-name: spin;
8 | animation-duration: 800ms;
9 | animation-iteration-count: infinite;
10 | animation-timing-function: linear;
11 | }
12 |
13 |
14 | @keyframes spin {
15 | from {
16 | transform: rotate(0deg);
17 | }
18 | to {
19 | transform: rotate(360deg);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/contacts/src/components/loading-spinner/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-loading-spinner
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | -------- | --------- | ----------- | -------- | ------- |
12 | | `defer` | `defer` | | `number` | `300` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-contacts-address-book-page](../address-book-page)
20 | - [pos-contacts-contact-details](../contact-details)
21 |
22 | ### Graph
23 | ```mermaid
24 | graph TD;
25 | pos-contacts-address-book-page --> pos-contacts-loading-spinner
26 | pos-contacts-contact-details --> pos-contacts-loading-spinner
27 | style pos-contacts-loading-spinner fill:#f9f,stroke:#333,stroke-width:4px
28 | ```
29 |
30 | ----------------------------------------------
31 |
32 | *Built with [StencilJS](https://stenciljs.com/)*
33 |
--------------------------------------------------------------------------------
/contacts/src/components/open-address-book/open-address-book.css:
--------------------------------------------------------------------------------
1 | #container {
2 | display: flex;
3 | flex-direction: column;
4 | gap: var(--size-5);
5 | align-items: flex-start;
6 | }
7 |
8 | #sign-in {
9 | display: flex;
10 | gap: var(--size-1);
11 | align-items: center;
12 | color: var(--color-grey-800);
13 | font-weight: var(--weight-light);
14 | }
15 |
16 | button.open {
17 | white-space: nowrap;
18 | font-size: var(--scale-1);
19 | border-radius: var(--radius-xs);
20 | padding: var(--size-2);
21 | background-color: white;
22 | color: var(--pos-primary-color);
23 | border: 1px solid var(--color-blue-700);
24 | display: flex;
25 | align-items: center;
26 | gap: var(--size-2);
27 | }
28 |
29 |
30 | button.open:hover {
31 | background-color: var(--color-grey-50);
32 | filter: brightness(90%);
33 | }
34 |
--------------------------------------------------------------------------------
/contacts/src/components/readme.md:
--------------------------------------------------------------------------------
1 | # pos-contacts-group
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ---------------- | --------- | ----------- | ---------------- | ----------- |
12 | | `contactsModule` | -- | | `ContactsModule` | `undefined` |
13 | | `uri` | `uri` | | `string` | `undefined` |
14 |
15 |
16 | ## Dependencies
17 |
18 | ### Used by
19 |
20 | - [pos-contacts-router](router)
21 |
22 | ### Graph
23 | ```mermaid
24 | graph TD;
25 | pos-contacts-router --> pos-contacts-group
26 | style pos-contacts-group fill:#f9f,stroke:#333,stroke-width:4px
27 | ```
28 |
29 | ----------------------------------------------
30 |
31 | *Built with [StencilJS](https://stenciljs.com/)*
32 |
--------------------------------------------------------------------------------
/contacts/src/components/welcome-page/test/welcome-page.spec.tsx:
--------------------------------------------------------------------------------
1 | import { h } from '@stencil/core';
2 | import { newSpecPage } from '@stencil/core/testing';
3 | import { WelcomePage } from '../welcome-page';
4 |
5 | describe('welcome page', () => {
6 | let page;
7 | beforeEach(async () => {
8 | page = await newSpecPage({
9 | components: [WelcomePage],
10 | template: () => ,
11 | supportsShadowDom: false,
12 | });
13 | });
14 |
15 | it('allows to sign in and open address book before login', () => {
16 | expect(page.root).toEqualHtml(`
17 |
18 |
19 |
20 | PodOS contacts
21 |
22 |
23 |
24 |
25 |
26 |
27 | `);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/contacts/src/components/welcome-page/welcome-page.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: grid;
3 | grid-template-columns: auto;
4 | grid-template-areas:
5 | "header"
6 | "main"
7 | }
8 |
9 | header {
10 | display: flex;
11 | flex-direction: row;
12 | grid-area: header;
13 | align-items: center;
14 | justify-content: space-between;
15 | padding: var(--size-10);
16 | }
17 |
18 | main {
19 | grid-area: main;
20 | margin: var(--size-10);
21 | background-color: white;
22 | border-radius: var(--radius-xl);
23 | padding: var(--size-4)
24 | }
25 |
26 | .toolbar {
27 | display: flex;
28 | justify-items: center;
29 | align-content: center;
30 | gap: var(--size-2);
31 | height: var(--size-10);
32 | }
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/contacts/src/components/welcome-page/welcome-page.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-contacts-welcome-page',
5 | styleUrl: './welcome-page.css',
6 | shadow: true,
7 | })
8 | export class WelcomePage {
9 | render() {
10 | return (
11 |
12 |
13 | PodOS contacts
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/contacts/src/events/PodOsModuleAware.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from '@stencil/core';
2 |
3 | export type PodOsModuleReceiver = (module: T) => void;
4 | interface PodOsModuleEvent {
5 | module: string;
6 | receiver: PodOsModuleReceiver;
7 | }
8 | export type PodOsModuleEventEmitter = EventEmitter>;
9 |
10 | export interface PodOsModuleAware {
11 | componentWillLoad(): void | Promise;
12 | subscribeModule: PodOsModuleEventEmitter;
13 | receiveModule: PodOsModuleReceiver;
14 | }
15 |
16 | export function subscribePodOsModule(module: string, component: PodOsModuleAware) {
17 | component.subscribeModule.emit({ module, receiver: component.receiveModule });
18 | }
19 |
--------------------------------------------------------------------------------
/contacts/src/events/usePodOS.ts:
--------------------------------------------------------------------------------
1 | import { PodOS } from '@pod-os/core';
2 |
3 | export function usePodOS(el: HTMLElement): Promise {
4 | return new Promise(resolve => {
5 | el.dispatchEvent(
6 | new CustomEvent('pod-os:init', {
7 | bubbles: true,
8 | composed: true,
9 | cancelable: true,
10 | detail: (os: PodOS) => {
11 | resolve(os);
12 | },
13 | }),
14 | );
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/contacts/src/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/contacts/src/favicon-32x32.png
--------------------------------------------------------------------------------
/contacts/src/global.css:
--------------------------------------------------------------------------------
1 | @import '~pollen-css/dist/pollen.css';
2 |
3 | @import '~@pod-os/elements/dist/elements/elements.css';
4 |
5 | body {
6 | font-family: var(--font-sans);
7 | background-color: var(--color-grey-50);
8 | margin: 0;
9 | height: 100vh;
10 | }
11 |
--------------------------------------------------------------------------------
/contacts/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PodOS Contacts
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/contacts/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components';
2 |
--------------------------------------------------------------------------------
/contacts/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "/",
3 | "name": "PodOS Contacts",
4 | "short_name": "Contacts",
5 | "description": "Manage contacts stored on your Solid pod.",
6 | "start_url": "/",
7 | "theme_color": "#008BF8",
8 | "background_color": "#fff",
9 | "display": "standalone",
10 | "icons": []
11 | }
12 |
--------------------------------------------------------------------------------
/contacts/src/netlify/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/contacts/src/utils/debounceTime.ts:
--------------------------------------------------------------------------------
1 | export { debounceTime } from 'rxjs';
2 |
--------------------------------------------------------------------------------
/contacts/stencil.config.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@stencil/core';
2 |
3 | export const config: Config = {
4 | namespace: 'contacts',
5 | globalStyle: 'src/global.css',
6 | outputTargets: [
7 | {
8 | type: 'dist',
9 | esmLoaderPath: '../loader',
10 | },
11 | {
12 | type: 'dist-custom-elements',
13 | },
14 | {
15 | type: 'docs-readme',
16 | },
17 | {
18 | type: 'www',
19 | copy: [
20 | {
21 | src: 'manifest.json',
22 | },
23 | {
24 | src: 'favicon-32x32.png',
25 | },
26 | {
27 | src: 'netlify',
28 | dest: '',
29 | },
30 | ],
31 | serviceWorker: null, // disable service workers
32 | },
33 | ],
34 | testing: {
35 | browserHeadless: 'new',
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/contacts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "types": ["jest", "@testing-library/jest-dom"],
4 | "allowSyntheticDefaultImports": true,
5 | "allowUnreachableCode": false,
6 | "declaration": false,
7 | "experimentalDecorators": true,
8 | "lib": ["dom", "es2017"],
9 | "moduleResolution": "node",
10 | "module": "esnext",
11 | "target": "es2017",
12 | "noUnusedLocals": true,
13 | "noUnusedParameters": true,
14 | "jsx": "react",
15 | "jsxFactory": "h",
16 | "skipLibCheck": true, // https://github.com/ionic-team/stencil-ds-output-targets/issues/261
17 | },
18 | "include": ["src"],
19 | "exclude": ["node_modules", "src/test/**/*"],
20 | }
21 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | dist
3 | types
4 | node_modules
5 |
--------------------------------------------------------------------------------
/core/Readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # PodOS Core
7 |
8 | Core logic of PodOS
9 |
--------------------------------------------------------------------------------
/core/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/core/esbuild/build-bundle.mjs:
--------------------------------------------------------------------------------
1 | import { build } from "esbuild";
2 | await build({
3 | logLevel: "info",
4 | entryPoints: ["src/index.ts"],
5 | outfile: "lib/index.js",
6 | bundle: true,
7 | target: "esnext",
8 | globalName: "PodOS",
9 | });
10 |
--------------------------------------------------------------------------------
/core/esbuild/build-esm.mjs:
--------------------------------------------------------------------------------
1 | import * as esbuild from "esbuild";
2 |
3 | import { config } from "./esm-config.mjs";
4 |
5 | await esbuild.build(config);
6 |
--------------------------------------------------------------------------------
/core/esbuild/esm-config.mjs:
--------------------------------------------------------------------------------
1 | export const config = {
2 | logLevel: "info",
3 | entryPoints: ["src/index.ts"],
4 | outdir: "dist",
5 | bundle: true,
6 | splitting: true,
7 | format: "esm",
8 | };
9 |
--------------------------------------------------------------------------------
/core/esbuild/watch-esm.mjs:
--------------------------------------------------------------------------------
1 | import * as esbuild from "esbuild";
2 |
3 | import { config } from "./esm-config.mjs";
4 |
5 | const context = await esbuild.context(config);
6 | await context.watch();
7 |
--------------------------------------------------------------------------------
/core/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import eslint from "@eslint/js";
4 | import tseslint from "typescript-eslint";
5 |
6 | export default tseslint.config({
7 | languageOptions: {
8 | ecmaVersion: 2022,
9 | sourceType: "module",
10 | },
11 | extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
12 | });
13 |
--------------------------------------------------------------------------------
/core/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @returns {Promise} */
2 | module.exports = async () => {
3 | return {
4 | transformIgnorePatterns: ["/node_modules/(?!@solid-data-modules/)"],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/core/src/PodOs.spec.ts:
--------------------------------------------------------------------------------
1 | import { BrowserSession, OfflineCache, PodOS } from "./index";
2 | import { of } from "rxjs";
3 |
4 | describe("PodOS", () => {
5 | let mockSession: BrowserSession;
6 |
7 | beforeEach(() => {
8 | mockSession = {
9 | logout: jest.fn(),
10 | observeSession: jest.fn().mockReturnValue(of()),
11 | } as unknown as BrowserSession;
12 | });
13 |
14 | describe("logout", () => {
15 | it("calls logout on the browser session", async () => {
16 | const podOs = new PodOS({ session: mockSession });
17 |
18 | await podOs.logout();
19 |
20 | expect(mockSession.logout).toHaveBeenCalled();
21 | });
22 |
23 | it("clears the cache", async () => {
24 | const mockOfflineCache = { clear: jest.fn() } as unknown as OfflineCache;
25 | const podOs = new PodOS({
26 | session: mockSession,
27 | offlineCache: mockOfflineCache,
28 | });
29 |
30 | await podOs.logout();
31 |
32 | expect(mockOfflineCache.clear).toHaveBeenCalled();
33 | });
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/core/src/authentication/observeSession.ts:
--------------------------------------------------------------------------------
1 | import {
2 | EVENTS,
3 | ISessionInfo,
4 | Session,
5 | } from "@inrupt/solid-client-authn-browser";
6 | import { BehaviorSubject, fromEvent, map, merge } from "rxjs";
7 | import { SessionInfo } from "./index";
8 |
9 | export function observeSession(session: Session): BehaviorSubject {
10 | const sessionInfoSubject = new BehaviorSubject(session.info);
11 | const login = fromEvent(session.events, EVENTS.LOGIN);
12 | const logout = fromEvent(session.events, EVENTS.LOGOUT);
13 | const sessionRestored = fromEvent(session.events, EVENTS.SESSION_RESTORED);
14 | merge(login, logout, sessionRestored)
15 | .pipe(map(() => sessionInfoSubject.next(session.info)))
16 | .subscribe();
17 | return sessionInfoSubject;
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/files/BinaryFile.ts:
--------------------------------------------------------------------------------
1 | import { SolidFile } from "./SolidFile";
2 |
3 | export class BinaryFile implements SolidFile {
4 | constructor(
5 | public readonly url: string,
6 | private readonly data: Blob,
7 | ) {}
8 |
9 | blob(): Blob {
10 | return this.data;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/files/BrokenFile.spec.ts:
--------------------------------------------------------------------------------
1 | import { BrokenFile } from "./BrokenFile";
2 | import { HttpStatus } from "./HttpStatus";
3 |
4 | describe("BrokenFile", () => {
5 | it("blob is null", () => {
6 | const file = new BrokenFile(
7 | "https://pod.test/missing.png",
8 | new HttpStatus(404, "Not Found"),
9 | );
10 | expect(file.blob()).toBeNull();
11 | });
12 |
13 | it("toString returns url and status", () => {
14 | const file = new BrokenFile(
15 | "https://pod.test/missing.png",
16 | new HttpStatus(404, "Not Found"),
17 | );
18 | expect(file.toString()).toBe(
19 | "404 - Not Found - https://pod.test/missing.png",
20 | );
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/core/src/files/BrokenFile.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from "./HttpStatus";
2 | import { SolidFile } from "./SolidFile";
3 |
4 | export class BrokenFile implements SolidFile {
5 | constructor(
6 | public readonly url: string,
7 | public readonly status: HttpStatus,
8 | ) {}
9 |
10 | toString() {
11 | return `${this.status.toString()} - ${this.url}`;
12 | }
13 |
14 | blob(): Blob | null {
15 | return null;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/files/FileFetcher.ts:
--------------------------------------------------------------------------------
1 | import { PodOsSession } from "../authentication";
2 | import { BinaryFile } from "./BinaryFile";
3 | import { BrokenFile } from "./BrokenFile";
4 | import { HttpStatus } from "./HttpStatus";
5 | import { SolidFile } from "./SolidFile";
6 |
7 | export class FileFetcher {
8 | constructor(private session: PodOsSession) {}
9 |
10 | async fetchFile(url: string): Promise {
11 | const response = await this.session.authenticatedFetch(url);
12 | if (response.ok) {
13 | const blob = await response.blob();
14 | return new BinaryFile(url, blob);
15 | } else {
16 | return new BrokenFile(
17 | url,
18 | new HttpStatus(response.status, response.statusText),
19 | );
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/files/HttpStatus.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from "./HttpStatus";
2 |
3 | describe("HttpStatus", () => {
4 | describe("toString", () => {
5 | it("returns status code and status text", () => {
6 | const status = new HttpStatus(404, "Not Found");
7 | expect(status.toString()).toEqual("404 - Not Found");
8 | });
9 |
10 | it("returns ony status if text is missing", () => {
11 | const status = new HttpStatus(404);
12 | expect(status.toString()).toEqual("404");
13 | });
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/core/src/files/HttpStatus.ts:
--------------------------------------------------------------------------------
1 | export class HttpStatus {
2 | constructor(
3 | public readonly code: number,
4 | public readonly text?: string,
5 | ) {}
6 |
7 | toString() {
8 | return this.text ? `${this.code} - ${this.text}` : this.code.toString();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/files/SolidFile.ts:
--------------------------------------------------------------------------------
1 | export interface SolidFile {
2 | url: string;
3 | blob: () => Blob | null;
4 | }
5 |
--------------------------------------------------------------------------------
/core/src/files/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SolidFile";
2 | export * from "./BinaryFile";
3 | export * from "./BrokenFile";
4 | export * from "./HttpStatus";
5 |
--------------------------------------------------------------------------------
/core/src/ldp-container/LdpContainer.assume.spec.ts:
--------------------------------------------------------------------------------
1 | import { graph } from "rdflib";
2 | import { Thing } from "../thing";
3 | import { LdpContainer } from "./LdpContainer";
4 |
5 | describe("Thing", () => {
6 | describe("assuming LdpContainer", () => {
7 | it("container keeps all properties from generic thing", () => {
8 | const store = graph();
9 | const thing = new Thing("https://thing.example", store, true);
10 | const container = thing.assume(LdpContainer);
11 | expect(container.uri).toEqual("https://thing.example");
12 | expect(container.store).toEqual(store);
13 | expect(container.editable).toBe(true);
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/core/src/ldp-container/LdpContainer.ts:
--------------------------------------------------------------------------------
1 | import { IndexedFormula, sym } from "rdflib";
2 | import { Thing } from "../thing";
3 |
4 | export interface ContainerContent {
5 | uri: string;
6 | name: string;
7 | }
8 | export class LdpContainer extends Thing {
9 | constructor(
10 | readonly uri: string,
11 | readonly store: IndexedFormula,
12 | readonly editable: boolean = false,
13 | ) {
14 | super(uri, store, editable);
15 | }
16 |
17 | contains(): ContainerContent[] {
18 | const contains = this.store.statementsMatching(
19 | sym(this.uri),
20 | sym("http://www.w3.org/ns/ldp#contains"),
21 | null,
22 | sym(this.uri),
23 | );
24 | return contains.map((content) => ({
25 | uri: content.object.value,
26 | name: content.object.value.replace(
27 | new RegExp(`${this.uri}([^/]*)/?`),
28 | "$1",
29 | ),
30 | }));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/ldp-container/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./LdpContainer";
2 |
--------------------------------------------------------------------------------
/core/src/modules/contacts.ts:
--------------------------------------------------------------------------------
1 | import { ContactsModule } from "@solid-data-modules/contacts-rdflib";
2 | import { Store } from "../Store";
3 |
4 | export async function loadContactsModule(
5 | store: Store,
6 | ): Promise {
7 | const module = await import("@solid-data-modules/contacts-rdflib");
8 | return new module.default({
9 | store: store.graph,
10 | fetcher: store.fetcher,
11 | updater: store.updater,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/namespaces/index.ts:
--------------------------------------------------------------------------------
1 | import { Namespace } from "rdflib";
2 |
3 | export const rdfs = Namespace("http://www.w3.org/2000/01/rdf-schema#");
4 | export const pim = Namespace("http://www.w3.org/ns/pim/space#");
5 |
--------------------------------------------------------------------------------
/core/src/offline-cache/OfflineCache.ts:
--------------------------------------------------------------------------------
1 | export interface CachedRdfDocument {
2 | url: string;
3 | revision: string;
4 | statements: string;
5 | }
6 |
7 | export interface OfflineCache {
8 | put(document: CachedRdfDocument): void;
9 | get(url: string): Promise;
10 | clear(): void;
11 | }
12 |
13 | export class NoOfflineCache implements OfflineCache {
14 | put() {}
15 | async get(): Promise {
16 | return undefined;
17 | }
18 | clear() {}
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/offline-cache/OnlineStatus.ts:
--------------------------------------------------------------------------------
1 | export interface OnlineStatus {
2 | isOnline(): boolean;
3 | }
4 |
5 | export class AssumeAlwaysOnline implements OnlineStatus {
6 | isOnline() {
7 | return true;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/offline-cache/index.ts:
--------------------------------------------------------------------------------
1 | export { OfflineCapableFetcher } from "./OfflineCapableFetcher";
2 | export {
3 | OfflineCache,
4 | CachedRdfDocument,
5 | NoOfflineCache,
6 | } from "./OfflineCache";
7 |
8 | export { OnlineStatus, AssumeAlwaysOnline } from "./OnlineStatus";
9 |
--------------------------------------------------------------------------------
/core/src/offline-cache/rdflib.d.ts:
--------------------------------------------------------------------------------
1 | // Import the original types from rdflib
2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
3 | import * as rdflib from "rdflib";
4 | import { AutoInitOptions } from "rdflib";
5 |
6 | /**
7 | * This module declaration is needed as a workarround, because
8 | * rdflib does not export all the types used in the method signatures of the Fetcher.
9 | * We are changing the signatures here to something that is available and (hopefully) matches with
10 | * what the Fetcher is actually compatible with
11 | */
12 | declare module "rdflib" {
13 | export class Fetcher {
14 | constructor(store: IndexedFormula, options?: Partial);
15 |
16 | async load>(
17 | url: T,
18 | options?: object,
19 | ): Promise ? Response[] : Response>;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/profile/index.ts:
--------------------------------------------------------------------------------
1 | export { WebIdProfile } from "./WebIdProfile";
2 |
--------------------------------------------------------------------------------
/core/src/rdf-document/RdfDocument.assume.spec.ts:
--------------------------------------------------------------------------------
1 | import { graph } from "rdflib";
2 | import { Thing } from "../thing";
3 | import { RdfDocument } from "./RdfDocument";
4 |
5 | describe("Thing", () => {
6 | describe("assuming RdfDocument", () => {
7 | it("document keeps all properties from generic thing", () => {
8 | const store = graph();
9 | const thing = new Thing("https://thing.example", store, true);
10 | const document = thing.assume(RdfDocument);
11 | expect(document.uri).toEqual("https://thing.example");
12 | expect(document.store).toEqual(store);
13 | expect(document.editable).toBe(true);
14 | });
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/core/src/rdf-document/RdfDocument.ts:
--------------------------------------------------------------------------------
1 | import { Thing } from "../thing";
2 | import { IndexedFormula, isNamedNode, sym } from "rdflib";
3 |
4 | export interface Subject {
5 | uri: string;
6 | }
7 |
8 | export class RdfDocument extends Thing {
9 | constructor(
10 | readonly uri: string,
11 | readonly store: IndexedFormula,
12 | readonly editable: boolean = false,
13 | ) {
14 | super(uri, store, editable);
15 | }
16 |
17 | subjects() {
18 | const matches = this.store.statementsMatching(
19 | null,
20 | null,
21 | null,
22 | sym(this.uri),
23 | );
24 | const uris = matches
25 | .filter((match) => isNamedNode(match.subject))
26 | .map((match) => match.subject.value);
27 | const uniqueUris = new Set(uris);
28 | uniqueUris.delete(this.uri);
29 | return [...uniqueUris].map((uri) => ({
30 | uri,
31 | }));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/src/rdf-document/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./RdfDocument";
2 |
--------------------------------------------------------------------------------
/core/src/search/LabelIndex.ts:
--------------------------------------------------------------------------------
1 | import { IndexedFormula, sym } from "rdflib";
2 | import { RdfDocument } from "../rdf-document";
3 | import { rdfs } from "../namespaces";
4 |
5 | /**
6 | * Represents a label index document as described in
7 | * https://github.com/pod-os/PodOS/blob/main/docs/features/full-text-search.md
8 | */
9 | export class LabelIndex extends RdfDocument {
10 | constructor(
11 | readonly uri: string,
12 | readonly store: IndexedFormula,
13 | readonly editable: boolean = false,
14 | ) {
15 | super(uri, store, editable);
16 | }
17 |
18 | /**
19 | * Returns the URIs and labels for all the things listed in the document.
20 | */
21 | getIndexedItems() {
22 | const matches = this.store.statementsMatching(
23 | null,
24 | rdfs("label"),
25 | null,
26 | sym(this.uri),
27 | );
28 |
29 | return matches.map((it) => {
30 | return {
31 | uri: it.subject.value,
32 | label: it.object.value,
33 | };
34 | });
35 | }
36 |
37 | contains(uri: string) {
38 | return this.store.holds(sym(uri), rdfs("label"), null, sym(this.uri));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/core/src/search/addToLabelIndex.ts:
--------------------------------------------------------------------------------
1 | import { UpdateOperation } from "@solid-data-modules/rdflib-utils";
2 | import { LabelIndex } from "./LabelIndex";
3 | import { lit, st, sym } from "rdflib";
4 | import { rdfs } from "../namespaces";
5 | import { Thing } from "../thing";
6 |
7 | export const addToLabelIndex = (
8 | thing: Thing,
9 | labelIndex: LabelIndex,
10 | ): UpdateOperation => {
11 | return {
12 | deletions: [],
13 | filesToCreate: [],
14 | insertions: [
15 | st(
16 | sym(thing.uri),
17 | rdfs("label"),
18 | lit(thing.label()),
19 | sym(labelIndex.uri),
20 | ),
21 | ],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/core/src/search/createDefaultLabelIndex.ts:
--------------------------------------------------------------------------------
1 | import { lit, st, sym } from "rdflib";
2 | import { solid, UpdateOperation } from "@solid-data-modules/rdflib-utils";
3 | import { WebIdProfile } from "../profile";
4 | import { rdfs } from "../namespaces";
5 |
6 | export function createDefaultLabelIndex(
7 | profile: WebIdProfile,
8 | ): { uri: string } & UpdateOperation {
9 | const webId = sym(profile.webId);
10 | const preferencesFile = profile.getPreferencesFile();
11 | const defaultFileName = "privateLabelIndex.ttl";
12 |
13 | const indexUrl = preferencesFile
14 | ? new URL(defaultFileName, preferencesFile).href
15 | : new URL(defaultFileName, webId.uri).href;
16 | const preferencesOrProfileDoc = preferencesFile
17 | ? sym(preferencesFile)
18 | : webId.doc();
19 |
20 | const indexDocument = sym(indexUrl);
21 | return {
22 | uri: indexDocument.uri,
23 | insertions: [
24 | st(
25 | webId,
26 | solid("privateLabelIndex"),
27 | indexDocument,
28 | preferencesOrProfileDoc,
29 | ),
30 | st(indexDocument, rdfs("label"), lit("Default Index"), indexDocument),
31 | ],
32 | deletions: [],
33 | filesToCreate: [],
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/search/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./SearchIndex";
2 | export * from "./LabelIndex";
3 | export * from "./SearchGateway";
4 |
--------------------------------------------------------------------------------
/core/src/terms/Term.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents a term from a RDF vocabulary
3 | */
4 | export interface Term {
5 | /**
6 | * Full URI of the term, e.g. http://xmlns.com/foaf/0.1/name
7 | */
8 | uri: string;
9 | /**
10 | * Shorthand syntax of the term, using a well-known prefix, e.g. foaf:name
11 | */
12 | shorthand: string;
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/terms/createListOfTerms.ts:
--------------------------------------------------------------------------------
1 | export const createListOfTerms = (terms: {
2 | [prefix: string]: { [name: string]: string };
3 | }) =>
4 | Object.keys(terms).flatMap((prefix) => {
5 | return Object.keys(terms[prefix]).map((name) => ({
6 | uri: terms[prefix][name],
7 | shorthand: `${prefix}:${name}`,
8 | }));
9 | });
10 |
--------------------------------------------------------------------------------
/core/src/terms/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Term";
2 | export * from "./listKnownTerms";
3 |
--------------------------------------------------------------------------------
/core/src/terms/listKnownTerms.spec.ts:
--------------------------------------------------------------------------------
1 | import { listKnownTerms } from "./listKnownTerms";
2 |
3 | describe("list known terms", () => {
4 | it("sample some important terms", () => {
5 | const predicates = listKnownTerms();
6 | expect(predicates).toContainEqual({
7 | uri: "http://schema.org/name",
8 | shorthand: "schema:name",
9 | });
10 | expect(predicates).toContainEqual({
11 | uri: "http://xmlns.com/foaf/0.1/img",
12 | shorthand: "foaf:img",
13 | });
14 | expect(predicates).toContainEqual({
15 | uri: "http://www.w3.org/2000/01/rdf-schema#label",
16 | shorthand: "rdfs:label",
17 | });
18 | expect(predicates).toContainEqual({
19 | uri: "http://www.w3.org/2006/vcard/ns#fn",
20 | shorthand: "vcard:fn",
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/core/src/terms/listKnownTerms.ts:
--------------------------------------------------------------------------------
1 | import * as terms from "rdf-namespaces";
2 | import { createListOfTerms } from "./createListOfTerms";
3 | import { Term } from "./Term";
4 |
5 | /**
6 | * Returns a list of terms from commonly used and well-known RDF vocabularies
7 | */
8 | export function listKnownTerms(): Term[] {
9 | return createListOfTerms(terms);
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/thing/accumulateSubjects.ts:
--------------------------------------------------------------------------------
1 | import { Statement } from "rdflib";
2 |
3 | interface Accumulator {
4 | [key: string]: string[];
5 | }
6 |
7 | /**
8 | * accumulate all subject values referencing a resource grouped by predicate
9 | * @param accumulator - Target javascript object to accumulate the values to
10 | * @param current - A statement with data to add to the accumulator
11 | */
12 | export const accumulateSubjects = (
13 | accumulator: Accumulator,
14 | current: Statement,
15 | ) => {
16 | const existing = accumulator[current.predicate.uri];
17 | return {
18 | ...accumulator,
19 | [current.predicate.uri]: existing
20 | ? [...existing, current.subject.value]
21 | : [current.subject.value],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/core/src/thing/accumulateValues.ts:
--------------------------------------------------------------------------------
1 | import { Statement } from "rdflib";
2 |
3 | interface Accumulator {
4 | [key: string]: string[];
5 | }
6 |
7 | /**
8 | * accumulate all object values of a resource grouped by predicate
9 | * @param accumulator - Target object to accumulate the values to
10 | * @param current - A statement with data to add to the object
11 | */
12 | export const accumulateValues = (
13 | accumulator: Accumulator,
14 | current: Statement,
15 | ) => {
16 | const existing = accumulator[current.predicate.uri];
17 | return {
18 | ...accumulator,
19 | [current.predicate.uri]: existing
20 | ? [...existing, current.object.value]
21 | : [current.object.value],
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/core/src/thing/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Thing";
2 | export { labelFromUri } from "./labelFromUri";
3 |
--------------------------------------------------------------------------------
/core/src/thing/isRdfType.spec.ts:
--------------------------------------------------------------------------------
1 | import { sym } from "rdflib";
2 | import { isRdfType } from "./isRdfType";
3 |
4 | describe("isRdfType", () => {
5 | it("returns true for rdf:type predicate", () => {
6 | isRdfType(sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"));
7 | });
8 |
9 | it("returns false for any other predicate", () => {
10 | isRdfType(sym("https://other.test/"));
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/core/src/thing/isRdfType.ts:
--------------------------------------------------------------------------------
1 | import { sym } from "rdflib";
2 | import { PredicateType } from "rdflib/lib/types";
3 |
4 | export function isRdfType(predicate: PredicateType): boolean {
5 | return predicate.sameTerm(
6 | sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/thing/labelForType.spec.ts:
--------------------------------------------------------------------------------
1 | import { labelForType } from "./labelForType";
2 |
3 | describe("labelForType", () => {
4 | it("returns fragment identifier", () => {
5 | const result = labelForType("https://vocab.example/types#SomeType");
6 | expect(result).toBe("SomeType");
7 | });
8 |
9 | it("returns last path segment if no fragment exists", () => {
10 | const result = labelForType("https://vocab.example/SomeType");
11 | expect(result).toBe("SomeType");
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/core/src/thing/labelForType.ts:
--------------------------------------------------------------------------------
1 | export function labelForType(typeUri: string) {
2 | if (typeUri.includes("#")) {
3 | return typeUri.substring(typeUri.lastIndexOf("#") + 1);
4 | } else {
5 | return typeUri.substring(typeUri.lastIndexOf("/") + 1);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/thing/labelFromUri.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a short human-readable label for a given URI
3 | * @param uri
4 | */
5 | export function labelFromUri(uri: string) {
6 | const url = new URL(uri);
7 | if (isTooGeneric(url.hash)) {
8 | return (getFilename(url) || url.host + url.pathname) + url.hash;
9 | }
10 |
11 | return labelFromFragment(url.hash) || getFilename(url) || url.host;
12 | }
13 |
14 | function labelFromFragment(fragment: string | null) {
15 | return fragment ? fragment.split("#")[1] : null;
16 | }
17 |
18 | function isTooGeneric(fragment: string) {
19 | const genericFragments = ["#it", "#this", "#me", "#i"];
20 | return genericFragments.includes(fragment);
21 | }
22 |
23 | function getFilename(url: URL) {
24 | if (url.pathname.endsWith("/")) {
25 | const containerName = url.pathname.split("/").at(-2);
26 | return containerName ? containerName + "/" : null;
27 | } else {
28 | return url.pathname.split("/").pop();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/uri/UriService.ts:
--------------------------------------------------------------------------------
1 | import { Store } from "../Store";
2 | import slugify from "slugify";
3 |
4 | export class UriService {
5 | // We expect to use the store for calculating the uris for things
6 | // e.g. looking up locations in type index
7 | constructor(private readonly store: Store) {}
8 |
9 | /**
10 | * Proposes a URI for a new thing based on what the referenceUri identifies:
11 | * - if a container, the new URI is in this container
12 | * - if a file, the new URI is in the same container as said file
13 | * - if a non-information-resource, the new URI is in the same container as that resource
14 | * @param referenceUri
15 | * @param name (will be slugified)
16 | */
17 | proposeUriForNewThing(referenceUri: string, name: string) {
18 | const filename = slugify(name, { lower: true });
19 | return `${new URL(filename, referenceUri)}#it`;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ES6",
5 | "module": "esnext",
6 | "esModuleInterop": true,
7 | "moduleResolution": "node",
8 | "forceConsistentCasingInFileNames": true,
9 | "strict": true,
10 | "skipLibCheck": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/dev-solid-server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | data/.internal/idp/adapter
3 | data/.internal/locks
4 | data/.internal/accounts/cookies
--------------------------------------------------------------------------------
/dev-solid-server/Readme.md:
--------------------------------------------------------------------------------
1 | # Development Solid Server
2 |
3 | Community Solid Server for local development
4 |
5 | ## Start
6 |
7 | You can start the server from pod-os root folder via:
8 |
9 | ```shell
10 | npm run dev:server
11 | ```
12 |
13 | or from here by just
14 |
15 | ```shell
16 | npm start
17 | ```
18 |
19 | ## Users
20 |
21 | | E-Mail | Password | WebID |
22 | | ----------------- | -------- | ------------------------------------------- |
23 | | alice@pod-os.test | alice | http://localhost:3000/alice/profile/card#me |
24 | | bob@pod-os.test | bob | http://localhost:3000/bob/profile/card#me |
25 |
26 | To register a new account visit http://localhost:3000/.account/login/password/register/
27 |
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/data/4b953dff-7b43-4fca-89df-54e3150e19bf$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/data/4b953dff-7b43-4fca-89df-54e3150e19bf","payload":{"linkedLoginsCount":1,"id":"4b953dff-7b43-4fca-89df-54e3150e19bf","**password**":{"ad8cd822-76aa-47d7-bfec-4771ce2b5cd8":{"email":"bob@pod-os.test","password":"$2a$10$UINLPe.3g358a6oYyNF/8.5JnpqLgqPpgfn5H3k1w9b8XQOo3RLIm","verified":true,"accountId":"4b953dff-7b43-4fca-89df-54e3150e19bf","id":"ad8cd822-76aa-47d7-bfec-4771ce2b5cd8"}},"**clientCredentials**":{},"**pod**":{"e1bbed34-717c-4880-bdad-aa42eff89301":{"baseUrl":"http://localhost:3000/bob/","accountId":"4b953dff-7b43-4fca-89df-54e3150e19bf","id":"e1bbed34-717c-4880-bdad-aa42eff89301","**owner**":{"a7b02e31-ac03-44b3-b246-9cadb45f349c":{"webId":"http://localhost:3000/bob/profile/card#me","podId":"e1bbed34-717c-4880-bdad-aa42eff89301","visible":false,"id":"a7b02e31-ac03-44b3-b246-9cadb45f349c"}}}},"**webIdLink**":{"973386d0-2e2c-4c8f-8cfa-dee71087c503":{"webId":"http://localhost:3000/bob/profile/card#me","accountId":"4b953dff-7b43-4fca-89df-54e3150e19bf","id":"973386d0-2e2c-4c8f-8cfa-dee71087c503"}}}}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/data/e0fb0b69-e0f0-4d19-abcf-94d15998b142$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/data/e0fb0b69-e0f0-4d19-abcf-94d15998b142","payload":{"linkedLoginsCount":1,"id":"e0fb0b69-e0f0-4d19-abcf-94d15998b142","**password**":{"329e144f-8397-40ef-a27f-09a1cbcf3b9c":{"email":"alice@pod-os.test","password":"$2a$10$SrSavUrqMJbjzsgxDYJBRu/EwDoFul12bzi/FrFtzsFfz.HUBD1uq","verified":true,"accountId":"e0fb0b69-e0f0-4d19-abcf-94d15998b142","id":"329e144f-8397-40ef-a27f-09a1cbcf3b9c"}},"**clientCredentials**":{},"**pod**":{"b718b08a-b19b-43fb-aa0d-449c60caf639":{"baseUrl":"http://localhost:3000/alice/","accountId":"e0fb0b69-e0f0-4d19-abcf-94d15998b142","id":"b718b08a-b19b-43fb-aa0d-449c60caf639","**owner**":{"b49908e4-9a89-4678-8b7f-53557beafa31":{"webId":"http://localhost:3000/alice/profile/card#me","podId":"b718b08a-b19b-43fb-aa0d-449c60caf639","visible":false,"id":"b49908e4-9a89-4678-8b7f-53557beafa31"}}}},"**webIdLink**":{"c7cb8ea5-f48e-4bb6-83f2-5384cfb9505d":{"webId":"http://localhost:3000/alice/profile/card#me","accountId":"e0fb0b69-e0f0-4d19-abcf-94d15998b142","id":"c7cb8ea5-f48e-4bb6-83f2-5384cfb9505d"}},"rememberLogin":true}}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/owner/a7b02e31-ac03-44b3-b246-9cadb45f349c$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/owner/a7b02e31-ac03-44b3-b246-9cadb45f349c","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/owner/b49908e4-9a89-4678-8b7f-53557beafa31$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/owner/b49908e4-9a89-4678-8b7f-53557beafa31","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/password/329e144f-8397-40ef-a27f-09a1cbcf3b9c$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/329e144f-8397-40ef-a27f-09a1cbcf3b9c","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/password/ad8cd822-76aa-47d7-bfec-4771ce2b5cd8$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/ad8cd822-76aa-47d7-bfec-4771ce2b5cd8","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/password/email/alice@pod-os.test$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/email/alice%40pod-os.test","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/password/email/bob@pod-os.test$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/password/email/bob%40pod-os.test","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/pod/b718b08a-b19b-43fb-aa0d-449c60caf639$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/b718b08a-b19b-43fb-aa0d-449c60caf639","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A3000%2Falice%2F$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A3000%2Falice%2F","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A3000%2Fbob%2F$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/baseUrl/http%3A%2F%2Flocalhost%3A3000%2Fbob%2F","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/pod/e1bbed34-717c-4880-bdad-aa42eff89301$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/pod/e1bbed34-717c-4880-bdad-aa42eff89301","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/webIdLink/973386d0-2e2c-4c8f-8cfa-dee71087c503$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/973386d0-2e2c-4c8f-8cfa-dee71087c503","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/webIdLink/c7cb8ea5-f48e-4bb6-83f2-5384cfb9505d$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/c7cb8ea5-f48e-4bb6-83f2-5384cfb9505d","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A3000%2Falice%2Fprofile%2Fcard#me$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A3000%2Falice%2Fprofile%2Fcard%23me","payload":["e0fb0b69-e0f0-4d19-abcf-94d15998b142"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A3000%2Fbob%2Fprofile%2Fcard#me$.json:
--------------------------------------------------------------------------------
1 | {"key":"accounts/index/webIdLink/webId/http%3A%2F%2Flocalhost%3A3000%2Fbob%2Fprofile%2Fcard%23me","payload":["4b953dff-7b43-4fca-89df-54e3150e19bf"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/idp/keys/cookie-secret$.json:
--------------------------------------------------------------------------------
1 | {"key":"idp/keys/cookie-secret","payload":["ea8fb1b80a3cd805c774d4b3ab31cde6bac0d6fa70e46c07fbe61492541f841ed1a76b569a08a6f37096c6da432b9c60123f63ee5311ce871f8ae3d96bbd9a95"]}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/idp/keys/jwks$.json:
--------------------------------------------------------------------------------
1 | {"key":"idp/keys/jwks","payload":{"keys":[{"kty":"EC","x":"_MGl5auYZjan2OmVjxokqq-A2AfmgJFOpHz9Rvxr4jo","y":"HyHxyQPbsNiNgTM7N-f_8FunngdYpVMCsGNfdv4YGVE","crv":"P-256","d":"yyF60GJeGTILoVGoE5_5pJFea4ss1pLbxLdYwJOga2I","alg":"ES256"}]}}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/setup/current-base-url$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/current-base-url","payload":"http://localhost:3000/"}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/setup/current-server-version$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/current-server-version","payload":"7.1.3"}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/setup/rootInitialized$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/rootInitialized","payload":true}
--------------------------------------------------------------------------------
/dev-solid-server/data/.internal/setup/v6-migration$.json:
--------------------------------------------------------------------------------
1 | {"key":"setup/v6-migration","payload":true}
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/.acl:
--------------------------------------------------------------------------------
1 | # Root ACL resource for the agent account
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The homepage is readable by the public
6 | <#public>
7 | a acl:Authorization;
8 | acl:agentClass foaf:Agent;
9 | acl:accessTo <./>;
10 | acl:mode acl:Read.
11 |
12 | # The owner has full access to every resource in their pod.
13 | # Other agents have no access rights,
14 | # unless specifically authorized in other .acl resources.
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | # Optional owner email, to be used for account recovery:
19 | acl:agent ;
20 | # Set the access to the root storage folder itself
21 | acl:accessTo <./>;
22 | # All resources will inherit this authorization, by default
23 | acl:default <./>;
24 | # The owner has all of the access modes allowed
25 | acl:mode
26 | acl:Read, acl:Write, acl:Control.
27 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/.meta:
--------------------------------------------------------------------------------
1 | a .
2 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/README$.markdown:
--------------------------------------------------------------------------------
1 | # Welcome to your pod
2 |
3 | ## A place to store your data
4 | Your pod is a **secure storage space** for your documents and data.
5 |
6 | You can choose to share those with other people and apps.
7 |
8 | As the owner of this pod,
9 | identified by http://localhost:3000/alice/profile/card#me,
10 | you have access to all of your documents.
11 |
12 | ## Working with your pod
13 | The easiest way to interact with pods
14 | is through Solid apps.
15 |
16 | For example,
17 | you can open your pod in [Databrowser](https://solid.github.io/mashlib/dist/browse.html?uri=http://localhost:3000/alice/).
18 |
19 | ## Learn more
20 | The [Solid website](https://solidproject.org/)
21 | and the people on its [forum](https://forum.solidproject.org/)
22 | will be glad to help you on your journey.
23 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/README.acl:
--------------------------------------------------------------------------------
1 | @prefix acl: .
2 | @prefix foaf: .
3 |
4 | <#public>
5 | a acl:Authorization;
6 | acl:accessTo <./README>;
7 | acl:agentClass foaf:Agent;
8 | acl:mode acl:Read.
9 |
10 | <#owner>
11 | a acl:Authorization;
12 | acl:accessTo <./README>;
13 | acl:agent ;
14 | acl:mode acl:Read, acl:Write, acl:Control.
15 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/contacts/Person/8xO3yY/index.ttl:
--------------------------------------------------------------------------------
1 | <#this>
2 | "Bob" ;
3 | a ;
4 | <#email> ;
5 | <#phone> .
6 |
7 | <#email>
8 | .
9 |
10 | <#phone>
11 | .
12 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/contacts/groups.ttl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/dev-solid-server/data/alice/contacts/groups.ttl
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/contacts/index.ttl:
--------------------------------------------------------------------------------
1 | @prefix vcard: .
2 | @prefix dc: .
3 | @prefix xsd: .
4 |
5 | <#this> a vcard:AddressBook;
6 | dc:title "Alice's contacts";
7 | vcard:nameEmailIndex ;
8 | vcard:groupIndex .
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/contacts/people.ttl:
--------------------------------------------------------------------------------
1 |
2 | ;
3 | "Bob" .
4 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/documents/notes.md:
--------------------------------------------------------------------------------
1 | # Alice's notes
2 |
3 | Alice can make some notes using markdown, store them in her Pod and view them using PodOS.
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/documents/page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Web Page
9 |
10 |
11 | Solid Test HTML
12 | This is an HTML page stored in Alice's Pod.
13 | It can be viewed with PodOS.
14 |
15 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/documents/test.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/dev-solid-server/data/alice/documents/test.pdf
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/games/.acl:
--------------------------------------------------------------------------------
1 | @prefix acl: .
2 | @prefix foaf: .
3 |
4 | <#public>
5 | a acl:Authorization;
6 | acl:agentClass foaf:Agent;
7 | acl:accessTo <./>;
8 | acl:default <./>;
9 | acl:mode acl:Read.
10 |
11 | <#owner>
12 | a acl:Authorization;
13 | acl:agent ;
14 | # Set the access to the root storage folder itself
15 | acl:accessTo <./>;
16 | # All resources will inherit this authorization, by default
17 | acl:default <./>;
18 | # The owner has all of the access modes allowed
19 | acl:mode
20 | acl:Read, acl:Write, acl:Control.
21 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/pages/.acl:
--------------------------------------------------------------------------------
1 | @prefix acl: .
2 | @prefix foaf: .
3 |
4 | <#public>
5 | a acl:Authorization;
6 | acl:agentClass foaf:Agent;
7 | acl:accessTo <./>;
8 | acl:default <./>;
9 | acl:mode acl:Read.
10 |
11 | <#owner>
12 | a acl:Authorization;
13 | acl:agent ;
14 | # Set the access to the root storage folder itself
15 | acl:accessTo <./>;
16 | # All resources will inherit this authorization, by default
17 | acl:default <./>;
18 | # The owner has all of the access modes allowed
19 | acl:mode
20 | acl:Read, acl:Write, acl:Control.
21 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/profile/card$.ttl:
--------------------------------------------------------------------------------
1 | @prefix foaf: .
2 | @prefix solid: .
3 | @prefix pim: .
4 |
5 | <>
6 | a foaf:PersonalProfileDocument ;
7 | foaf:maker ;
8 | foaf:primaryTopic .
9 |
10 |
11 | a foaf:Person ;
12 | foaf:name "Alice" ;
13 | solid:oidcIssuer ;
14 | pim:preferencesFile ;
15 | .
16 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/profile/card.acl:
--------------------------------------------------------------------------------
1 | # ACL resource for the WebID profile document
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The WebID profile is readable by the public.
6 | # This is required for discovery and verification,
7 | # e.g. when checking identity providers.
8 | <#public>
9 | a acl:Authorization;
10 | acl:agentClass foaf:Agent;
11 | acl:accessTo <./card>;
12 | acl:mode acl:Read.
13 |
14 | # The owner has full access to the profile
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | acl:accessTo <./card>;
19 | acl:mode acl:Read, acl:Write, acl:Control.
20 |
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/settings/prefs.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#>.
2 | @prefix solid: .
3 | @prefix c: .
4 |
5 |
6 | c:me
7 | solid:privateLabelIndex , ;
8 | .
--------------------------------------------------------------------------------
/dev-solid-server/data/alice/settings/privateLabelIndex.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#> .
2 | @prefix rdfs: .
3 |
4 |
5 | rdfs:label "Minecraft" .
6 |
7 |
8 | rdfs:label "Alice" .
9 |
10 |
11 | rdfs:label "test.pdf" .
12 |
13 |
14 | rdfs:label "Web Page" .
15 |
16 |
17 | rdfs:label "notes.md" .
18 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/.acl:
--------------------------------------------------------------------------------
1 | # Root ACL resource for the agent account
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The homepage is readable by the public
6 | <#public>
7 | a acl:Authorization;
8 | acl:agentClass foaf:Agent;
9 | acl:accessTo <./>;
10 | acl:mode acl:Read.
11 |
12 | # The owner has full access to every resource in their pod.
13 | # Other agents have no access rights,
14 | # unless specifically authorized in other .acl resources.
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | # Optional owner email, to be used for account recovery:
19 | acl:agent ;
20 | # Set the access to the root storage folder itself
21 | acl:accessTo <./>;
22 | # All resources will inherit this authorization, by default
23 | acl:default <./>;
24 | # The owner has all of the access modes allowed
25 | acl:mode
26 | acl:Read, acl:Write, acl:Control.
27 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/.meta:
--------------------------------------------------------------------------------
1 | a .
2 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/README$.markdown:
--------------------------------------------------------------------------------
1 | # Welcome to your pod
2 |
3 | ## A place to store your data
4 | Your pod is a **secure storage space** for your documents and data.
5 |
6 | You can choose to share those with other people and apps.
7 |
8 | As the owner of this pod,
9 | identified by http://localhost:3000/bob/profile/card#me,
10 | you have access to all of your documents.
11 |
12 | ## Working with your pod
13 | The easiest way to interact with pods
14 | is through Solid apps.
15 |
16 | For example,
17 | you can open your pod in [Databrowser](https://solid.github.io/mashlib/dist/browse.html?uri=http://localhost:3000/bob/).
18 |
19 | ## Learn more
20 | The [Solid website](https://solidproject.org/)
21 | and the people on its [forum](https://forum.solidproject.org/)
22 | will be glad to help you on your journey.
23 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/README.acl:
--------------------------------------------------------------------------------
1 | @prefix acl: .
2 | @prefix foaf: .
3 |
4 | <#public>
5 | a acl:Authorization;
6 | acl:accessTo <./README>;
7 | acl:agentClass foaf:Agent;
8 | acl:mode acl:Read.
9 |
10 | <#owner>
11 | a acl:Authorization;
12 | acl:accessTo <./README>;
13 | acl:agent ;
14 | acl:mode acl:Read, acl:Write, acl:Control.
15 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/profile/card$.ttl:
--------------------------------------------------------------------------------
1 | @prefix foaf: .
2 | @prefix solid: .
3 |
4 | <>
5 | a foaf:PersonalProfileDocument;
6 | foaf:maker ;
7 | foaf:primaryTopic .
8 |
9 |
10 | foaf:name "Bob";
11 | solid:oidcIssuer ;
12 | a foaf:Person.
13 |
--------------------------------------------------------------------------------
/dev-solid-server/data/bob/profile/card.acl:
--------------------------------------------------------------------------------
1 | # ACL resource for the WebID profile document
2 | @prefix acl: .
3 | @prefix foaf: .
4 |
5 | # The WebID profile is readable by the public.
6 | # This is required for discovery and verification,
7 | # e.g. when checking identity providers.
8 | <#public>
9 | a acl:Authorization;
10 | acl:agentClass foaf:Agent;
11 | acl:accessTo <./card>;
12 | acl:mode acl:Read.
13 |
14 | # The owner has full access to the profile
15 | <#owner>
16 | a acl:Authorization;
17 | acl:agent ;
18 | acl:accessTo <./card>;
19 | acl:mode acl:Read, acl:Write, acl:Control.
20 |
--------------------------------------------------------------------------------
/dev-solid-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dev-solid-server",
3 | "version": "1.0.0",
4 | "description": "Community Solid Server for local development",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "community-solid-server --config ./config/pod-os-dev.json --rootFilePath ./data"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "dependencies": {
12 | "@solid/community-server": "^7.1.2"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/pod-os/pod-os.git"
17 | },
18 | "bugs": {
19 | "url": "https://github.com/pod-os/pod-os/issues"
20 | },
21 | "homepage": "https://github.com/pod-os/pod-os#readme"
22 | }
23 |
--------------------------------------------------------------------------------
/docs/contributing/definition-of-done.md:
--------------------------------------------------------------------------------
1 | # Definition of Done
2 |
3 | - [ ] Tests have been written
4 | - [ ] all new code is covered by unit tests
5 | - [ ] the happy path of a new feature is covered by an end-to-end test
6 | - [ ] manual explorative tests have been performed
7 | - [ ] all dependencies are updated to the latest patch version at minimum
8 | - [ ] the [CI pipeline](https://github.com/pod-os/PodOS/actions) passes
9 | - [ ] documentation is up-to-date
10 | - [ ] TSDoc style comments on important functions, properties and events
11 | - [ ] stories for new PodOS elements have been added to [storybook](../../storybook)
12 | - [ ] Readme.md files of PodOS elements have been re-generated
13 | - [ ] architectural decisions are documented as an [ADR](../decisions/0000-use-markdown-architectural-decision-records.md)
14 | - [ ] Changelogs are updated according to
15 | [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
16 |
--------------------------------------------------------------------------------
/docs/decisions/0003-handle-data-with-rdflibjs.md:
--------------------------------------------------------------------------------
1 | # Handle data with rdflib.js
2 |
3 | ## Context and Problem Statement
4 |
5 | PodOS needs to load and process RDF data in a very generic way to be able to
6 | provide generic data browsing and to propose specific apps that are fitting for
7 | a resource. It should also be able to process data locally, that has been
8 | fetched already.
9 |
10 | ## Considered Options
11 |
12 | - rdflib.js
13 | - solid-client by Inrupt
14 |
15 | ## Decision Outcome
16 |
17 | Use rdflib.js in PodOS core. It has been successfully used on
18 | [SolidOS](https://github.com/solid/solidos) for the same use case. The
19 | solid-client by Inrupt seems to be good to load specific datasets known
20 | beforehand, but it is hard to use for generic fetching and processing. It does
21 | not combine all fetched data to a local store.
22 |
23 | rdflib.js shall be used only in PodOS core to handle the data for PodOS
24 | elements. The latter must not depend on rdflib.js, only on PodOS core.
25 |
--------------------------------------------------------------------------------
/docs/decisions/0004-no-stencil-e2e-tests.md:
--------------------------------------------------------------------------------
1 | # Do not use Stencil E2E Tests
2 |
3 | ## Context and Problem Statement
4 |
5 | PodOS elements are build with [Stencil](https://stenciljs.com). The official
6 | documentation differs between Unit Testing and End-to-end (E2E) Testing Stencil
7 | components.
8 |
9 | E2E turned out to be slow, very hard to debug and did not run with
10 | [IntelliJ idea](https://www.jetbrains.com/de-de/idea/) or
11 | [Wallaby](wallabyjs.com/).
12 |
13 | ## Decision Outcome
14 |
15 | Currently, it is enough to write unit tests and integration tests. For
16 | integration tests, we use the same mechanism as for unit tests, but render more
17 | than one component in one spec page, to ensure they work well together.
18 |
19 | Naming conventions:
20 |
21 | - *.spec.tsx for unit tests
22 | - *.integration.spec.tsx for integration tests
23 |
24 | PodOS core is mocked in both cases. Real End-to-End tests including PodOS core
25 | could be based on e.g. Cypress as soon as needed.
26 |
27 | Existing Stencil E2E Tests are replaced with integration tests.
28 |
--------------------------------------------------------------------------------
/docs/decisions/0006-end-to-end-testing-via-playwright.md:
--------------------------------------------------------------------------------
1 | # End-to-End testing via Playwright
2 |
3 | ## Context and Problem Statement
4 |
5 | We want to test the features of PodOS browser end-to-end from a users perspective and with data from a real Solid server.
6 | Currently, this is only done manually and is starting to become time-consuming.
7 |
8 | ## Considered Options
9 |
10 | - Cypress
11 | - Playwright
12 |
13 | ## Decision Outcome
14 |
15 | Chosen option: "End-to-end testing via Playwright", because
16 |
17 | - Playwright looks promising and existing
18 | - Playwright supports testing in multiple browsers
19 | - While experience with Cypress already exists, it was a chance to try something new
20 |
21 | ## Links
22 |
23 | - [Playwright](https://playwright.dev)
24 |
--------------------------------------------------------------------------------
/docs/decisions/0009-introduce-rxjs.md:
--------------------------------------------------------------------------------
1 | # Introduce RxJS
2 |
3 | ## Context and Problem Statement
4 |
5 | For other apps (like PodOS contacts, but also third-party apps) it was not possible to get the current user session. There is a way to get notified about session changes via `trackSession`, but this will not be sufficient if the session state does not change and the app just needs the current session state.
6 |
7 | ## Considered Options
8 |
9 | - use RxJS
10 | - make the session info globally available
11 |
12 | ## Decision Outcome
13 |
14 | - use a RxJS BehaviourSubject to stream all session values to interested subscribers
15 |
16 | ### Positive Consequences
17 |
18 | - current and future session values are communicated the same way
19 | - RxJS might come in handy in other places where applications need to react to state changes, like when new data for a resource gets fetched
20 |
21 | ### Negative Consequences
22 |
23 | - new dependency on RxJS
24 | - handling subscribe and unsubscribe can be complex and error-prone
25 |
26 | ## Links
27 |
28 | - [RxJS](https://rxjs.dev/)
29 |
30 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-dashboard/pos-example-resources/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-app-dashboard](..)
10 |
11 | ### Depends on
12 |
13 | - [pos-rich-link](../../../components/pos-rich-link)
14 |
15 | ### Graph
16 | ```mermaid
17 | graph TD;
18 | pos-example-resources --> pos-rich-link
19 | pos-rich-link --> pos-resource
20 | pos-rich-link --> pos-label
21 | pos-rich-link --> pos-description
22 | pos-resource --> ion-progress-bar
23 | pos-resource --> ion-card
24 | pos-resource --> ion-card-header
25 | pos-resource --> ion-card-content
26 | ion-card --> ion-ripple-effect
27 | pos-app-dashboard --> pos-example-resources
28 | style pos-example-resources fill:#f9f,stroke:#333,stroke-width:4px
29 | ```
30 |
31 | ----------------------------------------------
32 |
33 | *Built with [StencilJS](https://stenciljs.com/)*
34 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-dashboard/pos-getting-started/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-app-dashboard](..)
10 |
11 | ### Graph
12 | ```mermaid
13 | graph TD;
14 | pos-app-dashboard --> pos-getting-started
15 | style pos-getting-started fill:#f9f,stroke:#333,stroke-width:4px
16 | ```
17 |
18 | ----------------------------------------------
19 |
20 | *Built with [StencilJS](https://stenciljs.com/)*
21 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-dashboard/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-internal-router](../../components/pos-internal-router)
10 |
11 | ### Depends on
12 |
13 | - [pos-getting-started](pos-getting-started)
14 | - [pos-example-resources](pos-example-resources)
15 |
16 | ### Graph
17 | ```mermaid
18 | graph TD;
19 | pos-app-dashboard --> pos-getting-started
20 | pos-app-dashboard --> pos-example-resources
21 | pos-example-resources --> pos-rich-link
22 | pos-rich-link --> pos-resource
23 | pos-rich-link --> pos-label
24 | pos-rich-link --> pos-description
25 | pos-resource --> ion-progress-bar
26 | pos-resource --> ion-card
27 | pos-resource --> ion-card-header
28 | pos-resource --> ion-card-content
29 | ion-card --> ion-ripple-effect
30 | pos-internal-router --> pos-app-dashboard
31 | style pos-app-dashboard fill:#f9f,stroke:#333,stroke-width:4px
32 | ```
33 |
34 | ----------------------------------------------
35 |
36 | *Built with [StencilJS](https://stenciljs.com/)*
37 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-settings/pos-getting-started/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-app-settings](..)
10 |
11 | ### Graph
12 | ```mermaid
13 | graph TD;
14 | pos-app-settings --> pos-setting-offline-cache
15 | style pos-setting-offline-cache fill:#f9f,stroke:#333,stroke-width:4px
16 | ```
17 |
18 | ----------------------------------------------
19 |
20 | *Built with [StencilJS](https://stenciljs.com/)*
21 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-settings/pos-setting-offline-cache/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-app-settings](..)
10 |
11 | ### Graph
12 | ```mermaid
13 | graph TD;
14 | pos-app-settings --> pos-setting-offline-cache
15 | style pos-setting-offline-cache fill:#f9f,stroke:#333,stroke-width:4px
16 | ```
17 |
18 | ----------------------------------------------
19 |
20 | *Built with [StencilJS](https://stenciljs.com/)*
21 |
--------------------------------------------------------------------------------
/docs/elements/apps/pos-app-settings/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Dependencies
6 |
7 | ### Used by
8 |
9 | - [pos-internal-router](../../components/pos-internal-router)
10 |
11 | ### Depends on
12 |
13 | - [pos-setting-offline-cache](pos-setting-offline-cache)
14 |
15 | ### Graph
16 | ```mermaid
17 | graph TD;
18 | pos-app-settings --> pos-setting-offline-cache
19 | pos-internal-router --> pos-app-settings
20 | style pos-app-settings fill:#f9f,stroke:#333,stroke-width:4px
21 | ```
22 |
23 | ----------------------------------------------
24 |
25 | *Built with [StencilJS](https://stenciljs.com/)*
26 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-add-new-thing/readme.md:
--------------------------------------------------------------------------------
1 | # pos-add-new-thing
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | --------------------------- | --------------- | ----------- | -------- | ----------- |
12 | | `referenceUri` _(required)_ | `reference-uri` | | `string` | `undefined` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-app-browser](../../apps/pos-app-browser)
20 |
21 | ### Depends on
22 |
23 | - ion-icon
24 | - [pos-dialog](../pos-dialog)
25 | - [pos-new-thing-form](../pos-new-thing-form)
26 |
27 | ### Graph
28 | ```mermaid
29 | graph TD;
30 | pos-add-new-thing --> ion-icon
31 | pos-add-new-thing --> pos-dialog
32 | pos-add-new-thing --> pos-new-thing-form
33 | pos-dialog --> ion-icon
34 | pos-new-thing-form --> pos-select-term
35 | pos-app-browser --> pos-add-new-thing
36 | style pos-add-new-thing fill:#f9f,stroke:#333,stroke-width:4px
37 | ```
38 |
39 | ----------------------------------------------
40 |
41 | *Built with [StencilJS](https://stenciljs.com/)*
42 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-app/readme.md:
--------------------------------------------------------------------------------
1 | # pos-app
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ------------------------ | -------------------------- | ----------- | --------- | ------- |
12 | | `restorePreviousSession` | `restore-previous-session` | | `boolean` | `false` |
13 |
14 |
15 | ## Events
16 |
17 | | Event | Description | Type |
18 | | ------------------------- | --------------------------------------- | ------------------------------- |
19 | | `pod-os:session-restored` | Fired whenever the session was restored | `CustomEvent<{ url: string; }>` |
20 |
21 |
22 | ## Dependencies
23 |
24 | ### Used by
25 |
26 | - [pos-app-browser](../../apps/pos-app-browser)
27 |
28 | ### Graph
29 | ```mermaid
30 | graph TD;
31 | pos-app-browser --> pos-app
32 | style pos-app fill:#f9f,stroke:#333,stroke-width:4px
33 | ```
34 |
35 | ----------------------------------------------
36 |
37 | *Built with [StencilJS](https://stenciljs.com/)*
38 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-description/readme.md:
--------------------------------------------------------------------------------
1 | # pos-description
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Events
9 |
10 | | Event | Description | Type |
11 | | ----------------- | ----------- | ------------------ |
12 | | `pod-os:resource` | | `CustomEvent` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-app-generic](../../apps/pos-app-generic)
20 | - [pos-rich-link](../pos-rich-link)
21 |
22 | ### Graph
23 | ```mermaid
24 | graph TD;
25 | pos-app-generic --> pos-description
26 | pos-rich-link --> pos-description
27 | style pos-description fill:#f9f,stroke:#333,stroke-width:4px
28 | ```
29 |
30 | ----------------------------------------------
31 |
32 | *Built with [StencilJS](https://stenciljs.com/)*
33 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-dialog/readme.md:
--------------------------------------------------------------------------------
1 | # pos-dialog
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Overview
9 |
10 | Styled wrapper around native dialog element, with slots `title` and `content`
11 |
12 | ## Methods
13 |
14 | ### `close() => Promise`
15 |
16 |
17 |
18 | #### Returns
19 |
20 | Type: `Promise`
21 |
22 |
23 |
24 | ### `showModal() => Promise`
25 |
26 |
27 |
28 | #### Returns
29 |
30 | Type: `Promise`
31 |
32 |
33 |
34 |
35 | ## Dependencies
36 |
37 | ### Used by
38 |
39 | - [pos-add-new-thing](../pos-add-new-thing)
40 | - [pos-login](../pos-login)
41 |
42 | ### Depends on
43 |
44 | - ion-icon
45 |
46 | ### Graph
47 | ```mermaid
48 | graph TD;
49 | pos-dialog --> ion-icon
50 | pos-add-new-thing --> pos-dialog
51 | pos-login --> pos-dialog
52 | style pos-dialog fill:#f9f,stroke:#333,stroke-width:4px
53 | ```
54 |
55 | ----------------------------------------------
56 |
57 | *Built with [StencilJS](https://stenciljs.com/)*
58 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-error-toast/readme.md:
--------------------------------------------------------------------------------
1 | # pos-error-toast
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Dependencies
9 |
10 | ### Used by
11 |
12 | - [pos-app-browser](../../apps/pos-app-browser)
13 |
14 | ### Depends on
15 |
16 | - ion-toast
17 | - ion-ripple-effect
18 |
19 | ### Graph
20 | ```mermaid
21 | graph TD;
22 | pos-error-toast --> ion-toast
23 | pos-error-toast --> ion-ripple-effect
24 | ion-toast --> ion-icon
25 | ion-toast --> ion-ripple-effect
26 | pos-app-browser --> pos-error-toast
27 | style pos-error-toast fill:#f9f,stroke:#333,stroke-width:4px
28 | ```
29 |
30 | ----------------------------------------------
31 |
32 | *Built with [StencilJS](https://stenciljs.com/)*
33 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-login-form/readme.md:
--------------------------------------------------------------------------------
1 | # pos-login-form
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Events
9 |
10 | | Event | Description | Type |
11 | | ------------------------- | ------------------------------------------- | ------------------ |
12 | | `pod-os:idp-url-selected` | Emits the selected IDP URL to use for login | `CustomEvent` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-login](../pos-login)
20 |
21 | ### Graph
22 | ```mermaid
23 | graph TD;
24 | pos-login --> pos-login-form
25 | style pos-login-form fill:#f9f,stroke:#333,stroke-width:4px
26 | ```
27 |
28 | ----------------------------------------------
29 |
30 | *Built with [StencilJS](https://stenciljs.com/)*
31 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-pdf/readme.md:
--------------------------------------------------------------------------------
1 | # pos-pdf
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | -------- | --------- | ----------- | -------- | ----------- |
12 | | `alt` | `alt` | | `string` | `undefined` |
13 | | `src` | `src` | | `string` | `undefined` |
14 |
15 |
16 | ## Events
17 |
18 | | Event | Description | Type |
19 | | ------------- | ----------- | ------------------ |
20 | | `pod-os:init` | | `CustomEvent` |
21 |
22 |
23 | ## Dependencies
24 |
25 | ### Used by
26 |
27 | - [pos-app-pdf-viewer](../../apps/pos-app-pdf-viewer)
28 |
29 | ### Depends on
30 |
31 | - ion-skeleton-text
32 | - ion-icon
33 |
34 | ### Graph
35 | ```mermaid
36 | graph TD;
37 | pos-pdf --> ion-skeleton-text
38 | pos-pdf --> ion-icon
39 | pos-app-pdf-viewer --> pos-pdf
40 | style pos-pdf fill:#f9f,stroke:#333,stroke-width:4px
41 | ```
42 |
43 | ----------------------------------------------
44 |
45 | *Built with [StencilJS](https://stenciljs.com/)*
46 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-predicate/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Properties
6 |
7 | | Property | Attribute | Description | Type | Default |
8 | | -------- | --------- | ----------- | -------- | ----------- |
9 | | `label` | `label` | | `string` | `undefined` |
10 | | `uri` | `uri` | | `string` | `undefined` |
11 |
12 |
13 | ## Dependencies
14 |
15 | ### Used by
16 |
17 | - [pos-literals](../pos-literals)
18 | - [pos-relations](../pos-relations)
19 | - [pos-reverse-relations](../pos-reverse-relations)
20 |
21 | ### Depends on
22 |
23 | - ion-icon
24 |
25 | ### Graph
26 | ```mermaid
27 | graph TD;
28 | pos-predicate --> ion-icon
29 | pos-literals --> pos-predicate
30 | pos-relations --> pos-predicate
31 | pos-reverse-relations --> pos-predicate
32 | style pos-predicate fill:#f9f,stroke:#333,stroke-width:4px
33 | ```
34 |
35 | ----------------------------------------------
36 |
37 | *Built with [StencilJS](https://stenciljs.com/)*
38 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-type-router/readme.md:
--------------------------------------------------------------------------------
1 | # pos-type-router
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Events
9 |
10 | | Event | Description | Type |
11 | | ----------------- | ----------- | ------------------ |
12 | | `pod-os:resource` | | `CustomEvent` |
13 |
14 |
15 | ## Dependencies
16 |
17 | ### Used by
18 |
19 | - [pos-app-browser](../../apps/pos-app-browser)
20 |
21 | ### Graph
22 | ```mermaid
23 | graph TD;
24 | pos-app-browser --> pos-type-router
25 | style pos-type-router fill:#f9f,stroke:#333,stroke-width:4px
26 | ```
27 |
28 | ----------------------------------------------
29 |
30 | *Built with [StencilJS](https://stenciljs.com/)*
31 |
--------------------------------------------------------------------------------
/docs/elements/components/pos-value/readme.md:
--------------------------------------------------------------------------------
1 | # pos-value
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Properties
9 |
10 | | Property | Attribute | Description | Type | Default |
11 | | ----------- | ----------- | ------------------------------------------ | -------- | ----------- |
12 | | `predicate` | `predicate` | URI of the predicate to get the value from | `string` | `undefined` |
13 |
14 |
15 | ## Events
16 |
17 | | Event | Description | Type |
18 | | ----------------- | ----------- | ------------------ |
19 | | `pod-os:resource` | | `CustomEvent` |
20 |
21 |
22 | ----------------------------------------------
23 |
24 | *Built with [StencilJS](https://stenciljs.com/)*
25 |
--------------------------------------------------------------------------------
/elements/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/elements/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | www/
3 | loader/
4 |
5 | *~
6 | *.sw[mnpcod]
7 | *.log
8 | *.lock
9 | *.tmp
10 | *.tmp.*
11 | log.txt
12 | *.sublime-project
13 | *.sublime-workspace
14 |
15 | .stencil/
16 | .idea/
17 | .vscode/
18 | .sass-cache/
19 | .versions/
20 | node_modules/
21 | $RECYCLE.BIN/
22 |
23 | .DS_Store
24 | Thumbs.db
25 | UserInterfaceState.xcuserstate
26 | .env
27 |
--------------------------------------------------------------------------------
/elements/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "jsxBracketSameLine": false,
5 | "jsxSingleQuote": false,
6 | "quoteProps": "consistent",
7 | "printWidth": 120,
8 | "semi": true,
9 | "singleQuote": true,
10 | "tabWidth": 2,
11 | "trailingComma": "all",
12 | "useTabs": false
13 | }
14 |
--------------------------------------------------------------------------------
/elements/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/elements/jest-setup.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pod-os/PodOS/e7e3a1b442384e157b558fc7369076bb89657a15/elements/jest-setup.ts
--------------------------------------------------------------------------------
/elements/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@stencil/core/testing',
3 | setupFilesAfterEnv: ['/jest-setup.ts'],
4 | };
5 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-app-dashboard.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: grid;
3 | grid-template-columns: repeat(auto-fit, minmax(var(--size-12), var(--size-96)));
4 | gap: var(--size-4);
5 | padding: var(--size-2);
6 | justify-content: center;
7 | align-items: start;
8 | justify-items: stretch;
9 | height: 100%;
10 | background: linear-gradient(230deg, rgb(251, 251, 255) 0%, rgb(215, 223, 252) 100%);
11 | }
12 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-app-dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-app-dashboard',
5 | styleUrl: 'pos-app-dashboard.css',
6 | shadow: true,
7 | })
8 | export class PosAppDashboard {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-example-resources/pos-example-resources.css:
--------------------------------------------------------------------------------
1 | :host {
2 | border: var(--size-px) solid var(--color-grey-100);
3 | border-radius: var(--radius-lg);
4 | box-shadow: var(--shadow-md);
5 | padding: var(--size-8);
6 | max-width: var(--size-96);
7 | background: var(--color-white);
8 | }
9 |
10 | .links {
11 | display: flex;
12 | flex-direction: column;
13 | gap: var(--size-3);
14 | max-width: 80vw;
15 | }
16 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-example-resources/pos-example-resources.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-example-resources',
5 | styleUrl: 'pos-example-resources.css',
6 | shadow: true,
7 | })
8 | export class PosExampleResources {
9 | render() {
10 | return (
11 |
12 |
13 |
Try these... 💡
14 |
No idea where to start? Try these example resources, and follow your nose 👃
15 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-getting-started/pos-getting-started.css:
--------------------------------------------------------------------------------
1 | :host {
2 | border: var(--size-px) solid var(--color-grey-100);
3 | border-radius: var(--radius-lg);
4 | box-shadow: var(--shadow-md);
5 | padding: var(--size-8);
6 | max-width: var(--size-96);
7 | background: var(--color-white);
8 | }
9 |
10 | .question {
11 | font-weight: var(--weight-semibold);
12 | color: var(--color-grey-900);
13 | }
14 |
15 | a {
16 | font-weight: var(--weight-bold);
17 | color: var(--pos-primary-color);
18 | }
19 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-dashboard/pos-getting-started/pos-getting-started.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-getting-started',
5 | styleUrl: 'pos-getting-started.css',
6 | shadow: true,
7 | })
8 | export class PosGettingStarted {
9 | render() {
10 | return (
11 |
12 |
13 |
Getting started 🚀
14 |
🔎 Enter a URL into the above navigation bar to browse through the web of data.
15 |
🔐 Sign in to access private resources on your Solid Pod or those of your friends or coworkers.
16 |
17 |
23 |
24 |
Want to dig deeper into PodOS?
25 |
26 | Learn more →
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-generic/pos-app-generic.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Host } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-app-generic',
5 | styleUrls: ['./pos-app-generic.css'],
6 | shadow: true,
7 | })
8 | export class PosAppGeneric {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
33 |
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-image-viewer/pos-app-image-viewer.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { PosAppImageViewer } from './pos-app-image-viewer';
3 |
4 | describe('pos-app-image-viewer', () => {
5 | it('is empty initially', async () => {
6 | const page = await newSpecPage({
7 | components: [PosAppImageViewer],
8 | html: ``,
9 | });
10 | expect(page.root).toEqualHtml(`
11 |
12 |
13 |
14 | `);
15 | });
16 |
17 | it('renders pos-image after resource is received', async () => {
18 | const page = await newSpecPage({
19 | components: [PosAppImageViewer],
20 | html: ``,
21 | supportsShadowDom: false,
22 | });
23 | await page.rootInstance.receiveResource({
24 | uri: 'https://resource.test/picture.png',
25 | });
26 | await page.waitForChanges();
27 | const image = page.root.querySelector('pos-image');
28 | expect(image).toEqualHtml('');
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-ldp-container/pos-app-ldp-container.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-app-ldp-container',
5 | })
6 | export class PosAppLdpContainer {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | All subjects
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-rdf-document/pos-app-rdf-document.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-app-rdf-document',
5 | })
6 | export class PosAppRdfDocument {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-settings/pos-app-settings.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: grid;
3 | grid-template-columns: repeat(auto-fit, minmax(var(--size-12), var(--size-96)));
4 | gap: var(--size-4);
5 | padding: var(--size-2);
6 | justify-content: center;
7 | align-items: start;
8 | justify-items: stretch;
9 | height: 100%;
10 | background: linear-gradient(230deg, rgb(251, 251, 255) 0%, rgb(215, 223, 252) 100%);
11 | }
12 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-settings/pos-app-settings.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-app-settings',
5 | styleUrl: 'pos-app-settings.css',
6 | shadow: true,
7 | })
8 | export class PosAppSettings {
9 | render() {
10 | return ;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/elements/src/apps/pos-app-settings/pos-setting-offline-cache/pos-setting-offline-cache.css:
--------------------------------------------------------------------------------
1 | :host {
2 | border: var(--size-px) solid var(--color-grey-100);
3 | border-radius: var(--radius-lg);
4 | box-shadow: var(--shadow-md);
5 | padding: var(--size-8);
6 | max-width: var(--size-96);
7 | background: var(--color-white);
8 | }
9 |
10 | svg {
11 | width: var(--size-8);
12 | }
13 |
14 | h2 {
15 | display: flex;
16 | align-items: center;
17 | }
18 |
19 | p {
20 | padding: var(--size-2);
21 | }
22 |
23 | .info {
24 | background-color: var(--color-blue-200);
25 | }
26 | .warn {
27 | background-color: var(--color-yellow-200);
28 | }
29 |
--------------------------------------------------------------------------------
/elements/src/cache/NavigatorOnlineStatus.ts:
--------------------------------------------------------------------------------
1 | import { OnlineStatus } from '@pod-os/core';
2 |
3 | export class NavigatorOnlineStatus implements OnlineStatus {
4 | isOnline(): boolean {
5 | return navigator.onLine;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/elements/src/components/broken-file/BrokenFile.tsx:
--------------------------------------------------------------------------------
1 | import { BrokenFile as BrokenFileData, HttpStatus } from '@pod-os/core';
2 | import { h } from '@stencil/core';
3 |
4 | interface Props {
5 | file: BrokenFileData;
6 | }
7 |
8 | export const BrokenFile = ({ file }: Props) => {
9 | const iconName = iconForStatus(file.status);
10 | return (
11 |
20 | );
21 | };
22 |
23 | function iconForStatus(status: HttpStatus): string {
24 | switch (status.code) {
25 | case 401:
26 | case 403:
27 | return 'lock-closed-outline';
28 | case 404:
29 | return 'help-circle-outline';
30 | default:
31 | return 'alert-circle-outline';
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/elements/src/components/events/PodOsAware.ts:
--------------------------------------------------------------------------------
1 | import { PodOS } from '@pod-os/core';
2 | import { EventEmitter } from '@stencil/core';
3 |
4 | export type PodOsReceiver = (os: PodOS) => void;
5 | export type PodOsEventEmitter = EventEmitter;
6 |
7 | export interface PodOsAware {
8 | subscribePodOs: PodOsEventEmitter;
9 | receivePodOs: PodOsReceiver;
10 | }
11 |
12 | export function subscribePodOs(component: PodOsAware) {
13 | component.subscribePodOs.emit(component.receivePodOs);
14 | }
15 |
--------------------------------------------------------------------------------
/elements/src/components/events/ResourceAware.ts:
--------------------------------------------------------------------------------
1 | import {Thing} from "@pod-os/core";
2 | import { EventEmitter } from '@stencil/core';
3 |
4 | export type ResourceReceiver = (resource: Thing) => void;
5 | export type ResourceEventEmitter = EventEmitter;
6 |
7 | export interface ResourceAware {
8 | subscribeResource: ResourceEventEmitter;
9 | receiveResource: ResourceReceiver;
10 | }
11 |
12 | export function subscribeResource(component: ResourceAware) {
13 | component.subscribeResource.emit(component.receiveResource);
14 | }
15 |
--------------------------------------------------------------------------------
/elements/src/components/pos-add-literal-value/pos-add-literal-value.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | align-items: center;
4 | gap: 0.2rem;
5 | }
6 |
--------------------------------------------------------------------------------
/elements/src/components/pos-add-new-thing/pos-add-new-thing.css:
--------------------------------------------------------------------------------
1 | :host {
2 | font-family: var(--font-sans);
3 | display: block;
4 | }
5 |
6 | button#new {
7 | cursor: pointer;
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | border: none;
12 | width: var(--scale-5);
13 | height: var(--scale-5);
14 | background-color: var(--pos-primary-color);
15 | color: var(--color-grey-50);
16 | font-size: var(--scale-6);
17 | border-radius: var(--radius-xs);
18 | }
19 |
20 | button#new:hover, button#new:focus {
21 | outline: none;
22 | filter: brightness(110%);
23 | box-shadow: var(--shadow-sm);
24 | }
25 |
26 | pos-new-thing-form {
27 | margin: var(--scale-3);
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/elements/src/components/pos-add-new-thing/pos-add-new-thing.tsx:
--------------------------------------------------------------------------------
1 | import { Component, Host, h, Prop } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-add-new-thing',
5 | styleUrl: 'pos-add-new-thing.css',
6 | shadow: true,
7 | })
8 | export class PosAddNewThing {
9 | @Prop() referenceUri!: string;
10 |
11 | private dialog: HTMLPosDialogElement;
12 |
13 | openDialog() {
14 | this.dialog.showModal();
15 | }
16 |
17 | render() {
18 | return (
19 |
20 |
23 | (this.dialog = el as HTMLPosDialogElement)}>
24 | Add a new thing
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/elements/src/components/pos-container-contents/pos-container-contents.css:
--------------------------------------------------------------------------------
1 | ul {
2 | list-style: none;
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | li {
8 | padding: 0;
9 | margin: 0;
10 | }
--------------------------------------------------------------------------------
/elements/src/components/pos-container-contents/selectIconForTypes.spec.tsx:
--------------------------------------------------------------------------------
1 | import { selectIconForTypes } from './selectIconForTypes';
2 |
3 | describe('select icon for types', () => {
4 | it('selects a folder icon for containers', () => {
5 | const icon = selectIconForTypes([
6 | {
7 | uri: 'http://www.w3.org/ns/ldp#Resource',
8 | label: 'irrelevant here',
9 | },
10 | {
11 | uri: 'http://www.w3.org/ns/ldp#Container',
12 | label: 'irrelevant here',
13 | },
14 | ]);
15 | expect(icon).toEqual('folder-outline');
16 | });
17 |
18 | it('selects a file icon for other ldp resources', () => {
19 | const icon = selectIconForTypes([
20 | {
21 | uri: 'http://www.w3.org/ns/ldp#Resource',
22 | label: 'irrelevant here',
23 | },
24 | ]);
25 | expect(icon).toEqual('document-outline');
26 | });
27 |
28 | it('selects question mark icon if types are empty', () => {
29 | const icon = selectIconForTypes([]);
30 | expect(icon).toEqual('help-outline');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-container-contents/selectIconForTypes.ts:
--------------------------------------------------------------------------------
1 | import { RdfType } from '@pod-os/core';
2 |
3 | export function selectIconForTypes(types: RdfType[]) {
4 | if (containsType(types, 'http://www.w3.org/ns/ldp#Container')) {
5 | return 'folder-outline';
6 | } else if (containsType(types, 'http://www.w3.org/ns/ldp#Resource')) {
7 | return 'document-outline';
8 | } else {
9 | return 'help-outline';
10 | }
11 | }
12 |
13 | // TODO: remove duplication with pos-type-router/selectAppForTypes
14 | function containsType(types: RdfType[], typeUri: string) {
15 | return types.some(type => type.uri === typeUri);
16 | }
17 |
--------------------------------------------------------------------------------
/elements/src/components/pos-description/pos-description.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { PosDescription } from './pos-description';
3 |
4 | describe('pos-description', () => {
5 | it('is empty initially', async () => {
6 | const page = await newSpecPage({
7 | components: [PosDescription],
8 | html: ``,
9 | });
10 | expect(page.root).toEqualHtml(`
11 |
12 |
13 |
14 | `);
15 | });
16 |
17 | it('renders description from resource', async () => {
18 | const page = await newSpecPage({
19 | components: [PosDescription],
20 | html: ``,
21 | });
22 | await page.rootInstance.receiveResource({
23 | description: () => 'Test Resource',
24 | });
25 | await page.waitForChanges();
26 | expect(page.root).toEqualHtml(`
27 |
28 | Test Resource
29 |
30 | `);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-description/pos-description.tsx:
--------------------------------------------------------------------------------
1 | import { Thing } from '@pod-os/core';
2 | import { Component, Event, EventEmitter, State } from '@stencil/core';
3 | import { ResourceAware, subscribeResource } from '../events/ResourceAware';
4 |
5 | @Component({
6 | tag: 'pos-description',
7 | shadow: true,
8 | })
9 | export class PosDescription implements ResourceAware {
10 | @State() resource: Thing;
11 |
12 | @Event({ eventName: 'pod-os:resource' }) subscribeResource: EventEmitter;
13 |
14 | componentWillLoad() {
15 | subscribeResource(this);
16 | }
17 |
18 | receiveResource = (resource: Thing) => {
19 | this.resource = resource;
20 | };
21 |
22 | render() {
23 | return this.resource ? this.resource.description() : null;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/elements/src/components/pos-dialog/pos-dialog.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | dialog {
4 | background-color: var(--pos-background-color);
5 | border: none;
6 | border-radius: var(--radius-md);
7 | box-shadow: var(--shadow-md);
8 | max-width: var(--width-xs);
9 | }
10 |
11 | dialog header > :first-child {
12 | /* This is the title slot*/
13 | flex-grow: 1;
14 | font-weight: var(--weight-light);
15 | font-size: var(--scale-2);
16 | font-family: var(--font-sans);
17 | margin: 0;
18 | }
19 |
20 | dialog header {
21 | display: flex;
22 | flex-direction: row;
23 | justify-content: space-between;
24 | align-items: center;
25 | gap: var(--scale-0);
26 | border-bottom-style: inset;
27 | padding: 0 0 var(--scale-0) 0;
28 | }
29 |
30 | dialog > :last-child {
31 | /* This is the content slot*/
32 | display: block;
33 | margin-top: var(--scale-3);
34 | }
35 |
36 | button#close {
37 | cursor: pointer;
38 | display: flex;
39 | align-items: center;
40 | justify-content: center;
41 | border: none;
42 | background: none;
43 | font-size: var(--scale-3);
44 | color: var(--color-grey-500);
45 | }
46 |
47 | button#close:hover {
48 | color: var(--color-grey-800);
49 | }
50 |
--------------------------------------------------------------------------------
/elements/src/components/pos-dialog/pos-dialog.tsx:
--------------------------------------------------------------------------------
1 | import { Component, Host, h, Method } from '@stencil/core';
2 |
3 | /**
4 | * Styled wrapper around native dialog element, with slots `title` and `content`
5 | */
6 | @Component({
7 | tag: 'pos-dialog',
8 | styleUrl: 'pos-dialog.css',
9 | shadow: false, // shadow dom prevents the html dialog from working normally (autofocus, close on submit)
10 | })
11 | export class PosDialog {
12 | private dialog: HTMLDialogElement;
13 |
14 | @Method()
15 | async showModal() {
16 | this.dialog.showModal();
17 | }
18 |
19 | @Method()
20 | async close() {
21 | this.dialog.close();
22 | }
23 |
24 | render() {
25 | return (
26 |
27 |
36 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/elements/src/components/pos-document/pos-document.css:
--------------------------------------------------------------------------------
1 |
2 | :host {
3 | }
4 |
5 | iframe {
6 | width: 100%;
7 | height: 100vh;
8 | }
9 |
10 | /* consolidate with styles from pos-image */
11 | .error {
12 | display: flex;
13 | opacity: 0.8;
14 | background: repeating-linear-gradient( -45deg, rgba(150, 0, 0, 0.1), rgba(150, 0, 0, 0.1) 10px, #fff 5px, #fff 25px );
15 | flex-direction: column;
16 | border: 1px solid red;
17 | color: black;
18 | align-items: center;
19 | justify-content: center;
20 | word-break: break-all;
21 | padding: 1rem;
22 | box-sizing: border-box;
23 | }
24 |
25 | .error ion-icon {
26 | color: #282828;
27 | --ionicon-stroke-width: calc(var(--width) / 5) ;
28 | font-size: calc(var(--width) / 2)
29 | }
30 |
31 | a {
32 | text-decoration: none;
33 | width: var(--width);
34 | height: var(--height);
35 | }
36 |
37 | .code {
38 | font-weight: bold;
39 | font-size: calc(var(--width) / 8);
40 | }
41 |
42 | .text {
43 | font-size: calc(var(--width) / 20)
44 | }
45 |
--------------------------------------------------------------------------------
/elements/src/components/pos-error-toast/test/pos-error-toast.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 |
3 | import { PosErrorToast } from '../pos-error-toast';
4 |
5 | describe('pos-error-toast', () => {
6 | it('renders its children', async () => {
7 | const page = await newSpecPage({
8 | components: [PosErrorToast],
9 | supportsShadowDom: false,
10 | html: `Slot value`,
11 | });
12 | expect(page.root).toEqualHtml(`
13 |
14 |
15 |
16 |
17 | Slot value
18 |
19 | `);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/elements/src/components/pos-internal-router/pos-internal-router.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { PosInternalRouter } from './pos-internal-router';
3 |
4 | describe('pos-internal-router', () => {
5 | it('renders the dashboard by default', async () => {
6 | const page = await newSpecPage({
7 | components: [PosInternalRouter],
8 | html: ``,
9 | });
10 |
11 | expect(page.root).toEqualHtml(`
12 |
13 |
14 |
15 | `);
16 | });
17 |
18 | it('renders local settings', async () => {
19 | const page = await newSpecPage({
20 | components: [PosInternalRouter],
21 | html: ``,
22 | });
23 |
24 | expect(page.root).toEqualHtml(`
25 |
26 |
27 |
28 | `);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/elements/src/components/pos-internal-router/pos-internal-router.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Prop } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-internal-router',
5 | })
6 | export class PosInternalRouter {
7 | @Prop()
8 | uri: string = 'pod-os:dashboard';
9 |
10 | render() {
11 | return this.uri === 'pod-os:settings' ? (
12 |
13 | ) : (
14 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/elements/src/components/pos-label/pos-label.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { PosLabel } from './pos-label';
3 |
4 | describe('pos-label', () => {
5 | it('is empty initially', async () => {
6 | const page = await newSpecPage({
7 | components: [PosLabel],
8 | html: ``,
9 | });
10 | expect(page.root).toEqualHtml(`
11 |
12 |
13 |
14 | `);
15 | });
16 |
17 | it('renders label from resource', async () => {
18 | const page = await newSpecPage({
19 | components: [PosLabel],
20 | html: ``,
21 | });
22 | await page.rootInstance.receiveResource({
23 | label: () => 'Test Resource',
24 | });
25 | await page.waitForChanges();
26 | expect(page.root).toEqualHtml(`
27 |
28 | Test Resource
29 |
30 | `);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-label/pos-label.tsx:
--------------------------------------------------------------------------------
1 | import { Thing } from '@pod-os/core';
2 | import { Component, Event, State } from '@stencil/core';
3 | import { ResourceAware, ResourceEventEmitter, subscribeResource } from '../events/ResourceAware';
4 |
5 | @Component({
6 | tag: 'pos-label',
7 | shadow: true,
8 | })
9 | export class PosLabel implements ResourceAware {
10 | @State() resource: Thing;
11 |
12 | @Event({ eventName: 'pod-os:resource' })
13 | subscribeResource: ResourceEventEmitter;
14 |
15 | componentWillLoad() {
16 | subscribeResource(this);
17 | }
18 |
19 | receiveResource = (resource: Thing) => {
20 | this.resource = resource;
21 | };
22 |
23 | render() {
24 | return this.resource ? this.resource.label() : null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/elements/src/components/pos-literals/pos-literals.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --background-base-color: var(--color-grey-200);
3 | --background-color-even: hsl(from var(--background-base-color) h s calc(l + 7));
4 | --background-color-odd: hsl(from var(--background-base-color) h s calc(l + 10));
5 | --border-color: var(--background-base-color);
6 | }
7 |
8 | dd {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
13 | dl {
14 | padding: 0;
15 | margin: 0;
16 | display: flex;
17 | flex-direction: column;
18 | gap: var(--size-1);
19 | }
20 |
21 | dt {
22 | margin-bottom: var(--size-1);
23 | }
24 |
25 | .predicate-values:nth-child(odd) {
26 | background-color: var(--background-color-odd);
27 | }
28 |
29 | .predicate-values:nth-child(even) {
30 | background-color: var(--background-color-even);
31 | }
32 |
33 | .predicate-values {
34 | display: flex;
35 | flex-direction: column;
36 | border: var(--size-px) solid var(--border-color);
37 | padding: var(--size-2);
38 | gap: var(--size-1);
39 |
40 | .values {
41 | display: flex;
42 | flex-direction: column;
43 | gap: var(--size-2);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/elements/src/components/pos-login-form/pos-login-form.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | margin: 0
4 | }
5 |
6 | form {
7 | display: flex;
8 | flex-direction: column;
9 | gap: var(--size-4);
10 | }
11 |
12 | input {
13 | outline: var(--pos-input-outline);
14 | padding: var(--scale-000);
15 | border: none;
16 | border-radius: var(--radius-xs);
17 | width: var(--size-full);
18 | box-sizing: border-box;
19 | }
20 |
21 | input:focus-within {
22 | outline: var(--pos-input-focus-outline);
23 | }
24 |
25 | input#login {
26 | outline: none;
27 | box-shadow: var(--shadow-sm);
28 | cursor: pointer;
29 | color: var(--pos-primary-text-color);
30 | background-color: var(--pos-primary-color);
31 | }
32 |
33 | input#login:disabled {
34 | cursor: default;
35 | color: var(--pos-disabled-text-color);
36 | background-color: var(--pos-disabled-color);
37 | box-shadow: none
38 | }
39 |
40 | input#login:hover:not(:disabled), input#login:focus {
41 | filter: brightness(110%);
42 | box-shadow: var(--shadow-md);
43 | }
44 |
--------------------------------------------------------------------------------
/elements/src/components/pos-login/pos-login.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | align-items: center;
4 | gap: var(--size-2);
5 | }
6 | .user-data {
7 | align-items: center;
8 | display: inline-flex;
9 | gap: 10px;
10 | }
11 | .user-data pos-picture {
12 | --width: 40px;
13 | --height: 40px;
14 | --border-radius: 50%;
15 | }
16 |
17 | pos-login-form {
18 | margin: var(--size-2);
19 | }
20 |
21 | dialog {
22 | margin-top: var(--size-10);
23 | }
24 |
25 | button#login,
26 | button#logout {
27 | outline: none;
28 | font-weight: var(--weight-bold);
29 | letter-spacing: var(--letter-xl);
30 | border: none;
31 | box-sizing: border-box;
32 | border-radius: var(--radius-xs);
33 | padding: var(--size-2);
34 | box-shadow: var(--shadow-sm);
35 | color: var(--pos-primary-text-color);
36 | background-color: var(--pos-primary-color);
37 | }
38 |
39 | button#login:focus-within,
40 | button#logout:focus-within {
41 | outline: var(--pos-input-focus-outline);
42 | }
43 |
44 | button#login:focus,
45 | button#login:hover,
46 | button#logout:focus,
47 | button#logout:hover {
48 | filter: brightness(110%);
49 | box-shadow: var(--shadow-md);
50 | }
51 |
--------------------------------------------------------------------------------
/elements/src/components/pos-navigation-bar/pos-navigation-bar.css:
--------------------------------------------------------------------------------
1 | :host {
2 | }
3 |
4 | .suggestions ol {
5 | border: 1px solid var(--color-grey-200);
6 | display: flex;
7 | flex-direction: column;
8 | position: absolute;
9 | margin: 0;
10 | padding: 0;
11 | z-index: var(--layer-top);
12 | list-style-type: none;
13 | box-shadow: var(--shadow-xl);
14 | }
15 |
16 | .suggestions {
17 | position: relative;
18 | li {
19 | padding: 1rem;
20 | background-color: white;
21 | pos-rich-link {
22 | --background-color: inherit;
23 | }
24 | &.selected {
25 | background-color: var(--color-blue);
26 | &:hover {
27 | background-color: var(--color-blue-700);
28 | }
29 | }
30 | &:hover {
31 | background-color: var(--color-grey-200);
32 | }
33 | }
34 | }
35 |
36 | .suggestions li.selected pos-rich-link {
37 | --label-color: white;
38 | --description-color: var(--color-grey-200);
39 | --uri-color: var(--color-grey-200);
40 | }
41 |
42 | ion-searchbar {
43 | width: 100%;
44 | }
45 |
46 | form {
47 | display: flex;
48 | flex-direction: row;
49 | align-items: center;
50 | }
51 |
52 | .bar {
53 | flex-grow: 1;
54 | }
55 |
--------------------------------------------------------------------------------
/elements/src/components/pos-picture/pos-picture.css:
--------------------------------------------------------------------------------
1 | :host {
2 | /**
3 | * @prop --width: Width of the picture
4 | * @prop --height: Height of the picture
5 | * @prop --border-radius: Border radius of the picture
6 | * @prop --object-fit: CSS object-fit of the picture
7 | */
8 | --width: 300px;
9 | --height: 300px;
10 | --border-radius: var(--border-radius, 0);
11 | --object-fit: var(--object-fit, cover);
12 | }
13 |
--------------------------------------------------------------------------------
/elements/src/components/pos-picture/pos-picture.tsx:
--------------------------------------------------------------------------------
1 | import { Component, Event, EventEmitter, State, h, Prop } from '@stencil/core';
2 | import { Thing } from '@pod-os/core';
3 | import { ResourceAware, subscribeResource } from '../events/ResourceAware';
4 |
5 | @Component({
6 | tag: 'pos-picture',
7 | shadow: true,
8 | styleUrl: 'pos-picture.css',
9 | })
10 | export class PosPicture implements ResourceAware {
11 | /**
12 | * Use a blurred version of the image as its own background, if the image is scaled down to fit into the container.
13 | */
14 | @Prop() blurredBackground: boolean = false;
15 |
16 | @State() resource: Thing;
17 |
18 | @Event({ eventName: 'pod-os:resource' }) subscribeResource: EventEmitter;
19 |
20 | componentWillLoad() {
21 | subscribeResource(this);
22 | }
23 |
24 | receiveResource = (resource: Thing) => {
25 | this.resource = resource;
26 | };
27 |
28 | render() {
29 | const pic = this.resource ? this.resource.picture() : null;
30 | if (!pic) return null;
31 | return ;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/elements/src/components/pos-predicate/pos-predicate.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --text-color: var(--pos-primary-color);
3 | font-family: var(--font-sans);
4 | }
5 |
6 | button {
7 | line-height: var(--scale-0);
8 | font-family: inherit;
9 | margin: 0;
10 | padding: 0;
11 | display: flex;
12 | background: none;
13 | border: none;
14 | cursor: pointer;
15 | }
16 |
17 | button,
18 | a {
19 | color: var(--text-color);
20 | font-weight: var(--weight-light);
21 | line-height: var(--scale-0);
22 | font-size: var(--scale-0);
23 | font-family: inherit;
24 | }
25 |
26 | .container {
27 | display: flex;
28 | gap: var(--size-1);
29 | flex-direction: row;
30 | align-items: center;
31 | line-height: var(--scale-0);
32 | }
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-predicate/pos-predicate.tsx:
--------------------------------------------------------------------------------
1 | import { Component, h, Prop, State } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-predicate',
5 | shadow: true,
6 | styleUrl: './pos-predicate.css',
7 | })
8 | export class PosPredicate {
9 | @Prop()
10 | uri: string;
11 |
12 | @Prop()
13 | label: string;
14 |
15 | @State()
16 | expanded: boolean = false;
17 |
18 | render() {
19 | if (this.expanded) {
20 | return (
21 |
22 |
{this.uri}
23 |
26 |
27 | );
28 | } else {
29 | return (
30 |
33 | );
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/elements/src/components/pos-relations/pos-relations.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --background-base-color: var(--color-grey-200);
3 | --background-color-even: hsl(from var(--background-base-color) h s calc(l + 7));
4 | --background-color-odd: hsl(from var(--background-base-color) h s calc(l + 10));
5 | --border-color: var(--background-base-color);
6 | }
7 |
8 | dd {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
13 | dl {
14 | display: flex;
15 | flex-direction: column;
16 | gap: var(--size-1);
17 | padding: 0;
18 | margin: 0;
19 | }
20 |
21 | dt {
22 | margin-bottom: var(--size-1);
23 | }
24 |
25 | .predicate-values:nth-child(odd) {
26 | background-color: var(--background-color-odd);
27 | }
28 |
29 | .predicate-values:nth-child(even) {
30 | background-color: var(--background-color-even);
31 | }
32 |
33 | .predicate-values {
34 | display: flex;
35 | flex-direction: column;
36 | border: var(--size-px) solid var(--border-color);
37 | padding: var(--size-2);
38 | gap: var(--size-1);
39 |
40 | .values {
41 | display: flex;
42 | flex-direction: column;
43 | gap: var(--size-3);
44 | }
45 | }
46 |
47 | pos-rich-link {
48 | --background-color: inherit;
49 | }
50 |
--------------------------------------------------------------------------------
/elements/src/components/pos-reverse-relations/pos-reverse-relations.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --background-base-color: var(--color-grey-200);
3 | --background-color-even: hsl(from var(--background-base-color) h s calc(l + 7));
4 | --background-color-odd: hsl(from var(--background-base-color) h s calc(l + 10));
5 | --border-color: var(--background-base-color);
6 | }
7 |
8 | dd {
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
13 | dl {
14 | display: flex;
15 | flex-direction: column;
16 | gap: var(--size-1);
17 | padding: 0;
18 | margin: 0;
19 | }
20 |
21 | dt {
22 | margin-bottom: var(--size-1);
23 | }
24 |
25 | .predicate-values:nth-child(odd) {
26 | background-color: var(--background-color-odd);
27 | }
28 |
29 | .predicate-values:nth-child(even) {
30 | background-color: var(--background-color-even);
31 | }
32 |
33 | .predicate-values {
34 | display: flex;
35 | flex-direction: column;
36 | border: var(--size-px) solid var(--border-color);
37 | padding: var(--size-2);
38 | gap: var(--size-1);
39 | .values {
40 | display: flex;
41 | flex-direction: column;
42 | gap: var(--size-3);
43 | }
44 | }
45 |
46 | pos-rich-link {
47 | --background-color: inherit;
48 | }
49 |
--------------------------------------------------------------------------------
/elements/src/components/pos-rich-link/pos-rich-link.tsx:
--------------------------------------------------------------------------------
1 | import { Component, Event, EventEmitter, h, Prop } from '@stencil/core';
2 |
3 | @Component({
4 | tag: 'pos-rich-link',
5 | shadow: true,
6 | styleUrl: 'pos-rich-link.css',
7 | })
8 | export class PosRichLink {
9 | @Prop() uri: string;
10 |
11 | @Event({ eventName: 'pod-os:link' }) linkEmitter: EventEmitter;
12 |
13 | render() {
14 | return (
15 |
16 |
17 | {
20 | e.preventDefault();
21 | this.linkEmitter.emit(this.uri);
22 | }}
23 | >
24 |
25 |
26 | {new URL(this.uri).host}
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-router/pos-router.css:
--------------------------------------------------------------------------------
1 | .toolbar {
2 | display: flex;
3 | flex-direction: row;
4 | gap: 0;
5 | align-items: center;
6 | margin-left: 0.5rem;
7 | }
8 |
9 | pos-navigation-bar {
10 | flex-grow: 1;
11 | }
12 |
--------------------------------------------------------------------------------
/elements/src/components/pos-select-term/pos-select-term.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | }
4 |
5 | input {
6 | border: none;
7 | padding-top: 1rem;
8 | padding-bottom: 1rem;
9 | }
10 |
11 | input:focus {
12 | outline: none;
13 | }
14 |
--------------------------------------------------------------------------------
/elements/src/components/pos-subjects/pos-subjects.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --background-base-color: var(--color-grey-200);
3 | --background-color-even: hsl(from var(--background-base-color) h s calc(l + 7));
4 | --background-color-odd: hsl(from var(--background-base-color) h s calc(l + 10));
5 | --border-color: var(--background-base-color);
6 | }
7 |
8 | ul {
9 | display: flex;
10 | flex-direction: column;
11 | gap: var(--size-2);
12 |
13 | margin: 0;
14 | padding: 0;
15 |
16 | li {
17 | border: var(--size-px) solid var(--border-color);
18 | &:nth-child(even) {
19 | background-color: var(--background-color-even);
20 | }
21 | &:nth-child(odd) {
22 | background-color: var(--background-color-odd);
23 | }
24 | padding: var(--size-4);
25 | list-style-type: none;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/elements/src/components/pos-subjects/pos-subjects.tsx:
--------------------------------------------------------------------------------
1 | import { RdfDocument, Subject, Thing } from '@pod-os/core';
2 | import { Component, Event, EventEmitter, h, State } from '@stencil/core';
3 | import { ResourceAware, subscribeResource } from '../events/ResourceAware';
4 |
5 | @Component({
6 | tag: 'pos-subjects',
7 | shadow: true,
8 | styleUrl: 'pos-subjects.css',
9 | })
10 | export class PosSubjects implements ResourceAware {
11 | @State() data: Subject[] = [];
12 |
13 | @Event({ eventName: 'pod-os:resource' })
14 | subscribeResource: EventEmitter;
15 |
16 | componentWillLoad() {
17 | subscribeResource(this);
18 | }
19 |
20 | receiveResource = (resource: Thing) => {
21 | const doc = resource.assume(RdfDocument);
22 | this.data = doc.subjects();
23 | };
24 |
25 | render() {
26 | const items = this.data.map(it => (
27 |
28 |
29 |
30 | ));
31 | return this.data.length > 0 ? : null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/elements/src/components/pos-type-badges/pos-type-badges.css:
--------------------------------------------------------------------------------
1 | .types {
2 | display: flex;
3 | gap: var(--size-1);
4 | }
5 |
6 | .expanded {
7 | flex-direction: column;
8 | justify-content: flex-start;
9 | align-items: flex-start;
10 | }
11 |
12 | ion-badge {
13 | color: #333;
14 | background: #eee;
15 | }
16 |
17 | ion-badge.toggle {
18 | cursor: pointer;
19 | }
20 |
21 | ion-badge.toggle:hover {
22 | background: #ddd;
23 | }
24 |
--------------------------------------------------------------------------------
/elements/src/components/pos-type-router/pos-type-router.tsx:
--------------------------------------------------------------------------------
1 | import { RdfType, Thing } from '@pod-os/core';
2 | import { Component, Event, EventEmitter, h, State } from '@stencil/core';
3 | import { ResourceAware, subscribeResource } from '../events/ResourceAware';
4 | import { selectAppForTypes } from './selectAppForTypes';
5 |
6 | @Component({
7 | tag: 'pos-type-router',
8 | shadow: true,
9 | })
10 | export class PosTypeRouter implements ResourceAware {
11 | @State() types: RdfType[];
12 |
13 | @Event({ eventName: 'pod-os:resource' })
14 | subscribeResource: EventEmitter;
15 |
16 | componentWillLoad() {
17 | subscribeResource(this);
18 | }
19 |
20 | receiveResource = (resource: Thing) => {
21 | this.types = resource.types();
22 | };
23 |
24 | render() {
25 | return this.types ? this.renderApp() : null;
26 | }
27 |
28 | private renderApp() {
29 | const App = selectAppForTypes(this.types);
30 | return ;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-value/pos-value.spec.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { PosValue } from './pos-value';
3 |
4 | describe('pos-value', () => {
5 | it('is empty initially', async () => {
6 | const page = await newSpecPage({
7 | components: [PosValue],
8 | html: ``,
9 | });
10 | expect(page.root).toEqualHtml(`
11 |
12 |
13 |
14 | `);
15 | });
16 |
17 | it('renders property value from resource', async () => {
18 | const page = await newSpecPage({
19 | components: [PosValue],
20 | html: ``,
21 | });
22 | await page.rootInstance.receiveResource({
23 | anyValue: uri => `value of ${uri}`,
24 | });
25 | await page.waitForChanges();
26 | expect(page.root).toEqualHtml(`
27 |
28 | value of https://vocab.example/#term
29 | >
30 | `);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/elements/src/components/pos-value/pos-value.tsx:
--------------------------------------------------------------------------------
1 | import { Thing } from '@pod-os/core';
2 | import { Component, Event, Prop, State } from '@stencil/core';
3 | import { ResourceAware, ResourceEventEmitter, subscribeResource } from '../events/ResourceAware';
4 |
5 | @Component({
6 | tag: 'pos-value',
7 | shadow: true,
8 | })
9 | export class PosValue implements ResourceAware {
10 | /**
11 | * URI of the predicate to get the value from
12 | */
13 | @Prop() predicate: string;
14 | @State() resource: Thing;
15 |
16 | @Event({ eventName: 'pod-os:resource' })
17 | subscribeResource: ResourceEventEmitter;
18 |
19 | componentWillLoad() {
20 | subscribeResource(this);
21 | }
22 |
23 | receiveResource = (resource: Thing) => {
24 | this.resource = resource;
25 | };
26 |
27 | render() {
28 | return this.resource ? this.resource.anyValue(this.predicate) : null;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/elements/src/global.css:
--------------------------------------------------------------------------------
1 | /* Core CSS required for Ionic components to work properly */
2 |
3 | @import '~@ionic/core/css/ionic.bundle.css';
4 |
5 | @import '~pollen-css/dist/pollen.css';
6 |
7 | body {
8 | height: 100dvh;
9 | margin: 0;
10 | padding: 0;
11 | font-family: sans-serif;
12 | }
13 |
14 | :root {
15 | --pos-primary-text-color: var(--color-grey-50);
16 | --pos-normal-text-color: var(--color-grey-900);
17 | --pos-disabled-text-color: var(--color-grey-600);
18 | --pos-primary-color: var(--color-blue-500);
19 | --pos-disabled-color: var(--color-grey-100);
20 | --pos-background-color: var(--color-grey-50);
21 | --pos-border-color: var(--color-grey-200);
22 | --pos-input-outline: var(--size-px) solid var(--color-grey-700);
23 | --pos-input-focus-outline: var(--size-px) solid var(--pos-primary-color);
24 | --pos-border-solid: var(--size-px) solid var(--pos-border-color);
25 | }
26 |
--------------------------------------------------------------------------------
/elements/src/global.ts:
--------------------------------------------------------------------------------
1 | import '@ionic/core';
2 |
--------------------------------------------------------------------------------
/elements/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PodOS Browser (dev standalone)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/elements/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Components, JSX } from './components';
2 |
--------------------------------------------------------------------------------
/elements/src/pod-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PodOS Browser (dev pod)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/elements/src/pod-os.ts:
--------------------------------------------------------------------------------
1 | import { NoOfflineCache, PodOS } from '@pod-os/core';
2 | import { IndexedDbOfflineCache } from './cache/IndexedDbOfflineCache';
3 | import { NavigatorOnlineStatus } from './cache/NavigatorOnlineStatus';
4 | import { LocalSettings } from './store/settings';
5 |
6 | export const createPodOS = (settings: LocalSettings): PodOS => {
7 | return new PodOS({
8 | offlineCache: settings.offlineCache ? new IndexedDbOfflineCache() : new NoOfflineCache(),
9 | onlineStatus: new NavigatorOnlineStatus(),
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/elements/src/registerSW.js:
--------------------------------------------------------------------------------
1 | if ('serviceWorker' in navigator && location.protocol !== 'file:') {
2 | window.addEventListener('load', () => {
3 | navigator.serviceWorker.register('service-worker-localhost.js').catch(error => {
4 | console.error('Service worker registration failed:', error);
5 | });
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/elements/src/service-worker-localhost.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the service worker registered for local development
3 | */
4 |
5 | importScripts('https://cdn.jsdelivr.net/npm/@pod-os/service-worker@latest/lib/index.js');
6 |
7 | PodOsServiceWorker.setupServiceWorker(self, 'pod-os-dev-server', 'http://localhost:3333/build/', [
8 | '/',
9 | './index.html',
10 | './registerSW.js',
11 | ]);
12 |
--------------------------------------------------------------------------------
/elements/src/store/session.ts:
--------------------------------------------------------------------------------
1 | import { WebIdProfile } from '@pod-os/core';
2 | import { createStore } from '@stencil/store';
3 |
4 | const store = createStore({
5 | isLoggedIn: false,
6 | webId: '',
7 | profile: null as WebIdProfile
8 | });
9 |
10 | export default store;
11 |
--------------------------------------------------------------------------------
/elements/src/store/settings.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from '@stencil/store';
2 |
3 | export interface LocalSettings {
4 | offlineCache: boolean;
5 | }
6 |
7 | const storedSettings = localStorage.getItem('settings');
8 | const initialSettings = storedSettings
9 | ? JSON.parse(storedSettings)
10 | : {
11 | offlineCache: false,
12 | };
13 |
14 | export const localSettings = createStore(initialSettings);
15 |
16 | persistChanges();
17 | syncChangesAcrossTabs();
18 |
19 | function persistChanges() {
20 | localSettings.on('set', () => {
21 | const snapshot = JSON.stringify(localSettings.state);
22 | localStorage.setItem('settings', snapshot);
23 | });
24 | }
25 | function syncChangesAcrossTabs() {
26 | window.addEventListener('storage', event => {
27 | if (event.key === 'settings' && event.newValue) {
28 | const newSettings: LocalSettings = JSON.parse(event.newValue);
29 | localSettings.state.offlineCache = newSettings.offlineCache;
30 | }
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/elements/src/test/TestComponent.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from '@stencil/core';
2 |
3 | @Component({ tag: 'test-component' })
4 | export class TestComponent {}
5 |
--------------------------------------------------------------------------------
/elements/src/test/mockSessionStore.ts:
--------------------------------------------------------------------------------
1 | jest.mock('../store/session');
2 |
3 | import session from '../store/session';
4 |
5 | export function mockSessionStore() {
6 | const mock = {
7 | sessionChanged: undefined as unknown,
8 | };
9 | // @ts-ignore
10 | session.onChange = (prop, callback) => {
11 | if (prop === 'isLoggedIn') {
12 | mock.sessionChanged = callback;
13 | }
14 | };
15 | return mock;
16 | }
17 |
--------------------------------------------------------------------------------
/elements/src/test/pressKey.ts:
--------------------------------------------------------------------------------
1 | export async function pressKey(page, key: string) {
2 | const keyEvent = new KeyboardEvent('keydown', {
3 | key,
4 | });
5 | page.root.dispatchEvent(keyEvent);
6 | await page.waitForChanges();
7 | }
8 |
--------------------------------------------------------------------------------
/elements/src/test/renderFunctionalComponent.tsx:
--------------------------------------------------------------------------------
1 | import { newSpecPage } from '@stencil/core/testing';
2 | import { TestComponent } from './TestComponent';
3 |
4 | export function renderFunctionalComponent(jsx: any) {
5 | return newSpecPage({
6 | components: [TestComponent],
7 | template: () => jsx,
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/elements/src/window.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | export const PodOS = window.PodOS ? new window.PodOS.PodOS() : null;
3 |
--------------------------------------------------------------------------------
/elements/stencil.config.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@stencil/core';
2 |
3 | export const config: Config = {
4 | namespace: 'elements',
5 | globalScript: 'src/global.ts',
6 | globalStyle: 'src/global.css',
7 | outputTargets: [
8 | {
9 | type: 'dist',
10 | esmLoaderPath: '../loader',
11 | },
12 | {
13 | type: 'dist-custom-elements',
14 | },
15 | {
16 | type: 'docs-readme',
17 | dir: '../docs/elements',
18 | },
19 | {
20 | type: 'www',
21 | copy: [{ src: 'pod-index.html' }, { src: 'registerSW.js' }, { src: 'service-worker-localhost.js' }],
22 | serviceWorker: false, // disable stencils own service worker
23 | },
24 | ],
25 | };
26 |
--------------------------------------------------------------------------------
/elements/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "allowUnreachableCode": false,
5 | "declaration": false,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "dom",
9 | "es2017"
10 | ],
11 | "moduleResolution": "node",
12 | "module": "esnext",
13 | "target": "es2017",
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "jsx": "react",
17 | "jsxFactory": "h",
18 | "skipLibCheck": true // https://github.com/ionic-team/ionicons/issues/1011#issuecomment-962038754
19 | },
20 | "include": [
21 | "src",
22 | "./jest-setup.ts"
23 | ],
24 | "exclude": [
25 | "node_modules",
26 | "src/test/**/*",
27 | "**/*.spec.ts",
28 | "**/*.spec.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/service-worker/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 | types/
--------------------------------------------------------------------------------
/service-worker/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## 0.1.1
8 |
9 | ### Fixed
10 |
11 | - correctly respond with index page from cache
12 |
13 | ## 0.1.0
14 |
15 | ### Added
16 |
17 | - initial release:
18 | - Cache-first strategy for specified resources
19 | - Automatic cache cleanup on activation
20 | - Configurable initial cache list
21 | - Navigation request handling
22 |
23 |
24 |
--------------------------------------------------------------------------------
/service-worker/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/service-worker/esbuild/build-bundle.mjs:
--------------------------------------------------------------------------------
1 | import { build } from "esbuild";
2 | await build({
3 | logLevel: "info",
4 | entryPoints: ["src/index.ts"],
5 | outfile: "lib/index.js",
6 | bundle: true,
7 | target: "esnext",
8 | globalName: "PodOsServiceWorker",
9 | });
10 |
--------------------------------------------------------------------------------
/service-worker/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | import eslint from "@eslint/js";
4 | import tseslint from "typescript-eslint";
5 |
6 | export default tseslint.config({
7 | languageOptions: {
8 | ecmaVersion: 2022,
9 | sourceType: "module",
10 | },
11 | extends: [eslint.configs.recommended, ...tseslint.configs.recommended],
12 | });
13 |
--------------------------------------------------------------------------------
/service-worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pod-os/service-worker",
3 | "version": "0.1.1",
4 | "description": "Utilities to create a Service Worker for PodOS apps",
5 | "main": "lib/index.js",
6 | "types": "./types/index.d.ts",
7 | "files": [
8 | "lib/"
9 | ],
10 | "scripts": {
11 | "test": "jest",
12 | "lint": "eslint src",
13 | "build:bundle": "rimraf lib && node esbuild/build-bundle.mjs",
14 | "build:types": "rimraf types && tsc --emitDeclarationOnly --outDir types",
15 | "build": " npm run build:bundle && npm run build:types"
16 | },
17 | "license": "MIT",
18 | "devDependencies": {
19 | "esbuild": "^0.25.5",
20 | "rimraf": "^6.0.1",
21 | "@babel/preset-env": "^7.27.2",
22 | "@babel/preset-typescript": "^7.27.1",
23 | "@types/jest": "^29.5.14",
24 | "@types/jest-when": "^3.5.5",
25 | "eslint": "^9.27.0",
26 | "prettier": "^3.5.3",
27 | "typescript-eslint": "^8.33.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/service-worker/readme.md:
--------------------------------------------------------------------------------
1 | # PodOS Service Worker
2 |
3 | A service worker setup package for PodOS applications that provides caching and offline capabilities.
4 |
5 | ## Features
6 |
7 | - Cache-first strategy for specified resources
8 | - Automatic cache cleanup on activation
9 | - Configurable initial cache list
10 | - Navigation request handling
11 |
12 | ## Usage
13 |
14 | ### Loading from CDN
15 |
16 | The service worker setup can be loaded directly in your service worker file using `importScripts()`:
17 |
18 | ```javascript
19 | importScripts('https://cdn.jsdelivr.net/npm/@pod-os/service-worker/lib/index.js');
20 |
21 | PodOsServiceWorker.setupServiceWorker(
22 | self, // the serive worker itself
23 | 'my-pod-os-app-v1', // cache name
24 | 'https://cdn.jsdelivr.net/npm/@pod-os', // URL prefix for CDN resources to cache after fetching
25 | [ // static files to pre-load to cache
26 | '/',
27 | './index.html',
28 | './registerSW.js',
29 | ]
30 | );
31 |
32 | ```
--------------------------------------------------------------------------------
/service-worker/src/index.ts:
--------------------------------------------------------------------------------
1 | export { setupServiceWorker } from './setupServiceWorker';
2 |
--------------------------------------------------------------------------------
/service-worker/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "esnext",
5 | "module": "esnext",
6 | "esModuleInterop": true,
7 | "moduleResolution": "node",
8 | "forceConsistentCasingInFileNames": true,
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "lib": ["esnext", "webworker"]
12 | },
13 | "exclude": ["**/*.spec.ts"]
14 | }
15 |
--------------------------------------------------------------------------------
/storybook/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | core: {
3 | builder: "webpack5",
4 | },
5 | stories: [
6 | "../stories/**/*.stories.mdx",
7 | "../stories/**/*.stories.@(js|jsx|ts|tsx)",
8 | ],
9 | addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
10 | framework: "@storybook/web-components",
11 | };
12 |
--------------------------------------------------------------------------------
/storybook/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/storybook/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import { html } from "lit-html";
2 |
3 | export const parameters = {
4 | actions: { argTypesRegex: "^on[A-Z].*" },
5 | controls: {
6 | matchers: {
7 | color: /(background|color)$/i,
8 | date: /Date$/,
9 | },
10 | },
11 | };
12 |
13 | export const decorators = [
14 | (story) => html`
15 |
16 |
17 |
18 | ${story()}
19 |
20 |
21 |
22 | `,
23 | ];
24 |
--------------------------------------------------------------------------------
/storybook/Readme.md:
--------------------------------------------------------------------------------
1 | # PodOS storybook
2 |
3 | This storybook showcases the elements of PodOS
4 |
5 | ## Online version
6 |
7 | Visit [the latest version of the storybook](https://pod-os.github.io/PodOS/storybook/).
8 |
9 | ## Start locally
10 |
11 | Make sure you start `elements` first, as described in the [root readme](../Readme.md#run-locally).
12 |
13 | Then run from within the storybook directory:
14 | ```
15 | npm start
16 | ```
17 |
--------------------------------------------------------------------------------
/storybook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pod-os/storybook",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "cross-env STORYBOOK_POD_OS_ELEMENTS_DIST_URL=http://localhost:3333/build start-storybook -p 6006",
8 | "build": "build-storybook -o ../gh-pages/storybook"
9 | },
10 | "author": "",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "@babel/core": "^7.22.9",
14 | "@storybook/addon-actions": "^6.5.16",
15 | "@storybook/addon-essentials": "^6.5.16",
16 | "@storybook/addon-links": "^6.5.16",
17 | "@storybook/builder-webpack5": "^6.5.16",
18 | "@storybook/manager-webpack5": "^6.5.16",
19 | "@storybook/web-components": "^6.5.16",
20 | "babel-loader": "^9.1.3",
21 | "lit-html": "^2.7.5"
22 | },
23 | "dependencies": {
24 | "cross-env": "^7.0.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/storybook/stories/10_pos-predicate.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 | ## pos-predicate
15 |
16 |
27 |
--------------------------------------------------------------------------------
/storybook/stories/1_pos-resource.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 | ## pos-resource
15 |
16 |
29 |
--------------------------------------------------------------------------------
/storybook/stories/4_pos-literals.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | import {html} from "lit-html";
5 |
6 | import {
7 | Canvas,
8 | Meta,
9 | Story
10 | } from '@storybook/addon-docs/blocks';
11 |
12 |
16 |
17 |
18 | ## pos-literals
19 |
20 | Renders a table of literal values about the resource.
21 |
22 |
34 |
--------------------------------------------------------------------------------
/storybook/stories/5_pos-relations.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 |
20 | ## pos-relations
21 |
22 | Renders a table containing links to related resources.
23 |
24 |
36 |
--------------------------------------------------------------------------------
/storybook/stories/6_pos-reverse-relations.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 |
20 | ## pos-reverse-relations
21 |
22 | Renders a table containing links to resources that reference the current resource.
23 |
24 |
36 |
--------------------------------------------------------------------------------
/storybook/stories/8_pos-picture.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 |
20 | ## pos-picture
21 |
22 | Renders a picture of the resource.
23 |
24 |
36 |
--------------------------------------------------------------------------------
/storybook/stories/9_pos-type-badges.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
15 |
16 |
17 | ## pos-type-badges
18 |
19 | Renders badges, that show a human-readable name for the resource types.
20 |
21 | The badges can be expanded to show the full URIs of all types.
22 |
23 |
35 |
--------------------------------------------------------------------------------
/storybook/stories/composition/1_resource-composition.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 |
15 | ## pos-resource composition
16 |
17 | This is an example of how you can combine a `` element with any html and other PodOS elements.
18 |
19 |
39 |
--------------------------------------------------------------------------------
/storybook/stories/documents/1_pos-document.stories.mdx:
--------------------------------------------------------------------------------
1 | import { html } from "lit-html";
2 |
3 | import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
4 |
5 |
14 |
15 | ## pos-document
16 |
17 | Renders the contents of a document, like a PDF or an HTML page.
18 |
19 |
27 |
--------------------------------------------------------------------------------
/storybook/stories/inputs/1_pos-select-term.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
17 |
18 | ## pos-select-term
19 |
20 | Allows to select a term from a list of know vocabulary terms.
21 |
22 |
32 |
--------------------------------------------------------------------------------
/storybook/stories/inputs/2_pos-add-literal-value.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 | ## pos-add-literal-value
20 |
21 | Allows to add a new literal value to the resource in its scope. It only shows up, if the resource is editable to the current user or publicly editable.
22 |
23 |
35 |
--------------------------------------------------------------------------------
/storybook/stories/inputs/3_pos-add-new-thing.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 | ## pos-add-new-thing
20 |
21 | Allows to add a new thing to a Pod
22 |
23 |
33 |
--------------------------------------------------------------------------------
/storybook/stories/inputs/4_pos-new-thing-form.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
18 |
19 | ## pos-add-new-thing
20 |
21 | Allows to enter and submit data for a new thing
22 |
23 |
33 |
--------------------------------------------------------------------------------
/storybook/stories/inputs/5_pos-dialog.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
12 |
13 | ## pos-dialog
14 |
15 | Styled wrapper around native dialog element
16 |
17 |
31 |
--------------------------------------------------------------------------------
/storybook/stories/ldp-container/1_pos-container-contents.stories.mdx:
--------------------------------------------------------------------------------
1 | import { html } from "lit-html";
2 |
3 | import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
4 |
5 |
14 |
15 | ## pos-container-contents
16 |
17 | Shows a list of all things that are contained in an LDP container resource. The
18 | surrounding `` should therefore refer to an LDP container.
19 |
20 |
29 |
--------------------------------------------------------------------------------
/storybook/stories/login/1_pos-login.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
12 |
13 |
14 | ## pos-login
15 |
16 | Allows the user to log in.
17 |
18 | > ATTENTION: To be able to log in from here, you have to go to the `Canvas` tab and click
19 | > the `Open canvas in new tab` button in the upper right corner of the page
20 |
21 |
29 |
--------------------------------------------------------------------------------
/storybook/stories/login/2_pos-login-form.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
12 |
13 |
14 | ## pos-login-form
15 |
16 | Allows the user to enter or select the URL of their Identity Provider
17 |
18 |
26 |
--------------------------------------------------------------------------------
/storybook/stories/navigation/0_pos-rich-link.stories.mdx:
--------------------------------------------------------------------------------
1 | import {
2 | html
3 | } from "lit-html";
4 |
5 | import {
6 | Canvas,
7 | Meta,
8 | Story
9 | } from '@storybook/addon-docs/blocks';
10 |
11 |
20 |
21 |
22 | ## pos-rich-link
23 |
24 | Links to the given URI and shows a label and description of the resource identified by that URI, if available.
25 |
26 |
36 |
--------------------------------------------------------------------------------
/storybook/stories/navigation/2_pos-make-findable.stories.mdx:
--------------------------------------------------------------------------------
1 | import {
2 | html
3 | } from "lit-html";
4 |
5 | import {
6 | Canvas,
7 | Meta,
8 | Story
9 | } from '@storybook/addon-docs/blocks';
10 |
11 |
20 |
21 |
22 | ## pos-make-findable
23 |
24 | A button, to make a thing discoverable through search, by adding it to a label index.
25 |
26 | For this to work, you need to be signed in and have a [private label index](https://github.com/pod-os/PodOS/blob/main/docs/features/full-text-search.md).
27 |
28 | > ATTENTION: To be able to log in from here, you have to go to the `Canvas` tab and click
29 | > the `Open canvas in new tab` button in the upper right corner of the page
30 |
31 |
46 |
--------------------------------------------------------------------------------
/storybook/stories/rdf-document/1_pos-subjects.stories.mdx:
--------------------------------------------------------------------------------
1 | import { html } from "lit-html";
2 |
3 | import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks";
4 |
5 |
14 |
15 | ## pos-subjects
16 |
17 | Shows a list of all things that occur as a subject in an RDF document resource. The
18 | surrounding `` should therefore refer to an RDF document.
19 |
20 |
29 |
--------------------------------------------------------------------------------
/storybook/stories/values/1_pos-value.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 |
15 | ## pos-value
16 |
17 | Renders any value for the given predicate of the resource.
18 |
19 |
31 |
--------------------------------------------------------------------------------
/storybook/stories/values/2_pos-label.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 |
15 | ## pos-label
16 |
17 | Renders a human-readable label of the resource.
18 |
19 |
31 |
--------------------------------------------------------------------------------
/storybook/stories/values/3_pos-description.stories.mdx:
--------------------------------------------------------------------------------
1 | import {html} from "lit-html";
2 |
3 | import {
4 | Canvas,
5 | Meta,
6 | Story
7 | } from '@storybook/addon-docs/blocks';
8 |
9 |
13 |
14 |
15 | ## pos-description
16 |
17 | Renders a human-readable description of the resource.
18 |
19 |
20 |
32 |
--------------------------------------------------------------------------------