├── .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"> 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 | 2 | 4 | 5 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /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 | PodOS Logo 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 |
19 | 20 |
    21 | {this.phoneNumbers.map(phoneNumber => ( 22 |
  • 23 | {phoneNumber.value} 24 |
  • 25 | ))} 26 |
27 |
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 | PodOS Logo 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 |
18 |

New to Solid?

19 |

20 | Get a Pod → 21 |

22 |
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 |
27 | 28 |
29 |
30 | 31 | 32 |
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 | (this.dialog = el as HTMLDialogElement)}> 28 |
29 | 30 | 33 |
34 | 35 |
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 ?
      {items}
    : 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 | 18 | 21 | {({uri, label}) => html` 22 | 23 | 24 | `} 25 | 26 | 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 | 18 | 21 | {({uri}) => html` 22 | 23 | This content will show up, after ${uri} has been fetched. 24 | Child components will use the resource uri as a reference for showing further content. 25 | 26 | `} 27 | 28 | 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 | 24 | 27 | {({uri}) => html` 28 | 29 | 30 | 31 | `} 32 | 33 | 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 | 26 | 29 | {({uri}) => html` 30 | 31 | 32 | 33 | `} 34 | 35 | 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 | 26 | 29 | {({uri}) => html` 30 | 31 | 32 | 33 | `} 34 | 35 | 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 | 26 | 29 | {({uri, width, height, borderRadius}) => html` 30 | 31 | 32 | 33 | `} 34 | 35 | 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 | 25 | 28 | {({uri}) => html` 29 | 30 | 31 | 32 | `} 33 | 34 | 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 | 21 | 24 | {({uri}) => html` 25 | 26 | The URI ${uri} identifies , which is described as: 27 |
    28 |
    29 |
    30 |
    31 |
    32 |

    Here is a table with some facts about :

    33 | 34 |
    35 |
    36 | `} 37 |
    38 |
    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 | 20 | 21 | {({ uri }) => html` 22 | 23 | 24 | `} 25 | 26 | 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 | 24 | 27 | {() => html` 28 | 29 | `} 30 | 31 | 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 | 25 | 28 | {({uri}) => html` 29 | 30 | 31 | 32 | `} 33 | 34 | 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 | 25 | 28 | {({referenceUri}) => html` 29 | 30 | `} 31 | 32 | 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 | 25 | 28 | {({referenceUri}) => html` 29 | 30 | `} 31 | 32 | 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 | 19 | 22 | {() => html` 23 | 24 | 25 | The title of the dialog 26 |
    The content of the dialog.
    27 |
    28 | `} 29 |
    30 |
    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 | 21 | 22 | {({ ldpContainerUri }) => html` 23 | 24 | 25 | 26 | `} 27 | 28 | 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 | 23 | 26 | {({uri}) => html``} 27 | 28 | 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 | 20 | 23 | {() => html``} 24 | 25 | 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 | 28 | 31 | {({uri}) => html` 32 | 33 | `} 34 | 35 | 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 | 33 | 36 | {({uri}) => html` 37 |
    38 | 39 |
    40 |
    41 | 42 |
    43 | `} 44 |
    45 |
    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 | 21 | 22 | {({ uri }) => html` 23 | 24 | 25 | 26 | `} 27 | 28 | 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 | 21 | 24 | {({subject, predicate}) => html` 25 | 26 | 27 | 28 | `} 29 | 30 | 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 | 21 | 24 | {({uri}) => html` 25 | 26 | 27 | 28 | `} 29 | 30 | 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 | 22 | 25 | {({uri}) => html` 26 | 27 | 28 | 29 | `} 30 | 31 | 32 | --------------------------------------------------------------------------------