├── .exceptions ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── apps ├── fileviewer.html └── scratchpad.html ├── blog ├── 2022 │ ├── 04 │ │ └── ipfs-with-chromium.md │ └── 09 │ │ └── considerations-for-p2p-web-community-networks.md ├── 2023 │ ├── 11 │ │ ├── drag-and-drop-retrospective.md │ │ └── ipfs-pub-sub-chat-retro.md │ ├── 12 │ │ ├── demos-and-tutorials-second-round.md │ │ └── ipfs-dir-upload-retrospective.md │ ├── 01 │ │ └── demos-and-tutorials-announcement.md │ ├── 03 │ │ └── themebuilder-retrospective.md │ └── 07 │ │ └── demos-and-tutorials-first-round.md ├── 2024 │ ├── 01 │ │ ├── ipfs-3rd-party-dep-retrospective.md │ │ └── p2pad-code-editor-retrospective.md │ └── 03 │ │ ├── demos-and-tutorials-final-round.md │ │ ├── dlinktree-builder-retrospective.md │ │ └── ipfs-gallery-retrospective.md └── index.md ├── docs ├── ai.md ├── bittorent-protocol-handlers.md ├── examples │ ├── blocks-app │ │ ├── README.md │ │ ├── index.html │ │ ├── script.js │ │ └── style.css │ ├── browser-devenv-v2 │ │ ├── files │ │ │ ├── index.html │ │ │ ├── lib.js │ │ │ ├── style.css │ │ │ └── upload.js │ │ └── index.html │ ├── browser-devenv-v3 │ │ ├── files │ │ │ ├── ace.css │ │ │ ├── ace.js │ │ │ ├── index.html │ │ │ ├── lib.js │ │ │ ├── mode-css.js │ │ │ ├── mode-html.js │ │ │ ├── mode-javascript.js │ │ │ ├── style.css │ │ │ ├── upload.js │ │ │ ├── worker-css.js │ │ │ ├── worker-html.js │ │ │ └── worker-javascript.js │ │ └── index.html │ ├── browser-devenv │ │ ├── index.html │ │ ├── index.html.template │ │ ├── lib.js │ │ └── lib.js.template │ ├── dlinktree-builder │ │ ├── index.html │ │ ├── script.js │ │ └── styles.css │ ├── drag-and-drop │ │ ├── index.html │ │ ├── script.js │ │ └── styles.css │ ├── ipfs-gallery │ │ ├── gallery.js │ │ └── index.html │ ├── ipfs-pub-sub-chat │ │ ├── index.html │ │ ├── lib.js │ │ ├── pubsub.js │ │ └── style.css │ ├── llm-appgen │ │ ├── LICENSE │ │ ├── README.md │ │ └── index.html │ ├── llm-chat.html │ ├── llm-echo-chamber.html │ ├── llm-lenses-chat │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ └── index.html │ ├── llm-vision.html │ ├── multitodo │ │ ├── README.md │ │ └── index.html │ ├── p2pad │ │ ├── codeEditor.js │ │ ├── common.js │ │ ├── dweb.js │ │ ├── index.html │ │ └── styles.css │ ├── quickcode.html │ └── themebuilder.html ├── extensions.md ├── hypercore-protocol-handlers.md ├── index.md ├── ipfs-protocol-handlers.md ├── protocols.md ├── theming.md └── tutorials │ ├── dlinktree-builder.md │ ├── drag-and-drop.md │ ├── ipfs-3rdparty-dep │ ├── ace-builds.png │ ├── ace-edit.png │ ├── devenv-annotated-v2.png │ └── index.md │ ├── ipfs-browser-devenv │ ├── agregore-browser-devtools.png │ ├── part-1.md │ ├── part-2.md │ └── part-3.md │ ├── ipfs-dir-upload │ ├── devenv-annotated.png │ ├── index.md │ ├── template-site.png │ └── upload-form.png │ ├── ipfs-gallery │ ├── devenv-3-annotated.png │ ├── devenv-bs.png │ ├── index.md │ ├── ipfs-gallery-layout.png │ └── ipfs-gallery.png │ ├── ipfs-pub-sub-chat.md │ ├── p2pad-code-editor.md │ ├── process.md │ └── themebuilder-tutorial.md ├── explore.md ├── faq.md ├── hyper-url.png ├── icon.svg ├── index.json ├── index.md ├── lint.sh ├── screenshot.png └── videos.md /.exceptions: -------------------------------------------------------------------------------- 1 | agregore 2 | github 3 | repo 4 | Gomobile 5 | Bromite 6 | Multicast 7 | pubsub 8 | DeGoogling 9 | upstreamed 10 | devtools 11 | Wifi 12 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-node@v2-beta 26 | with: 27 | node-version: '18' 28 | - name: Compile website into HTML 29 | run: npx agregore-markdown-site-generator 30 | - name: Publish to Distributed Press 31 | uses: hyphacoop/actions-distributed-press@v1.1.0 32 | with: 33 | publish_dir: ./ 34 | dp_url: https://press.mauve.moe 35 | refresh_token: ${{ secrets.DISTRIBUTED_PRESS_TOKEN }} 36 | site_url: agregore.mauve.moe 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gmi 2 | style.css 3 | !docs/examples/** 4 | .aider* 5 | .env 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # website 2 | Website for Agregore 3 | -------------------------------------------------------------------------------- /apps/fileviewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DWeb File Viewer 6 | 7 | 8 | 9 | 10 | 23 | 28 |
29 | 30 |
31 | 82 | -------------------------------------------------------------------------------- /blog/2023/01/demos-and-tutorials-announcement.md: -------------------------------------------------------------------------------- 1 | # Agregore Web Apps Announcement 2 | 3 | The peer-to-peer (P2P) web has come a long way in recent years, with the emergence of mature protocols such as IPFS (InterPlanetary File System). These protocols are gaining support in an increasing number of browsers. [Agregore](https://github.com/AgregoreWeb/agregore-browser) for example allows you to not only view content using P2P protocols, but also create new content. 4 | 5 | This allows for the use and development of P2P web apps that don't depend on extra infrastructure like 3rd party hosted services, proprietary platforms, etc. 6 | 7 | It is also possible to develop P2P web applications with only access to a P2P-capable browser by removing the need for complex development environments that requires different toolchains, editors, virtualization, etc developing applications becomes more accessible. 8 | 9 | However, P2P web applications are still relatively new and developing them requires a different approach compared to traditional web applications. There are new concepts to understand and older concepts that don't apply anymore. And there aren't a lot of resources and tutorials available to help developers learn how to build P2P applications. We're still in a stage in which developers unfamiliar with making apps in this environment face a steep learning curve. 10 | 11 | With that in mind, we are excited to announce a new project in collaboration with the [Filecoin Foundation](https://fil.org/), aimed at making it easier for developers to build applications for the distributed web. Our goal is to build several small applications that showcase the different features of a P2P web application, and then turn these applications into tutorials that anyone can follow. 12 | 13 | We're going to be working in three-week sprints to take small app ideas from conception to reality. We'll also be developing tutorials along the way, so anyone can use the apps we're making to learn P2P web concepts and expand on what we come up with. 14 | 15 | We want to explore how long it takes devs of different backgrounds to make apps with these P2P protocols. So we'll timebox the apps at three weeks; roughly two to make the apps and then one to finish up the tutorial and write up retrospectives. These retrospectives will be a space for us to ask questions about the process and what could be better, which we also plan to publish. 16 | 17 | Our hope is that by making the process of building P2P web applications more visible and accessible, we can help to grow the P2P web ecosystem and encourage more people to learn about the technology and build their own applications. We also believe that in the long term, this will lower the barrier to entry for people who want to create software applications that they find useful. 18 | 19 | Click here to [join our Matrix channel](https://matrix.to/#/#agregore:mauve.moe) to keep up to date on new sprints and learn about ways you can participate! 20 | 21 | -------------------------------------------------------------------------------- /blog/2023/03/themebuilder-retrospective.md: -------------------------------------------------------------------------------- 1 | # Retrospective - Theme Builder App 2 | 3 | ## What was the outcome? 4 | 5 | 1. To share your app with the group, first walk us through the original goal and what you ended up making. 6 | 7 | I wanted a simple one page app with colour pickers that changed the value of CSS variables (which were then visually reflected on the site). I then wanted a way for the user to have a way to "save" their changes and use them elsewhere. 8 | 9 | In the end I had just that, using the Agregore theming, the "save" functionality came in the form of a button which downloaded the CSS (albeit in a crude form it feels). 10 | 11 | 2. Did you accomplish more or less than the original app idea? If you started out with a user story, were those aims accomplished? 12 | 13 | I pretty much got everything I wanted done for the app. I did have a "user story" which was essentially describing my apps basic goal. So yes, the aims were accomplished. 14 | 15 | 3. Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 16 | 17 | It didn't really, no. While ideas for like, "stretch goals" came up I really wanted to focus on something basic since this was the first time I'd ever developed anything in an environment like this and I was a little rusty in general. 18 | 19 | ## What was the process like? 20 | 21 | 1. Describe making the app. Link to the tutorial here. 22 | 23 | Making the app was interesting! I guess it wasn't too eventful, the tough part was trying to figure out what my dev environment was going to be like. 24 | 25 | 2. What else was notable about the process? Some discussion questions: 26 | 27 | - What went well? 28 | - The overall process felt pretty good! I got into a decent rhythm with it and it felt like what I had to do next was pretty clear. 29 | - What took a lot of time? 30 | - The most time consuming thing was dealing with the p2p hosting, my experience with making p2p web apps were small "hello world" type apps and so I didn't know best practices in terms of how to connect files together. (Like having an image or using a css/js file instead of it all being in one big HTML file). In the end I decided not to deal with it this time around. But hopefully I can try it out for my next app. 31 | - Did you find anything else to be unexpected about making the app? 32 | - Can't think of anything for this one. 33 | - If you could give yourself advice when you started making the app, what would you tell yourself? 34 | - Probably to be flexible. I spent way too long thinking about how I was going to do something a specific way when I could have just done it. Apps are not unchangeable, if I did something "wrong" I could simply fix it later or make note of it for the future. 35 | 36 | ## Discussion 37 | 38 | 1. What would you do with this app if you kept working on it? E.g. future improvements? Demo it with users (if so, who would the users be)? 39 | 40 | As it is right now, it's very barebones (which I think is good!) I think I'd like a way to load themes into it that a friend provides (to emphasize sharing themes around) and I'd like a way to like, have saved themes be on the site and loadable so you can have multiple themes to switch between (so like, imagine there are swatches on a sidebar which you can click on and then the site turns into that theme, and you can add more themes to that, I dunno if that describes it very well). I wonder too if I could merge these things into a "theme shop" kinda thing but like, not a shop! 41 | 42 | I also was thinking about adding more customization to things, like maybe you want more theme colours than the 4 currently provided, or you want to use serif fonts for headers but sans-serif for the rest of the site. I think there is a point or a line somewhere where someone should just learn CSS? But I do think there is some usability in a *little* more customization. 43 | 44 | 2. If any technical improvements to Agregore or the underlying protocols could make development easier for this app, please describe them here. 45 | 46 | Can't think of anything at the moment. -------------------------------------------------------------------------------- /blog/2023/11/drag-and-drop-retrospective.md: -------------------------------------------------------------------------------- 1 | # Retrospective: Drag and Drop to IPFS and Hypercore 🌐 2 | 3 | 4 | ## Presenting Your App 🎉 5 | 6 | ### Original Goal and Outcome 🎯 7 | The primary goal was to create a straightforward drag-and-drop interface that allows users to easily upload files to the decentralized web, specifically to IPFS and Hypercore. The final product aligns closely with the original concept, maintaining its simplicity and user-friendly design. 8 | 9 | ### Accomplishments and Evolution 📈 10 | - The app achieved its original intent, facilitating file uploads to both IPFS and Hypercore. 11 | - The core idea remained consistent throughout the development process. There were no significant deviations or evolutions in the concept. 12 | 13 | ### Challenges and Insights 💡 14 | - IPFS integration was relatively straightforward. 15 | - Hypercore integration presented more challenges, primarily due to limited documentation. This required additional research and experimentation. 16 | - A key learning from this experience is the importance of gathering all necessary documentation beforehand to streamline the development process. 17 | 18 | ## The Development Process 🛠️ 19 | 20 | ### Building the App 🏗️ 21 | - The app was constructed using HTML, CSS, and JavaScript. The detailed steps and code snippets are available in the [tutorial I created](/docs/examples/drag-and-drop/README). 22 | - Notable aspects of the development process: 23 | - **What Went Well:** The overall implementation of the drag-and-drop feature and IPFS integration went smoothly. 24 | - **Time-Consuming Tasks:** Hypercore integration took a significant amount of time, primarily due to the lack of comprehensive documentation. 25 | - **Unexpected Discoveries:** The contrast in ease of implementation between IPFS and Hypercore was unexpected. 26 | 27 | ### Advice to My Past Self 🗣️ 28 | - Prioritize collecting all relevant documentation before starting the development process. 29 | - Anticipate potential challenges with lesser-documented features like Hypercore. 30 | 31 | ## Future Prospects and Discussion 🚀 32 | 33 | ### Potential Enhancements 🔧 34 | - **UI Improvements:** Implement loading states to inform users about the progress of their uploads. 35 | - **Enhanced Feedback Mechanisms:** Augment the UI to provide more information about the app's state and ongoing processes. 36 | - **Comprehensive Interface:** Develop a more complete interface that enhances user engagement and experience. 37 | 38 | ### Suggestions for Agregore and Underlying Protocols 📝 39 | - **Documentation Enhancement:** One significant area for improvement in Agregore would be the provision of more extensive documentation, especially for features like Hypercore. This would greatly aid developers in understanding and utilizing these technologies effectively. 40 | 41 | ## Conclusion 🌟 42 | This project not only introduced me to the fundamentals of Agregore but also immersed me in the innovative realm of decentralized web technologies. The journey from conception to realization of this app has been both challenging and rewarding, offering valuable insights into the development process and the potential of decentralized web applications. 43 | -------------------------------------------------------------------------------- /blog/2023/11/ipfs-pub-sub-chat-retro.md: -------------------------------------------------------------------------------- 1 | #### What was the outcome? 2 | 3 | 1. To share your app with the group, first walk us through the original goal and what you ended up making. 4 | 5 | Initially I wanted to create a video calling app using PUBSUB to do signalling and WebRTC for the audio and video. I ended up removing the WebRTC component of the app and created a chat app instead. 6 | 7 | 8 | 2. Did you accomplish more or less than the original app idea? If you started out with a user story, were those aims accomplished? 9 | 10 | Technically I accomplished less, since text < video, but in terms of the application it introduced slightly more interface logic than the video would have required. Using WebRTC would also have required that I use a 3rd party STUN/TURN server which introduce a non-peer-to-peer element to the app. 11 | 12 | 3. Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 13 | 14 | I wrote the app first with PUBSUB and WebRTC, but while writing the tutorial I realized that it was trying to cover too many topics. I decided to remove the WebRTC component and create a chat application using PUBSUB to cover a more realistic range of topics. 15 | 16 | 17 | #### What was the process like? 18 | 19 | 1. Describe making the app. Link to the tutorial here. 20 | 21 | As mentioned above, I started with the app before writing the tutorial. I used the development environment made in a [previous tutorial](/docs/tutorials/ipfs-browser-devenv/part-1) and it worked reasonably well, although I wished that it had a few more features. You can find the tutorial for the PUBSUB chat app [here](/docs/tutorials/ipfs-pubsub-chat) 22 | 23 | 2. What else was notable about the process? Some discussion questions: 24 | Q: What went well? 25 | A: I prototyped the PUBSUB and WebRTC components previously, which made it easy to use in this app. 26 | 27 | Q: What took a lot of time? 28 | A: Deciding to change the application after having written most of the tutorial required rewriting the whole tutorial. 29 | 30 | Q: Did you find anything else to be unexpected about making the app? 31 | A: When testing the app using two laptops, it took me a while to share the URL between the two devices since the URLs aren't easy to manually enter. 32 | 33 | Q: If you could give yourself advice when you started making the app, what would you tell yourself? 34 | A: Don't try to do too much! 35 | 36 | #### Discussion 37 | 38 | 1. What would you do with this app if you kept working on it? E.g. future improvements? Demo it with users (if so, who would the users be)? 39 | Add some features like implementing presence to give users a nickname and indicate when other people are online. Add the ability to have join more than one chat channel. Add encryption to allow private communications. Add the ability to persist chat history using IPLD. 40 | 41 | 2. If any technical improvements to Agregore or the underlying protocols could make development easier for this app, please describe them here. 42 | A plugin for Agregore to share short readable/writeable links between devices using Agregore to avoid having to copy and paste long CIDs. 43 | 44 | -------------------------------------------------------------------------------- /blog/2023/12/demos-and-tutorials-second-round.md: -------------------------------------------------------------------------------- 1 | # Agregore demos and tutorials round 2 2 | 3 | This is the second round of apps and tutorials that we have worked on from [this announcement](/blog/2023/01/demos-and-tutorials-announcement). 4 | For this round we have switched up the team and created 3 more apps and tutorials. 5 | 6 | ## What we made 7 | 8 | ### Dirk's p2p pub sub chat 9 | 10 | [@DirkCuys](https://github.com/dirkcuys) put together a basic chat app using the `pubsub://` protocol that is part of IPFS. This orotocol allows you to `subscribe` on a `topic` and connect to anyone else subscribed on the topic and then `publish` messages that get delivered to everyone. You can see a live version of the chat [here](/docs/examples/ipfs-pub-sub-chat/) or build it yourself with [the tutorial](/docs/tutorials/ipfs-pub-sub-chat). 11 | 12 | ### Dirk's Directory Uploader 13 | 14 | [@DirkCuys](https://github.com/dirkcuys) also investigated how one could more easily upload entire folders to Agregore's built in protocol handlers and extended his initial browser development environment with this funcionality. With this [demo](/docs/examples/browser-devenv-v2/) you can upload a static website or a bunch of dependencies for a javascript project. You can see how you can add this funcionality in the new [tutorial](/docs/tutorials/ipfs-dir-upload/). 15 | 16 | ### New Docs for `hyper://` Protocol Handlers 17 | 18 | We have a new contributor: [@tripledoublev](https://github.com/tripledoublev) aka Vincent who started off by contributing new docs for the [hyper:// protocol handlers](/docs/hypercore-protocol-handlers). Now developers can more easily develop apps with the protocol thanks to copy paste-able examples and easy to acess docs. 19 | 20 | ### Vincent's Drag and Drop 21 | 22 | Next, [Vincent](https://github.com/tripledoublev) created a new Drag and Drop uploader. With this you can easily add a file in Agregore and share it with your friends. Check out the published [app](/docs/examples/drag-and-drop/) or follow the [tutorial](/docs/tutorials/drag-and-drop). This can also be used as a basis for drag and drop file uploads for your own apps like photo collages or profile pictures. 23 | 24 | ## Retrospectives 25 | 26 | As with the preview milestone we had retrospectives to see how the process worked. One of the most important lessons was to scope back when possible. Sometimes the base APIs on the web platform are more complex than one might expect and it's useful to collect more information before starting development so you can avoid having to pivot a bunch due to uncertainty. Some improvements we should look into are making it even easier to copy P2P URLs accross devices, generally improve the documentation, and see if there are improvements that could be added in the protocol handlers for uploading large files. 27 | 28 | ## Next Steps 29 | 30 | Next we have ou final round where we'll be publishing 4 more apps and tutorials and wrapping up work on the grant. If you're curious to have these tutorials used for your communities or to develop new ones, reach out to us by [email](mailto:contact@mauve.moe). 31 | 32 | --- 33 | 34 | Full retrospectives: 35 | 36 | [IPFS Pub Sub Chat](/blog/2023/11/ipfs-pub-sub-chat-retro) 37 | [Directory Uploader](/blog/2023/12/ipfs-dir-upload-retrospective) 38 | [Drag and Drop](/blog/2023/11/drag-and-drop-retrospective) 39 | -------------------------------------------------------------------------------- /blog/2023/12/ipfs-dir-upload-retrospective.md: -------------------------------------------------------------------------------- 1 | # Retrospective: Uploading a directory of files to IPFS 2 | 3 | ## What was the outcome? 4 | 5 | ### Original goal and what you ended up making. 6 | 7 | Initially the plan was to create an IPFS site/application that uses 3rd party dependencies. Specifically, I wanted to update the [self hosted development environment](/docs/tutorials/ipfs-browser-devenv/part-1) created in a previous tutorial to use a code editor. I ended up making a tutorial that adds the ability to the environment to upload a directory hierarchy of files. 8 | 9 | ### Did you accomplish more or less than the original app idea? 10 | 11 | Both more and less. Less because the end result is only uploading files. But also more, since this is a requirement for adding 3rd-party dependencies and it can be used for other purposes too, like uploading media to an IPFS site someone is creating. 12 | 13 | ### Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 14 | 15 | It changed a lot. A big factor driving the change was managing the complexity of the tutorial. I've created partial tutorials for adding 3rd-party dependencies, building 3rd-party dependencies in the command line using rollup, handling folders for drag-and-drop. I ended up removing most of this content. 16 | 17 | 18 | ## What was the process like? 19 | 20 | ### Describe making the app 21 | It was an iterative process with lots of restarts and rethinks, but [the final tutorial](https://agregore.mauve.moe/docs/tutorials/ipfs-dir-upload/) stands on it's own and should be easy to work through. 22 | 23 | Some reflection on the process: 24 | 25 | - **What went well?**: Writing the code was mostly smooth sailing. I'm excited about adding a proper code editor to the development environment in a next tutorial! 26 | - **What took a lot of time?**: Changing direction multiple times. 27 | - **Did you find anything else to be unexpected about making the app?**: Yes, directory upload can be done in 3 different ways, none of them part of the HTML spec and all wildly varying in complexity with inconvenient trade-offs. 28 | - **If you could give yourself advice when you started making the app, what would you tell yourself?**: Start with a much simpler goal and expand on that only if needed. 29 | 30 | ## Discussion 31 | 32 | ### Future improvements and ideas 33 | 34 | Uploading many files can take a long time and the current implementation of the folder upload doesn't provide any user feedback. It would also be useful adding the ability to organize files - eg. move, rename, delete, etc. If someone wanted to make a photobook website on IPFS using the development environment, they would sorely lack both an indication of how their upload is progressing and the ability to organize files after it's been uploaded. 35 | 36 | ### Suggestions for Agregore or the underlying protocols 37 | 38 | One of the challenges with uploading a whole directory hierarchy using the js-ipfs-fetch handlers in Agregore Web is that you cannot upload multiple files to different directory paths at the same time. To work around this, files had to be uploaded in batches where all the files in a batch has the same base path. 39 | 40 | Being able to merge two UnixFS directories using js-ipfs-fetch without needed to download the content first would make strategies like parallelizing the uploading of batches. I don't know if this is feasible, but currently it is hard to imagine uploading directories with many big files. 41 | 42 | ## Conclusion 43 | 44 | While this tutorial ended up being about something that should have been a small part of the initial idea, the final tutorial covers useful info and will be useful for more than simply adding a 3rd party JavaScript dependency to a site hosted on IPFS. 45 | -------------------------------------------------------------------------------- /blog/2024/01/ipfs-3rd-party-dep-retrospective.md: -------------------------------------------------------------------------------- 1 | # Retrospective: Add a 3rd-party library to an IPFS site 2 | 3 | ## What was the outcome? 4 | 5 | ### Original goal and what you ended up making. 6 | 7 | The plan was to create an IPFS site/application that uses 3rd party dependencies. Specifically, I wanted to update the [self hosted development environment](/docs/tutorials/ipfs-browser-devenv/part-1) created in a previous tutorial to use a code editor. Since the required functionality was there this time I could successfully add the code editor! 8 | 9 | ### Did you accomplish more or less than the original app idea? 10 | 11 | It felt just about right! 12 | 13 | ### Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 14 | 15 | A lot of the changes happened in prior work and things come together nicely this time! 16 | 17 | ## What was the process like? 18 | 19 | ### Describe making the app 20 | Once again it was an iterative process, but with less restarts this time! [The tutorial](https://agregore.mauve.moe/docs/tutorials/ipfs-3rdparty-dep/) is short and sweet, but the payoff feels good at the end having a code editor with syntax highlighting and other features! 21 | 22 | Some reflection on the process: 23 | 24 | - **What went well?**: The groundwork paid off and this time I could get to what I wanted to do without many detours. 25 | - **What took a lot of time?**: Initially I wanted to use [CodeMirror](https://codemirror.net/), but to make it work I had to create a custom package using rollup. I ended up switching to ACE which was much easier to use. 26 | - **Did you find anything else to be unexpected about making the app?**: It was really easy integrating ACE, I was expecting it to be more work. That said, each 3rd party dependency will have it's own caveats, some will be an easy one file addition, while others might require you to specifically package them. 27 | - **If you could give yourself advice when you started making the app, what would you tell yourself?**: Be fine with keeping it simple. There is constantly the temptation to just add one more thing, don't. 28 | 29 | ## Discussion 30 | 31 | ### Future improvements and ideas 32 | 33 | The upload feature still lacks progress feedback which is not ideal when uploading many files. 34 | 35 | ### Suggestions for Agregore or the underlying protocols 36 | 37 | Working with in Agregore and storing files in a local node is a fun and easy way to play around with code without having to run the code somewhere (aka setting up a server/hosting environment). It could be a useful tool for people just getting started with HTML and web development! 38 | 39 | [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) hosted on IPFS seems like it should be a great match, but I ran into several problems when I tried to use ESM builds from CodeMirror and had to use rollup to build a custom esm package that included all the needed parts without any conflicts or duplicates. It seems worth investigating if there is a way to resolve the issues I encountered. 40 | 41 | ## Conclusion 42 | 43 | Things played out well this time round! I hope the code editor and tutorials prove useful for someone. 44 | -------------------------------------------------------------------------------- /blog/2024/01/p2pad-code-editor-retrospective.md: -------------------------------------------------------------------------------- 1 | # P2Pad Retrospective: Building a Real-Time Browser-Based Code Editor with Agregore 🌐 2 | 3 | ## Reflecting on the Journey 🎉 4 | 5 | ### Original Objective and Outcome 🎯 6 | The goal was to create an introductory tutorial for building a real-time, browser-based code editor similar to platforms like jsfiddle or codepen, using Agregore. Focusing on simplicity and beginner-friendliness, the tutorial aimed to cover HTML, CSS, and JavaScript essentials, along with an introduction to decentralized web protocols. The end result is a functional code editor that facilitates interaction with IPFS and Hypercore, aligning well with these objectives. 7 | 8 | ### Achievements and Evolution 📈 9 | - Successfully developed P2Pad, a user-friendly, real-time code editor capable of interacting with decentralized web technologies. 10 | - Struck a delicate balance between simplicity for beginners and a meaningful introduction to the practical applications of decentralized web protocols. 11 | 12 | ### Challenges and Insights 💡 13 | - Balancing simplicity with the complexity of a real-time in-browser code editor that interfaces with the decentralized web. 14 | - Keeping it simple without oversimplifying was crucial for the tutorial's effectiveness. 15 | - Insight: Keeping tutorials modular and reusing code enhances learning and development efficiency. 16 | 17 | ## The Development Experience 🛠️ 18 | 19 | ### Crafting the Application 🏗️ 20 | - Constructed using a blend of HTML, CSS, and JavaScript, with detailed steps provided in the tutorial. 21 | - Key aspects of the development process: 22 | - **What Went Well:** The integration of basic web technologies and the exploration of the potential of decentralized web protocols. 23 | - **Areas of Complexity:** Ensuring that the code was both simple enough for beginners while offering the desired DWeb interactions. 24 | - **Discovery:** The power of modularity and reusability in coding tutorials, especially when dealing with multiple features. 25 | 26 | ### Advice for Future Endeavors 🗣️ 27 | - Emphasize the importance of modularity and reusability in tutorial design as I found myself reusing some code from my previous Drag and Drop tutorial. 28 | - Consider developing a dedicated library for DWeb interactions to streamline future tutorial creation. 29 | 30 | ## Future Directions and Discussions 🚀 31 | 32 | ### Potential Improvements and Extensions 🔧 33 | - **Simplification:** Identify areas where the tutorial or the app itself could be simplified further, making it even more accessible to beginners. 34 | - **Modularity:** Enhance the separation of concerns and organize code more optimally, possibly through a dedicated library for DWeb interactions. 35 | - **Documentation:** Improve and expand documentation for reusable code snippets, aiding future users in locating and applying these functions effectively. 36 | 37 | ### Suggestions for Agregore Styling Guidelines 📝 38 | - **Agregore Style Guide:** A formal style guide that taps into and extends the Agregore theme would be highly beneficial. This could standardize the look and feel of applications developed using Agregore, leading to a more cohesive user experience. 39 | - **Documentation Emphasis:** The value of comprehensive and accessible documentation cannot be overstated, especially for beginners exploring new technologies like Agregore and the decentralized web. 40 | 41 | ## Conclusion 🌟 42 | This project was a great opportunity for a continued exploration into the realm of web development and decentralized web technologies. It underscored the importance of keeping tutorials simple yet informative, modular, and reusable. The journey highlighted the need for clear documentation and style guidelines in emerging software like Agregore, paving the way for future innovations and learning. 43 | -------------------------------------------------------------------------------- /blog/2024/03/demos-and-tutorials-final-round.md: -------------------------------------------------------------------------------- 1 | # Agregore demos and tutorials round 3 2 | 3 | This is our third and final round of apps and tutorials for our [FFDW grant](/blog/2023/01/demos-and-tutorials-announcement). In this round we have four new apps and tutorials as well as some final thoughts for what we want next. 4 | 5 | ## What we made 6 | 7 | This time around we had a mix of working more on some foundational tutorials and some usable apps that people could customize for themselves. 8 | 9 | ### Dirk's ACE Editor integration 10 | 11 | First up is Dirk's final tweak to his code editor series. This time he's figured out how to add third party dependencies to the editor, specifically ACE for syntax highlighting. You can follow along with the latest tutorial [here](/docs/tutorials/ipfs-3rdparty-dep/) or start at the beginning [here](/docs/tutorials/ipfs-browser-devenv/part-1). You can also see the live deployed version [here](/docs/examples/browser-devenv-v3/index.html). 12 | 13 | ### Dirk's Image Gallery 14 | 15 | Dirk's final app was an image gallery where you can upload images and then create a web app to display them in a pleasant tiled format. You can find the tutorial [here](/docs/tutorials/ipfs-gallery/) and a version of the app hosted on html [here](/docs/examples/ipfs-gallery/). This also builds on top of his development environment so you can use the new syntax highlighting and multi-file uploading features as you develop. 16 | 17 | ### Vincent's P2Pad code editor 18 | 19 | After getting comfortable with making apps and tutorials, Vincent worked on a simple [JSFiddle](https://jsfiddle.net/) or [Code Pen](https://codepen.io/) inspired editor where you enter HTML, CSS, and JavaScript into separate text inputs and render a live preview for quickly sketching up web pages and apps. This is a more barebones alternative to Dirk's editor which handles editing files in a directory whereas this generates single HTML pages. You can play around and make your own page [here](/docs/examples/p2pad/) or follow [the tutorial](/docs/tutorials/p2pad-code-editor) to make your own. 20 | 21 | ### Vincent's DLinkTree link aggregator 22 | 23 | For his last app Vincent made use of his P2Pad app too bootstrap making a small app for aggregating links and publishing them as a page on IPFS. You can view the 24 | [tutorial](/docs/tutorials/dlinktree-builder) to build it yourself or check out the final version [here](/docs/examples/dlinktree-builder/). Since this doesn't require Devtools to build you can even try to build it on an Android phone with [Agregore Mobile](https://github.com/AgregoreWeb/agregore-mobile/releases/tag/101.0.4951.53). 25 | 26 | ## Retrospectives 27 | 28 | As with the other milestones, keeping stuff small and reducing feature sets has helped keep the apps more focused and made it easier to build them. This time around we also saw the benefits of working on top of existing code which could be copied from past projects or in the case of the editors, used to make new projects with less fuss than devtools. It aslo looks like working on the app code before writing the tutorial has been best for avoiding rewriting the tutorial as you go. Overall we've been getting better at scoping tutorials and apps and finding ways to work with the tools we have available. 29 | 30 | ## Next steps 31 | 32 | From doing these tutorials we've found that we can get a lot done with just a bit of code and Agregore's protocol handlers. We'd love to iterate on this further and focus on the following: 33 | - Get the tutorials and docs translated to more languages like Spanish to reach more audiences. 34 | - Fix some of the bugs we found during app/tutorial development. 35 | - Add some better sharing built into Agregore itself. 36 | - Expand one some of the improvements we thought of for existing apps as follow up tutorials. 37 | 38 | If you want to work on this or fund work like this, get in touch by either joinng our [Matrix Channel](https://matrix.to/#/#agregore:mauve.moe) or sending us an [email](mailto:agregore@mauve.moe). 39 | 40 | --- 41 | 42 | Full Retrospectives: 43 | 44 | - [Ace Editor](/blog/2024/01/ipfs-3rd-party-dep-retrospective) 45 | - [IPFS Gallary](/blog/2024/03/ipfs-gallery-retrospective) 46 | - [P2Pad](/blog/2024/01/p2pad-code-editor-retrospective) 47 | - [DLinkTree](/blog/2024/03/dlinktree-builder-retrospective) 48 | -------------------------------------------------------------------------------- /blog/2024/03/dlinktree-builder-retrospective.md: -------------------------------------------------------------------------------- 1 | # Dlinktree Builder Retrospective: A Blend of Simplicity and Functionality 🌳 2 | 3 | ## Reflecting on the Creation Process 🚀 4 | 5 | ### Original Intent and Final Outcome 🎯 6 | The ambition behind the Dlinktree Builder was to devise a tool that embodies both simplicity and utility, allowing users to compile and share links. The final product not only adheres to this vision but also showcases a seamless integration with the decentralized web, enabling uploads via IPFS and Hypercore protocols. 7 | 8 | ### Achievements and Evolution 🌱 9 | - **Simplicity Maintained:** The tool remains intuitive, even for those with minimal technical background. 10 | - **Possible Customization:** The tutorial allows users to which aspect of the code they can edit for easy customization of their Dlinktree builder. 11 | - **Feature Richness:** Despite the emphasis on simplicity, we managed to incorporate a few features like editable titles, drag-and-drop image uploads, and dynamic link addition. 12 | - **Code Reusability:** Echoing practices from past projects, significant portions of the code were repurposed, enhancing efficiency and coherence across tutorials. 13 | 14 | ### Challenges and Revelations 💡 15 | - **Feature Parity vs. Simplicity:** Striking the right balance between incorporating features and maintaining usability remains the ultimate challenge. 16 | - **Joy of Building on Previous Work:** Leveraging code from previous tutorials not only saved time but also provided a sense of continuity and growth. Using the P2Pad to code a DLinktree Builder gives an example of how these tutorials can come together to create further apps. 17 | 18 | ## The Development Tapestry 🧵 19 | 20 | ### Constructing the Builder 🏗️ 21 | - Utilizing HTML, CSS, and JavaScript, this web app is both functional and aesthetically pleasing. All the detailed steps can be found in the accompanying [tutorial](/docs/tutorials/dlinktree-builder). 22 | - **What Worked Well:** Integrating decentralized web protocols gets smoother every time, thanks to the foundation laid in previous projects. 23 | - **Challenges Faced:** Ensuring the app remained approachable without diluting its functionality required careful consideration at each step. Having multiple ideas for improvement while needing to keep it short is a constant challenge. 24 | - **Unexpected Insights:** The modular approach to tutorial creation fosters a deeper understanding of DWeb technologies. 25 | 26 | ### Words of Wisdom to My Past Self 📜 27 | - Embrace the simplicity that comes with limited features; it often leads to a more focused tool. 28 | - Trust in the power of reusing and refining code; it’s not just efficient but also satisfying. 29 | 30 | ## Future Horizons and Musings 🌄 31 | 32 | ### Roadmap for Improvement 🔍 33 | - **Further Simplification:** Identify opportunities to streamline user interactions, making the tool even more accessible. 34 | - **Expansion of Features:** Consider incorporating fetch to get previously shared Dlinktrees and add new links to an existing structure. 35 | - **Better Code Organization:** With the growing complexity, organizing code into more modular components could enhance maintainability and readability. This could take the form of a dedicated Agregore Dev Library which would allow new developers to enter Agregore-enabled DWeb world seamlessly. 36 | 37 | ### Advocating for Enhanced Agregore Experiences 🌟 38 | - **Utility Functions Library:** A library of common functions used to upload to various DWeb protocols would allow developers to spin new apps quickly. 39 | - **Emphasis on Documentation:** Robust documentation is crucial, especially as we delve deeper into the possibilities of the decentralized web. 40 | 41 | ## Closing Thoughts 💫 42 | Building the Dlinktree Builder was a rewarding journey that not only tested the commitment to simplicity and functionality but also allows to revel in the process of creating something meaningful within the Agregore ecosystem. It reinforced the value of modular, reusable code and the importance of clear, user-friendly design. Looking forward, the insights gained from this project will undoubtedly shape the approach to future endeavors in the decentralized web space. 43 | -------------------------------------------------------------------------------- /blog/2024/03/ipfs-gallery-retrospective.md: -------------------------------------------------------------------------------- 1 | # Retrospective: IPFS gallery 2 | 3 | ## What was the outcome? 4 | 5 | ### Original goal and what you ended up making. 6 | 7 | I wanted to create a user focused app that leverages the development environment I've built in previous tutorials. To that purpose the idea was to create an app that allows an user to upload images to IPFS and create a gallery that can be shared. 8 | 9 | You can find the tutorial [here](/docs/tutorials/ipfs-gallery/) and a version of the app hosted on html [here](/docs/examples/ipfs-gallery/). 10 | 11 | ### Did you accomplish more or less than the original app idea? 12 | 13 | I'd say in the end this worked out well. I ended up not using some elements of the development environment that I planned to, but it made more sense to do it that way. 14 | 15 | ### Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 16 | 17 | The idea took shape very organically. Some of the decisions, like keeping images on the client side until publishing the gallery, serendipitously also allowed a final version of the app to be publisable over http! 18 | 19 | ## What was the process like? 20 | 21 | ### Describe making the app 22 | I sketched the outline for the tutorial and then developed the application in a way to fit the outline -> ie. interface first, then interaction and then saving to IPFS. 23 | 24 | Some reflection on the process: 25 | 26 | - **What went well?**: Doing most of the app development before writing the tutorial 27 | - **What took a lot of time?**: Getting the layout working without requiring too much CSS and JavaScript. I'm still convinced there is a better way, but I had to move on and finish the application. 28 | - **Did you find anything else to be unexpected about making the app?**: While the code editor is great in that it only requires the Agregore Web browser, I find that the flow of the editor caused me to loose sight of context repeatedly. In other development environments I work, I'd have an editor open with the code and that would stay open. With the integrated editor you have to reload the page each time you make an update and that is distracting. 29 | - **If you could give yourself advice when you started making the app, what would you tell yourself?**: Once again, cut down on requirements. I like adding features, but you really have to cut it down to end up with a working and understandable application and tutorial. 30 | 31 | ## Discussion 32 | 33 | ### Future improvements and ideas 34 | 35 | There are several interface improvements that can be done - deleting photos in a gallery, reordering photos, improving the layout, etc. 36 | 37 | ### Suggestions for Agregore or the underlying protocols 38 | 39 | I ran into an issue when I uploaded a folder containing a space - ie "folder 1". The upload work, but when doing a request like ?noResolve request, I got a 500 response. Here is a minimal example to trigger the error: 40 | 41 | ```js 42 | let file = new File(['a'], 'folder 1/file1.txt') 43 | let formData = new FormData() 44 | formData.append('file', file) 45 | let resp = await fetch('ipfs://bafyaabakaieac/', {method: 'put', body: formData}) 46 | const newCid = new URL(resp.headers.get('location')).origin 47 | resp = await fetch(newCid + '/folder 1/' + '?noResolve') 48 | ``` 49 | 50 | ## Conclusion 51 | 52 | I really enjoyed creating this application and I feel even though the gallery is minimal, it is both useable and useful! 53 | -------------------------------------------------------------------------------- /blog/index.md: -------------------------------------------------------------------------------- 1 | # Agregore Blog 2 | 3 | Here's a list of our recent Blog Posts. 4 | If you'd like to contribute fixes, please open an issue [on GitHub](https://github.com/AgregoreWeb/website). 5 | 6 | - [Demos and Tutorials Round 3 (final)](./2024/03/demos-and-tutorials-final-round) 7 | - [Demos and Tutorials Round 2](./2023/12/demos-and-tutorials-second-round) 8 | - [Demos and Tutorials Round 1](./2023/07/demos-and-tutorials-first-round) 9 | - [Demos and Tutorials Announcement](./2023/01/demos-and-tutorials-announcement) 10 | - [Considerations for P2P Web in Community Networks](./2022/09/considerations-for-p2p-web-community-networks) 11 | - [Agregore Mobile 1: IPFS and Chromium](./2022/04/ipfs-with-chromium) 12 | -------------------------------------------------------------------------------- /docs/ai.md: -------------------------------------------------------------------------------- 1 | ## 🤖 AI and LLM APIs 🤖 2 | 3 | Agregore enables P2P web apps to access user configurable Large Language Model APIS that are modelled after the text completion features in OpenAI. 4 | 5 | Unlike other browsers that provide a chat interface, that accesses the browser, we leave it up to web apps and extensions to do whatever they want while keeping control over the model to the user. 6 | 7 | As well, instead of onboarding you onto an expensive and [environment killing](https://impactclimate.mit.edu/2024/04/10/considering-the-environmental-impacts-of-generative-ai-to-spark-responsible-development/) cloud based LLM, we default to using a local [ollama](https://ollama.com/) install and a default 3B model that can be run locally on most consumer hardware. These models are a bit less effective at complex tasks but they take orders of magnitude less power, work fully offline (after initial setup) and keep all your conversations private. 8 | 9 | ### Setting up ollama 10 | 11 | Before you can run local models you will want to set up [ollama](https://ollama.com/download) on your computer. In the future we may integrate it directly into Agregore, if you want this feature, please [open an issue on our Github repository](https://github.com/AgregoreWeb/agregore-browser/issues/new). 12 | 13 | ```bash 14 | curl -fsSL https://ollama.com/install.sh | sh 15 | ``` 16 | 17 | From there you can test that it's running by navigating to the [models list](http://127.0.0.1:11434/v1/models). 18 | 19 | Note that the first time the LLM API is used it will download the configured `model` if it is not already downloaded. This will prompt the user before downloading and notify them when the download is done. 20 | 21 | ### API 📜 22 | 23 | #### `window.llm.isSupported` 24 | 25 | ```javascript 26 | if(await window.llm?.isSupported()) { 27 | // Use APIs here 28 | } else { 29 | alert("This website requires Agregore's LLM API") 30 | } 31 | ``` 32 | 33 | #### `window.llm.chat` 34 | 35 | ```javascript 36 | let messages = [ 37 | {role: 'system', content: 'You are a friendly AI assistant that likes to ramble about cats'}, 38 | {role: 'user', content: 'What is your favorite thing?'} 39 | ] 40 | 41 | const {role, content} = await window.llm.chat({ 42 | // this is mandatory 43 | messages, 44 | // this is optional 45 | maxTokens: 1337, 46 | temperature: 0.9, 47 | stop: ["cat"] 48 | }) 49 | 50 | // Now you can loop and keep a convo history 51 | messages.push({role, content}) 52 | ``` 53 | 54 | #### `window.llm.complete` 55 | 56 | ```javascript 57 | const text = await window.llm.complete('The capital of Canada is', { 58 | // this is optional 59 | maxTokens: 1337, 60 | // remove this and use the default unless you know what you're doing 61 | temperature: 0.9, 62 | stop: [" "] 63 | }) 64 | ``` 65 | 66 | ### Configuring ️✏️ 67 | 68 | You can configure your settings in your `.agregorerc` file which you can open with `Help > Edit Configuration File`. 69 | 70 | ```json 71 | { 72 | "llm": { 73 | "enabled": true, 74 | "baseURL": "http://127.0.0.1:11434/v1/", 75 | "apiKey": "ollama", 76 | "model": "qwen2.5-coder" 77 | } 78 | } 79 | ``` 80 | 81 | IF you don't want pages to access this feature at all, set `llm.enabled` to `false` and it'll auto deny any requests. 82 | 83 | #### Ollama Models 84 | 85 | If you're curious to try out different models, check out the list available in the [Ollama Library](https://ollama.com/library). 86 | 87 | #### OpenAI 88 | 89 | If your computer is very weak or if you're set on using the fancier cloud models, you can make use of [OpenAI](https://openai.com/) awnd their available models. 90 | 91 | First you should replace the `llm.apiKey` config in your `.agregorerc` with [an OpenAI API key](https://platform.openai.com/api-keys), and then replace the `llm.baseURL` field with `https://api.openai.com/v1/`. You will also want to choose a `model` to use like `gpt-4o-mini` from the [list on their website](https://platform.openai.com/docs/models). -------------------------------------------------------------------------------- /docs/bittorent-protocol-handlers.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/bittorent-protocol-handlers.md -------------------------------------------------------------------------------- /docs/examples/blocks-app/README.md: -------------------------------------------------------------------------------- 1 | Blocks 2 | ====== 3 | 4 | A personal app launcher/bookmarking tool. To begin, [visit the app](https://agregore.mauve.moe/docs/examples/blocks-app/) and click the "Save Blocks" button. This will create a copy of the app and save it to your Agregore browser. 5 | 6 | Add your links to the app and they'll automatically save to your own copy on 7 | IPNS. You can then send your IPNS link to your friends, and they can start adding or removing links into a copy of their own. 8 | 9 | -------------------------------------------------------------------------------- /docs/examples/blocks-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blocks 6 | 7 | 8 | 9 | 10 |
11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/examples/blocks-app/script.js: -------------------------------------------------------------------------------- 1 | const BLOCKS_KEY = 'blocks-app' 2 | const BLOCKS_IPNS_KEY = `ipns://localhost?key=${BLOCKS_KEY}` 3 | const BLOCKS_JSON_FILE = 'blocks.json' 4 | const FILES_TO_COPY = [ 5 | 'index.html', 6 | 'style.css', 7 | 'script.js' 8 | ] 9 | 10 | function navigate (link) { 11 | window.location.href = link 12 | } 13 | 14 | function makeInputElement (value) { 15 | const input = document.createElement('input') 16 | input.setAttribute('class', 'add-block-input') 17 | input.setAttribute('type', 'text') 18 | input.setAttribute('placeholder', 'Enter URL') 19 | input.setAttribute('value', value) 20 | return input 21 | } 22 | 23 | class Block { 24 | constructor (link) { 25 | this.data = link 26 | 27 | this.func = navigate.bind(this, link) 28 | this.drawEdit = this.drawEdit.bind(this) 29 | this.saveEdit = this.saveEdit.bind(this) 30 | this.deleteBlock = this.deleteBlock.bind(this) 31 | } 32 | 33 | draw () { 34 | const blockElement = document.createElement('div') 35 | blockElement.setAttribute('class', 'block') 36 | blockElement.append(document.createTextNode(this.data)) 37 | 38 | // on click 39 | blockElement.addEventListener('click', this.func) 40 | 41 | // edit button overlay 42 | const overlay = document.createElement('div') 43 | overlay.setAttribute('class', 'overlay') 44 | 45 | const editButton = document.createElement('a') 46 | editButton.setAttribute('href', '#') 47 | editButton.setAttribute('class', 'edit-button') 48 | editButton.append(document.createTextNode('✏️')) 49 | 50 | editButton.addEventListener('click', this.drawEdit) 51 | 52 | const deleteButton = document.createElement('a') 53 | deleteButton.setAttribute('href', '#') 54 | deleteButton.setAttribute('class', 'delete-button') 55 | deleteButton.append(document.createTextNode('❌')) 56 | 57 | deleteButton.addEventListener('click', this.deleteBlock) 58 | 59 | overlay.append(editButton) 60 | overlay.append(deleteButton) 61 | 62 | blockElement.append(overlay) 63 | return blockElement 64 | } 65 | 66 | drawEdit (event) { 67 | event.stopPropagation() 68 | 69 | const blockElement = event.target.parentElement.parentElement 70 | 71 | const input = makeInputElement(this.data) 72 | 73 | blockElement.replaceChild(input, blockElement.firstChild) 74 | 75 | const confirmButton = document.createElement('a') 76 | confirmButton.setAttribute('href', '#') 77 | confirmButton.setAttribute('class', 'confirm-edit-button') 78 | confirmButton.append(document.createTextNode('Save ✔️')) 79 | 80 | // find overlay in block element 81 | const overlay = blockElement.getElementsByClassName('overlay')[0] 82 | overlay.replaceChild(confirmButton, overlay.firstChild) 83 | 84 | blockElement.removeEventListener('click', this.func) 85 | 86 | confirmButton.addEventListener('click', this.saveEdit) 87 | } 88 | 89 | saveEdit (event) { 90 | event.stopPropagation() 91 | // get input 92 | const blockElement = event.target.parentElement.parentElement 93 | const input = blockElement.getElementsByTagName('input')[0] 94 | this.data = input.value 95 | this.func = navigate.bind(this, this.data) 96 | while (blockElement.firstChild) { 97 | blockElement.removeChild(blockElement.firstChild) 98 | } 99 | saveBlocks() 100 | drawBlocks() 101 | }; 102 | 103 | deleteBlock (event) { 104 | event.stopPropagation() 105 | blocksToDraw.splice(blocksToDraw.indexOf(this), 1) 106 | saveBlocks() 107 | drawBlocks() 108 | } 109 | } 110 | 111 | class AddBlock extends Block { 112 | constructor () { 113 | super() 114 | this.data = 'Add Block +' 115 | this.func = function () { 116 | const input = document.getElementsByClassName('add-block-input')[0] 117 | const link = input.value 118 | if (!link) { 119 | return 120 | } 121 | makeAndSaveNewBlock(link) 122 | drawBlocks() 123 | 124 | input.value = '' 125 | } 126 | } 127 | 128 | draw () { 129 | /* this one is a little different */ 130 | const blockElement = document.createElement('div') 131 | blockElement.setAttribute('class', 'block add-block') 132 | 133 | // prepend text input field 134 | const input = makeInputElement('') 135 | 136 | blockElement.append(input) 137 | blockElement.append(document.createElement('br')) 138 | 139 | const addButton = document.createElement('a') 140 | addButton.setAttribute('class', 'add-block-button') 141 | addButton.append(document.createTextNode(this.data)) 142 | addButton.addEventListener('click', this.func) 143 | 144 | blockElement.append(addButton) 145 | 146 | return blockElement 147 | } 148 | } 149 | 150 | function makeAndSaveNewBlock (link, position = -1) { 151 | // make the block object out of a link 152 | blocksToDraw.push(new Block(link)) 153 | saveBlocks() 154 | } 155 | 156 | function makeNewBlock (link) { 157 | blocksToDraw.push(new Block(link)) 158 | } 159 | 160 | function drawBlocks () { 161 | const shelf = document.getElementById('shelf') 162 | while (shelf.firstChild) { 163 | shelf.removeChild(shelf.firstChild) 164 | } 165 | shelf.append(new AddBlock().draw()) 166 | for (const block of blocksToDraw) { 167 | // draw the block 168 | shelf.append(block.draw()) 169 | } 170 | } 171 | 172 | async function getOwnIPNS () { 173 | try { 174 | const response = await fetch(BLOCKS_IPNS_KEY) 175 | if (!response.ok) throw new Error(await response.text()) 176 | return response.url 177 | } catch { 178 | // Couldn't get the URL! 179 | console.log('Creating site') 180 | await initOwnSite() 181 | return getOwnIPNS() 182 | } 183 | } 184 | 185 | async function initOwnSite () { 186 | const response = await fetch(BLOCKS_IPNS_KEY, { method: 'post' }) 187 | const siteURL = response.headers.get('Location') 188 | const form = new FormData() 189 | for (const file of FILES_TO_COPY) { 190 | const sourceURL = new URL(`./${file}`, window.location.href).href 191 | const fileResponse = await fetch(sourceURL) 192 | form.append('file', await fileResponse.blob(), file) 193 | } 194 | 195 | console.log('Uploading initial site contents') 196 | const uploadResponse = await fetch(siteURL, { 197 | method: 'put', 198 | body: form 199 | }) 200 | 201 | if (!uploadResponse.ok) { 202 | throw new Error(`Cannot create site: ${response.status}\n${await uploadResponse.text()}`) 203 | } 204 | } 205 | 206 | async function saveBlocks () { 207 | copyButton = document.getElementsByClassName('copy-button')[0] 208 | console.log(copyButton) 209 | copyButton.textContent = 'Saving... ⏳' 210 | 211 | 212 | const data = [] 213 | for (const block of blocksToDraw) { 214 | data.push(block.data) 215 | } 216 | const siteURL = await getOwnIPNS() 217 | console.log('About to save blocks list to', siteURL) 218 | const blockFileURL = new URL(BLOCKS_JSON_FILE, siteURL).href 219 | await fetch(blockFileURL, { 220 | method: 'put', 221 | body: JSON.stringify(data) 222 | }) 223 | copyButton.textContent = 'Saved! ✅' 224 | 225 | if (!window.location.href.includes(siteURL)) { 226 | window.location.href = siteURL 227 | } 228 | } 229 | 230 | async function loadBlocks () { 231 | const blockFileURL = new URL(BLOCKS_JSON_FILE, window.location.href).href 232 | try { 233 | const dataResponse = await fetch(blockFileURL) 234 | 235 | if (dataResponse.ok) { 236 | const data = await dataResponse.json() 237 | for (const link of data) { 238 | blocksToDraw.push(new Block(link)) 239 | } 240 | drawBlocks() 241 | } else { 242 | throw new Error(dataResponse) 243 | } 244 | } catch(dataResponse) { 245 | console.error(`Could not load blocks: ${dataResponse.status} - ${await dataResponse.text}`) 246 | makeNewBlock('https://ipfs-search.com/') 247 | drawBlocks() 248 | } 249 | } 250 | 251 | const blocksToDraw = [] 252 | 253 | function initializeRoot () { 254 | const rootDiv = document.getElementById('root') 255 | rootDiv.textContent = 'My Collection' 256 | 257 | const createCopy = document.createElement('button') 258 | createCopy.setAttribute('href', '#') 259 | createCopy.setAttribute('class', 'copy-button') 260 | createCopy.append(document.createTextNode('Save Blocks 📝')) 261 | createCopy.addEventListener('click', async () => { 262 | saveBlocks() 263 | }) 264 | 265 | rootDiv.append(createCopy) 266 | 267 | 268 | const shelf = document.createElement('div') 269 | shelf.setAttribute('id', 'shelf') 270 | rootDiv.append(shelf) 271 | } 272 | 273 | window.onload = function run () { 274 | initializeRoot() 275 | loadBlocks() 276 | drawBlocks() 277 | } 278 | -------------------------------------------------------------------------------- /docs/examples/blocks-app/style.css: -------------------------------------------------------------------------------- 1 | /* Reset styles */ 2 | *, *::before, *::after { box-sizing: border-box; } 3 | * { margin: 0; } 4 | html, body { height: 100%; } 5 | 6 | body { 7 | line-height: 1.5; 8 | -webkit-font-smoothing: antialiased; 9 | background-color: #000000; 10 | color: #ffffff; 11 | font-family: sans-serif; 12 | width: 94%; 13 | margin: 0 3%; 14 | } 15 | 16 | img, picture, video, canvas, svg { 17 | display: block; 18 | max-width: 100%; 19 | } 20 | 21 | input, button, textarea, select { 22 | font: inherit; 23 | } 24 | 25 | #root { 26 | display: block; 27 | } 28 | 29 | #shelf { 30 | display: grid; 31 | grid-gap: 10%; 32 | /* to make sure there's at least two elements in a row: */ 33 | grid-template-columns: repeat(auto-fit, minmax(20%, 1fr)); 34 | min-width: 100%; 35 | 36 | } 37 | 38 | .block { 39 | position: relative; 40 | border: 1px solid #ffffff; 41 | width: 100%; 42 | padding: 5%; 43 | aspect-ratio: 1; 44 | overflow: hidden; 45 | word-wrap: break-word; 46 | border-radius: 0.1dvmax; 47 | } 48 | 49 | .add-block { 50 | text-align: center; 51 | padding: 5%; 52 | } 53 | 54 | .confirm-edit-button { 55 | text-decoration: none; 56 | color: white; 57 | } 58 | 59 | .add-block input, .block input { 60 | padding: 0; 61 | background: #111111; 62 | width: 100%; 63 | height: 80%; 64 | color: aqua; 65 | border: none; 66 | outline: none; 67 | } 68 | 69 | div.overlay { 70 | position: absolute; 71 | bottom: 7.5%; 72 | right: 3%; 73 | width: 100%; 74 | text-align: center; 75 | 76 | } 77 | 78 | a.edit-button, a.delete-button { 79 | text-decoration: none; 80 | padding: 0 5%; 81 | } 82 | 83 | .copy-button { 84 | text-decoration: none; 85 | margin: 0 5%; 86 | float: right; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v2/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Agregore self-hosted devenv V2 5 |

Agregore self-hosted devenv V2

6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v2/files/lib.js: -------------------------------------------------------------------------------- 1 | async function updateSite(filename, content){ 2 | const resp = await fetch(`${window.origin}/${filename}`, {method: 'put', body: content}) 3 | const newLocation = resp.headers.get('location') 4 | window.location = new URL(newLocation).origin 5 | } 6 | 7 | async function publishSite(){ 8 | let resp = await fetch('ipns://localhost/?key=mysite', {method: 'POST'}) 9 | const key = resp.headers.get('location') 10 | resp = await fetch(key, {method: 'POST', body: window.origin}) 11 | window.location = new URL(resp.headers.get('location')).origin 12 | } 13 | 14 | async function loadFile(filename){ 15 | const resp = await fetch(filename) 16 | const content = await resp.text() 17 | document.getElementById('idFilenameInput').value = filename 18 | document.getElementById('idContentInput').value = content 19 | } 20 | 21 | async function listDir(path){ 22 | const resp = await fetch(path + '?noResolve') 23 | const files = await resp.json() 24 | return files 25 | } 26 | 27 | async function loadSidebar(){ 28 | const sidebar = document.getElementById('idSidebar') 29 | const files = await listDir(window.origin) 30 | const list = document.createElement('ul') 31 | list.style = "list-style: none; padding-inline-start: 0;" 32 | 33 | async function makeFileListElements(path, file) { 34 | if (file.endsWith('/')){ 35 | let subfiles = await listDir(window.origin + path + file) 36 | let elements = await Promise.all( 37 | subfiles.map(subfile => 38 | makeFileListElements(path + file, subfile) 39 | ) 40 | ) 41 | return elements.reduce( (arr, el) => [...arr, ...el] ) 42 | } 43 | let li = document.createElement('li') 44 | li.innerHTML = `${path}${file}` 45 | li.querySelector('a').onclick = e => loadFile(path + file) 46 | return [li] 47 | } 48 | 49 | await Promise.all( 50 | files.map(async file => { 51 | let elements = await makeFileListElements('/', file) 52 | elements.map(li => list.appendChild(li)) 53 | }) 54 | ) 55 | 56 | sidebar.appendChild(list) 57 | 58 | if (window.origin.startsWith('ipfs://')){ 59 | const button = document.createElement('button') 60 | button.innerHTML = 'Publish site' 61 | button.onclick = e => { 62 | e.preventDefault() 63 | publishSite() 64 | } 65 | sidebar.appendChild(button) 66 | } 67 | 68 | let dirUploadForm = document.createElement('directory-upload') 69 | 70 | dirUploadForm.addEventListener('dirUploadStart', e => { 71 | console.log('onDirUploadStart', e.detail) 72 | const modal = document.createElement('dialog') 73 | modal.id = 'uploadStatus' 74 | modal.innerHTML = `

Uploading ${e.detail.fileCount} file(s)

` 75 | document.body.appendChild(modal) 76 | modal.showModal() 77 | }) 78 | 79 | dirUploadForm.addEventListener('dirUpload', e => { 80 | console.log('onDirUpload', e) 81 | let modal = document.getElementById('uploadStatus') 82 | modal.innerHTML = `

Upload complete. You will be redirected shortly

` 83 | setTimeout(te => window.location = e.detail.cid, 5000) 84 | }) 85 | 86 | sidebar.appendChild(dirUploadForm) 87 | } 88 | 89 | async function showEditor(){ 90 | let editorDiv = document.getElementById("editor") 91 | if (!editorDiv){ 92 | editorDiv = document.createElement('div') 93 | editorDiv.id = 'editor' 94 | } 95 | 96 | editorDiv.style = `display: flex; 97 | flex-direction: column; 98 | position: absolute; 99 | top: 0; 100 | left: 0; 101 | width: 100vw; 102 | height: 100vh; 103 | background-color: rgb(233 233 233 / 95%); 104 | ` 105 | 106 | editorDiv.innerHTML = `
107 |

Files

108 |
109 |
110 | 111 | 112 | 113 | 114 | 115 |
116 |
` 117 | document.body.appendChild(editorDiv) 118 | const form = document.getElementById('idForm') 119 | form.onsubmit = e => { 120 | e.preventDefault() 121 | const filename = document.getElementById('idFilenameInput').value 122 | const content = document.getElementById('idContentInput').value 123 | updateSite(filename, content) 124 | } 125 | 126 | await loadSidebar() 127 | } 128 | 129 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v2/files/style.css: -------------------------------------------------------------------------------- 1 | @import url("agregore://theme/vars.css"); 2 | html { 3 | font-family: var(--ag-theme-font-family); 4 | } 5 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v2/files/upload.js: -------------------------------------------------------------------------------- 1 | async function batchUpload(fileList, pathPrefix){ 2 | let currentCid = window.origin 3 | let files = Array.from(fileList) 4 | while (files.length > 0){ 5 | let batch = [files.pop()] 6 | batchPath = batch[0].webkitRelativePath.split('/').slice(0,-1).join('/') 7 | console.log('Looking for files with matching path', batchPath) 8 | for (var i=files.length-1; i>=0; i--){ 9 | if(batchPath == files[i].webkitRelativePath.split('/').slice(0,-1).join('/')){ 10 | batch.push(files[i]) 11 | files[i] = files[files.length-1] // copy last element over matched file 12 | files.pop() // remove last element 13 | } 14 | } 15 | 16 | console.log(batch) 17 | let formData = new FormData() 18 | for (const file of batch){ 19 | formData.append('file', file) 20 | } 21 | // let putUrl = [currentCid, pathPrefix, batchPath].join('/') // <-- uncomment this line if you are using Agregore < 2.4.0 22 | let putUrl = [currentCid, pathPrefix].join('/') // <-- delete this line if you are using Agregore < 2.4.0 23 | const resp = await fetch(putUrl, {method: 'put', body: formData}) 24 | currentCid = new URL(resp.headers.get('location')).origin 25 | console.log(currentCid + (pathPrefix && '/') + pathPrefix) 26 | console.log(`Uploaded ${fileList.length-files.length}/${fileList.length} files`) 27 | } 28 | return currentCid 29 | } 30 | 31 | 32 | class DirectoryUpload extends HTMLElement { 33 | constructor () { 34 | super() 35 | } 36 | 37 | connectedCallback () { 38 | this.innerHTML = `
39 |

Choose files to upload

40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 |
48 | ` 63 | const form = this.querySelector('#dirForm') 64 | form.addEventListener('submit', this.onSubmit.bind(this)) 65 | } 66 | 67 | async onSubmit(e) { 68 | console.log('📥 📥 📥') 69 | e.preventDefault() 70 | 71 | let currentCid = window.origin 72 | 73 | // get the prefix and clean it up a bit 74 | let pathPrefix = document.querySelector('#idPathPrefixInput').value 75 | while (pathPrefix.startsWith('/')){ 76 | pathPrefix = pathPrefix.slice(1) 77 | } 78 | while (pathPrefix.endsWith('/')){ 79 | pathPrefix = pathPrefix.slice(0, -1) 80 | } 81 | 82 | const fileInput = document.querySelector('#dirForm input[type="file"]') 83 | this.dispatchEvent(new CustomEvent('dirUploadStart', { detail: { fileCount: fileInput.files.length } })) 84 | const newCid = await batchUpload(fileInput.files, pathPrefix) 85 | this.dispatchEvent(new CustomEvent('dirUpload', { detail: { cid: newCid } })) 86 | } 87 | } 88 | 89 | customElements.define('directory-upload', DirectoryUpload); 90 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agregore IPFS Development Environment V2 4 |

Agregore IPFS Development Environment V2

5 |

This page needs to be opened using the Agregore Browser to function correctly.

6 |

When you click start, a new IPFS site will be created for you containing a basic development environment you can use to update the site.

7 | 8 | 9 | 37 | 38 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v3/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agregore self-hosted devenv V3 4 | 5 |

Agregore self-hosted devenv V3

6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v3/files/lib.js: -------------------------------------------------------------------------------- 1 | async function updateSite(filename, content){ 2 | const resp = await fetch(`${window.origin}/${filename}`, {method: 'put', body: content}) 3 | const newLocation = resp.headers.get('location') 4 | window.location = new URL(newLocation).origin 5 | } 6 | 7 | async function publishSite(){ 8 | let resp = await fetch('ipns://localhost/?key=mysite', {method: 'POST'}) 9 | const key = resp.headers.get('location') 10 | resp = await fetch(key, {method: 'POST', body: window.origin}) 11 | window.location = new URL(resp.headers.get('location')).origin 12 | } 13 | 14 | async function loadFile(filename){ 15 | const resp = await fetch(filename) 16 | const content = await resp.text() 17 | document.getElementById('idFilenameInput').value = filename 18 | window.editor.setValue(content) 19 | if (filename.match(/\.js/)){ 20 | editor.session.setMode("ace/mode/javascript") 21 | } else if (filename.match(/\.html/)){ 22 | editor.session.setMode("ace/mode/html") 23 | } else if (filename.match(/\.css/)){ 24 | editor.session.setMode("ace/mode/css") 25 | } else { 26 | editor.session.setMode("ace/mode/text") 27 | } 28 | 29 | } 30 | 31 | async function listDir(path){ 32 | const resp = await fetch(path + '?noResolve') 33 | const files = await resp.json() 34 | return files 35 | } 36 | 37 | async function loadSidebar(){ 38 | const sidebar = document.querySelector('.sideBar') 39 | const files = await listDir(window.origin) 40 | const list = document.createElement('ul') 41 | list.style = "list-style: none; padding-inline-start: 0;" 42 | 43 | async function makeFileListElements(path, file) { 44 | if (path == '/' && file == 'vendor/') { 45 | let li = document.createElement('li') 46 | li.innerHTML = `/vendor/` 47 | return [li] 48 | } 49 | if (file.endsWith('/')){ 50 | let subfiles = await listDir(window.origin + path + file) 51 | let elements = await Promise.all( 52 | subfiles.map(subfile => 53 | makeFileListElements(path + file, subfile) 54 | ) 55 | ) 56 | return elements.reduce( (arr, el) => [...arr, ...el] ) 57 | } 58 | let li = document.createElement('li') 59 | li.innerHTML = `${path}${file}` 60 | li.querySelector('a').onclick = e => loadFile(path + file) 61 | return [li] 62 | } 63 | 64 | await Promise.all( 65 | files.map(async file => { 66 | let elements = await makeFileListElements('/', file) 67 | elements.map(li => list.appendChild(li)) 68 | }) 69 | ) 70 | 71 | sidebar.appendChild(list) 72 | 73 | if (window.origin.startsWith('ipfs://')){ 74 | const button = document.createElement('button') 75 | button.innerHTML = 'Publish site' 76 | button.onclick = e => { 77 | e.preventDefault() 78 | publishSite() 79 | } 80 | sidebar.appendChild(button) 81 | } 82 | 83 | let dirUploadForm = document.createElement('directory-upload') 84 | 85 | dirUploadForm.addEventListener('dirUploadStart', e => { 86 | console.log('onDirUploadStart', e.detail) 87 | const modal = document.createElement('dialog') 88 | modal.id = 'uploadStatus' 89 | modal.innerHTML = `

Uploading ${e.detail.fileCount} file(s)

` 90 | document.body.appendChild(modal) 91 | modal.showModal() 92 | }) 93 | 94 | dirUploadForm.addEventListener('dirUpload', e => { 95 | console.log('onDirUpload', e) 96 | let modal = document.getElementById('uploadStatus') 97 | modal.innerHTML = `

Upload complete. You will be redirected shortly

` 98 | setTimeout(te => window.location = e.detail.cid, 5000) 99 | }) 100 | 101 | sidebar.appendChild(dirUploadForm) 102 | } 103 | 104 | async function showEditor(){ 105 | 106 | // Add style 107 | let styleLink = document.createElement('link') 108 | styleLink.href = 'style.css' 109 | styleLink.rel = 'stylesheet' 110 | document.head.appendChild(styleLink) 111 | 112 | let aceCss = document.createElement('link') 113 | aceCss.href="/vendor/ace-builds/css/ace.css" 114 | aceCss.rel="stylesheet" 115 | document.head.appendChild(aceCss) 116 | 117 | // Add JavaScript 118 | await import('./upload.js') 119 | await import('/vendor/ace-builds/src-min-noconflict/ace.js') 120 | ace.config.set('basePath', '/vendor/ace-builds/src-min-noconflict/') 121 | 122 | let editorDiv = document.getElementById("editor") 123 | if (!editorDiv){ 124 | editorDiv = document.createElement('div') 125 | editorDiv.id = 'editor' 126 | editorDiv.classList.add('siteEditor') 127 | } 128 | 129 | editorDiv.innerHTML = `
130 | 133 |
134 | 135 | 136 | 137 | 138 | 139 |
140 |
` 141 | document.body.appendChild(editorDiv) 142 | 143 | window.editor = ace.edit("idContentInput") 144 | 145 | const form = document.getElementById('idForm') 146 | form.onsubmit = e => { 147 | e.preventDefault() 148 | const filename = document.getElementById('idFilenameInput').value 149 | const content = window.editor.getValue() 150 | updateSite(filename, content) 151 | } 152 | 153 | await loadSidebar() 154 | } 155 | 156 | window.addEventListener('load', e => { 157 | window.showEditor = showEditor 158 | document.addEventListener('keydown', e => { 159 | if( e.ctrlKey && e.key == 'i' ){ 160 | showEditor().catch(console.error) 161 | } 162 | }) 163 | }) 164 | 165 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v3/files/style.css: -------------------------------------------------------------------------------- 1 | @import url("agregore://theme/vars.css"); 2 | html { 3 | font-family: var(--ag-theme-font-family); 4 | } 5 | 6 | .siteEditor { 7 | display: flex; 8 | flex-direction: column; 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | width: 100vw; 13 | height: 100vh; 14 | background-color: rgb(233 233 233 / 95%); 15 | } 16 | 17 | .siteEditor > div { 18 | display: flex; 19 | flex-grow: 1; 20 | padding: 1em; 21 | } 22 | 23 | .siteEditor form { 24 | flex-grow: 1; 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | 29 | .siteEditor .sideBar { 30 | padding-right: 1em; 31 | width: 20vw; 32 | overflow:scroll; 33 | } 34 | 35 | #idForm pre { 36 | flex-grow: 1; 37 | margin: 0; 38 | font-size: 14px; 39 | } 40 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v3/files/upload.js: -------------------------------------------------------------------------------- 1 | async function batchUpload(fileList, pathPrefix){ 2 | let currentCid = window.origin 3 | let files = Array.from(fileList) 4 | while (files.length > 0){ 5 | let batch = [files.pop()] 6 | let batchPath = batch[0].webkitRelativePath.split('/').slice(0,-1).join('/') 7 | console.log('Looking for files with matching path', batchPath) 8 | for (var i=files.length-1; i>=0; i--){ 9 | if(batchPath == files[i].webkitRelativePath.split('/').slice(0,-1).join('/')){ 10 | batch.push(files[i]) 11 | files[i] = files[files.length-1] // copy last element over matched file 12 | files.pop() // remove last element 13 | } 14 | } 15 | 16 | console.log(batch) 17 | let formData = new FormData() 18 | for (const file of batch){ 19 | formData.append('file', file) 20 | } 21 | let putUrl = [currentCid, pathPrefix].join('/') 22 | const resp = await fetch(putUrl, {method: 'put', body: formData}) 23 | currentCid = new URL(resp.headers.get('location')).origin 24 | console.log(currentCid + (pathPrefix && '/') + pathPrefix) 25 | console.log(`Uploaded ${fileList.length-files.length}/${fileList.length} files`) 26 | } 27 | return currentCid 28 | } 29 | 30 | class DirectoryUpload extends HTMLElement { 31 | constructor () { 32 | super() 33 | } 34 | 35 | connectedCallback () { 36 | this.innerHTML = `
37 |

Choose files to upload

38 | 39 | 40 | 41 |
42 | 43 |
44 | 45 |
46 | ` 61 | const form = this.querySelector('#dirForm') 62 | form.addEventListener('submit', this.onSubmit.bind(this)) 63 | } 64 | 65 | async onSubmit(e) { 66 | console.log('📥 📥 📥') 67 | e.preventDefault() 68 | 69 | let currentCid = window.origin 70 | 71 | // get the prefix and clean it up a bit 72 | let pathPrefix = document.querySelector('#idPathPrefixInput').value 73 | while (pathPrefix.startsWith('/')){ 74 | pathPrefix = pathPrefix.slice(1) 75 | } 76 | while (pathPrefix.endsWith('/')){ 77 | pathPrefix = pathPrefix.slice(0, -1) 78 | } 79 | 80 | const fileInput = document.querySelector('#dirForm input[type="file"]') 81 | this.dispatchEvent(new CustomEvent('dirUploadStart', { detail: { fileCount: fileInput.files.length } })) 82 | const newCid = await batchUpload(fileInput.files, pathPrefix) 83 | this.dispatchEvent(new CustomEvent('dirUpload', { detail: { cid: newCid } })) 84 | } 85 | } 86 | 87 | customElements.define('directory-upload', DirectoryUpload); 88 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv-v3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | Agregore IPFS Development Environment V3 8 |

Agregore IPFS Development Environment V3

9 |

This page should be opened using the Agregore Browser to function correctly.

10 |

When you click start, a new IPFS site will be created for you containing a basic development environment you can use to update the site.

11 | 12 | 13 | 49 | 50 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agregore IPFS Development Environment 4 | 5 |

Agregore IPFS Development Environment

6 |

This page needs to be opened using the Agregore Browser to function correctly.

7 |

When you click start, a new IPFS site will be created for you containing a basic development environment you can use to update the site.

8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv/index.html.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | Editor Example 4 | 5 |

Hello world

6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv/lib.js: -------------------------------------------------------------------------------- 1 | const EMPTY_IPFS_URL = 'ipfs://bafyaabakaieac/' 2 | 3 | async function addFiles(files){ 4 | const formData = new FormData() 5 | files.forEach( (file, index) => { 6 | formData.append('file', file) 7 | }) 8 | const resp = await fetch(EMPTY_IPFS_URL, {method: 'put', body: formData}) 9 | const newLocation = resp.headers.get('location') 10 | window.location = new URL(newLocation).origin 11 | } 12 | 13 | async function bootstrapSite(){ 14 | let resp = await fetch('lib.js.template') 15 | const libjs = await resp.text() 16 | resp = await fetch('index.html.template') 17 | const indexhtml = await resp.text() 18 | 19 | addFiles([ 20 | new File([indexhtml], 'index.html', {type: 'text/html'}), 21 | new File([libjs], 'lib.js', {type: 'text/javascript'}), 22 | ]) 23 | } 24 | -------------------------------------------------------------------------------- /docs/examples/browser-devenv/lib.js.template: -------------------------------------------------------------------------------- 1 | async function updateSite(filename, content){ 2 | const resp = await fetch(`${window.origin}/${filename}`, {method: 'put', body: content}) 3 | const newLocation = resp.headers.get('location') 4 | window.location = new URL(newLocation).origin 5 | } 6 | 7 | async function publishSite(){ 8 | let resp = await fetch('ipns://localhost/?key=mysite', {method: 'POST'}) 9 | const key = resp.headers.get('location') 10 | resp = await fetch(key, {method: 'POST', body: window.origin}) 11 | window.location = new URL(resp.headers.get('location')).origin 12 | } 13 | 14 | async function loadFile(filename){ 15 | const resp = await fetch(filename) 16 | const content = await resp.text() 17 | document.getElementById('idFilenameInput').value = filename 18 | document.getElementById('idContentInput').value = content 19 | } 20 | 21 | async function listDir(path){ 22 | const resp = await fetch(path + '?noResolve') 23 | const files = await resp.json() 24 | return files 25 | } 26 | 27 | async function loadSidebar(){ 28 | const sidebar = document.getElementById('idSidebar') 29 | const files = await listDir(window.origin) 30 | const list = document.createElement('ul') 31 | list.style = "list-style: none; padding-inline-start: 0;" 32 | 33 | async function makeFileListElements(path, file) { 34 | if (file.endsWith('/')){ 35 | let subfiles = await listDir(window.origin + path + file) 36 | let elements = await Promise.all( 37 | subfiles.map(subfile => 38 | makeFileListElements(path + file, subfile) 39 | ) 40 | ) 41 | return elements.reduce( (arr, el) => [...arr, ...el] ) 42 | } 43 | let li = document.createElement('li') 44 | li.innerHTML = `${path}${file}` 45 | li.querySelector('a').onclick = e => loadFile(path + file) 46 | return [li] 47 | } 48 | 49 | await Promise.all( 50 | files.map(async file => { 51 | let elements = await makeFileListElements('/', file) 52 | elements.map(li => list.appendChild(li)) 53 | }) 54 | ) 55 | 56 | sidebar.appendChild(list) 57 | 58 | if (window.origin.startsWith('ipfs://')){ 59 | const button = document.createElement('button') 60 | button.innerHTML = 'Publish site' 61 | button.onclick = e => { 62 | e.preventDefault() 63 | publishSite() 64 | } 65 | sidebar.appendChild(button) 66 | } 67 | } 68 | 69 | async function showEditor(){ 70 | let editorDiv = document.getElementById("editor") 71 | if (!editorDiv){ 72 | editorDiv = document.createElement('div') 73 | editorDiv.id = 'editor' 74 | } 75 | editorDiv.style = `display: flex; 76 | flex-direction: column; 77 | position: absolute; 78 | top: 0; 79 | left: 0; 80 | width: 100vw; 81 | height: 100vh; 82 | background-color: rgb(233 233 233 / 95%); 83 | ` 84 | editorDiv.innerHTML = `
85 |

Files

86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 |
94 |
` 95 | document.body.appendChild(editorDiv) 96 | const form = document.getElementById('idForm') 97 | form.onsubmit = e => { 98 | e.preventDefault() 99 | const filename = document.getElementById('idFilenameInput').value 100 | const content = document.getElementById('idContentInput').value 101 | updateSite(filename, content) 102 | } 103 | 104 | await loadSidebar() 105 | 106 | } 107 | -------------------------------------------------------------------------------- /docs/examples/dlinktree-builder/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DLINKTREE BUILDER 5 | 6 |
7 |
8 |

9 | Click here to edit your title 10 |

11 |
12 | Click to upload image 13 |
14 | 17 | 18 | 19 | 28 | 29 | 33 |
34 |
35 |
36 | 45 | 46 |
47 |
48 | 49 |
50 | 51 | -------------------------------------------------------------------------------- /docs/examples/dlinktree-builder/styles.css: -------------------------------------------------------------------------------- 1 | @import url("agregore://theme/style.css"); 2 | 3 | :root { 4 | --gap: 5px; 5 | --half-gap: calc(var(--gap) / 2); 6 | } 7 | 8 | body, 9 | * { 10 | padding: 0; 11 | margin: 0; 12 | font-family: var(--ag-theme-font-family); 13 | background: var(--ag-theme-background); 14 | color: var(--ag-theme-text); 15 | box-sizing: border-box; 16 | } 17 | 18 | main { 19 | width: 100vw; 20 | height: 100vh; 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | .grid-container { 26 | display: grid; 27 | grid-template-columns: 1fr; 28 | grid-template-rows: 1fr 4fr 1fr; 29 | height: 94vh; 30 | gap: var(--gap); 31 | } 32 | 33 | .grid-container > *, 34 | .grid-container > textarea { 35 | padding: var(--gap); 36 | overflow: auto; 37 | border: 1px solid var(--ag-theme-primary); 38 | resize: none; 39 | height: 100%; 40 | min-height: 1rem; 41 | } 42 | 43 | div textarea:focus, 44 | #uploadtoDWebButton:hover { 45 | outline: 2px solid var(--ag-theme-secondary); 46 | color: var(--ag-theme-text); 47 | } 48 | 49 | #dweb-container, 50 | #uploadListBox { 51 | display: flex; 52 | padding: var(--half-gap); 53 | align-items: center; 54 | justify-content: space-between; 55 | } 56 | 57 | #dweb-container > *, 58 | #uploadListBox li { 59 | display: flex; 60 | align-items: flex-end; 61 | gap: var(--half-gap); 62 | } 63 | 64 | .links { 65 | display: flex; 66 | flex-direction: column; 67 | gap: var(--gap); 68 | padding: var(--gap); 69 | } 70 | 71 | span { 72 | pointer-events: cursor; 73 | color: var(--ag-theme-secondary); 74 | } 75 | 76 | span:hover { 77 | color: var(--ag-theme-primary); 78 | } 79 | 80 | select { 81 | width: auto; 82 | } 83 | 84 | .dnone { 85 | display: none; 86 | } 87 | 88 | @media screen and (max-width: 768px) { 89 | #dweb-container, 90 | #dweb-container > *, 91 | .grid-container { 92 | flex-direction: column; 93 | align-items: flex-start; 94 | grid-template-columns: 1fr; /* Adjusts to a single column for mobile */ 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /docs/examples/drag-and-drop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Agregore Drag and Drop 5 | 6 |
7 | 14 |
15 |

Drop a file to upload it

16 |
17 | 18 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /docs/examples/drag-and-drop/script.js: -------------------------------------------------------------------------------- 1 | function $(query) { 2 | return document.querySelector(query) 3 | } 4 | 5 | const uploadBox = $('#uploadBox') 6 | uploadBox.ondragover = () => false 7 | uploadBox.ondrop = async (e) => { 8 | e.preventDefault() 9 | const { dataTransfer } = e 10 | if(!dataTransfer) return 11 | 12 | await uploadFiles(dataTransfer.files); 13 | } 14 | 15 | const uploadListBox = $('#uploadListBox') 16 | 17 | const protocolSelect = $('#protocolSelect') 18 | 19 | async function uploadFiles(files) { 20 | const protocol = protocolSelect.value; 21 | 22 | const formData = new FormData(); 23 | // Append each file to the FormData 24 | for (const file of files) { 25 | formData.append('file', file, file.name); 26 | } 27 | 28 | // Construct the URL based on the protocol 29 | let url; 30 | if (protocol === 'hyper') { 31 | const hyperdriveUrl = await generateHyperdriveKey('drag-and-drop'); 32 | url = `${hyperdriveUrl}`; 33 | } else { 34 | url = `ipfs://bafyaabakaieac/`; 35 | } 36 | 37 | // Perform the upload for each file 38 | try { 39 | const response = await fetch(url, { 40 | method: 'PUT', 41 | body: formData, 42 | }); 43 | 44 | if (!response.ok) { 45 | addError(files, await response.text()); 46 | } 47 | const urlResponse = protocol === 'hyper' ? response.url : response.headers.get('Location'); 48 | addURL(urlResponse); 49 | } catch (error) { 50 | console.error(`Error uploading ${files}:`, error); 51 | } 52 | } 53 | 54 | 55 | 56 | async function generateHyperdriveKey(name) { 57 | try { 58 | const response = await fetch(`hyper://localhost/?key=${name}`, { method: 'POST' }); 59 | if (!response.ok) { 60 | throw new Error(`Failed to generate Hyperdrive key: ${response.statusText}`); 61 | } 62 | return await response.text(); // This returns the hyper:// URL 63 | } catch (error) { 64 | console.error('Error generating Hyperdrive key:', error); 65 | throw error; 66 | } 67 | } 68 | 69 | function addURL(url) { 70 | uploadListBox.innerHTML += `
  • ${url}
  • ` 71 | } 72 | 73 | function addError(name, text) { 74 | uploadListBox.innerHTML += `
  • Error in ${name}: ${text}
  • ` 75 | } -------------------------------------------------------------------------------- /docs/examples/drag-and-drop/styles.css: -------------------------------------------------------------------------------- 1 | @import url("agregore://theme/vars.css"); 2 | html { 3 | background: var(--ag-theme-background); 4 | color: var(--ag-theme-text); 5 | font-family: var(--ag-theme-font-family); 6 | } 7 | #uploadBox { 8 | padding: 1em; 9 | border: 0.25em solid var(--ag-theme-primary); 10 | border-radius: 0.5em; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | a { 17 | color: var(--ag-theme-secondary); 18 | padding:0.15em 0.5em; 19 | border-radius: 0.5em; 20 | } 21 | 22 | main { 23 | margin: 1em; 24 | } -------------------------------------------------------------------------------- /docs/examples/ipfs-gallery/gallery.js: -------------------------------------------------------------------------------- 1 | function layout(){ 2 | const gallery = document.querySelector('.gallery') 3 | for (const row of gallery.querySelectorAll('.row')){ 4 | let images = Array.from(row.querySelectorAll('img')) 5 | if (images.length > 0) 6 | { 7 | const ratioHeight = images[0].naturalHeight 8 | let ratioWidth = images[0].naturalWidth 9 | images.shift() 10 | while (images.length > 0){ 11 | let image = images.shift() 12 | ratioWidth += image.naturalWidth*ratioHeight/image.naturalHeight 13 | } 14 | console.log(`aspect-ratio: ${ratioWidth}/${ratioHeight};`) 15 | row.style = `aspect-ratio: ${ratioWidth}/${ratioHeight};` 16 | } 17 | } 18 | } 19 | 20 | async function addFileToGallery(file){ 21 | window.newImages = window.newImages || [] //+ 22 | window.newImages.push(file) //+ 23 | const columns = 3 24 | 25 | // Show save form 26 | document.getElementById("idSaveGalleryForm").classList.remove('hidden') 27 | 28 | // Create markup 29 | const img = document.createElement("img") 30 | img.classList.add('new') 31 | img.dataset.fileName = file.name 32 | img.src = URL.createObjectURL(file); 33 | let imgDiv = document.createElement('div') 34 | imgDiv.appendChild(img) 35 | 36 | // Get dimensions 37 | const image = await createImageBitmap(file) 38 | img.width = image.width 39 | img.height = image.height 40 | 41 | // Add event listener for lightbox 42 | img.addEventListener('click', e => { 43 | document.querySelector('.lightBox img').src = img.src 44 | document.querySelector('.lightBox').classList.remove('hidden') 45 | }) 46 | 47 | // Remove stock images 48 | const gallery = document.querySelector('.gallery') 49 | if (gallery.querySelectorAll('.row.stock').length > 0){ 50 | for (const stockRow of gallery.querySelectorAll('.row.stock')){ 51 | gallery.removeChild(stockRow) 52 | } 53 | const div = document.createElement('div') 54 | div.classList.add('row') 55 | gallery.appendChild(div) 56 | } 57 | 58 | // Find the right place to add the image 59 | if (gallery.querySelectorAll('.row:last-child img').length < columns){ 60 | gallery.querySelector('.row:last-child').appendChild(imgDiv) 61 | } else { 62 | const div = document.createElement('div') 63 | div.classList.add('row') 64 | div.appendChild(imgDiv) 65 | gallery.appendChild(div) 66 | } 67 | 68 | // Call layout to recalculate the aspect ratios 69 | setTimeout(layout, 500) 70 | } 71 | 72 | async function saveGallery(e){ 73 | e.preventDefault() 74 | // Hide save form and link before saving updated markup 75 | document.getElementById("idSaveGalleryForm").classList.add('hidden') 76 | document.getElementById("idGalleryUrl").classList.add('hidden') 77 | 78 | let formData = new FormData() 79 | 80 | // Add new images to formData 81 | for (const image of window.newImages){ 82 | formData.append('file', image) 83 | } 84 | 85 | // Get the original HTML 86 | const index = await fetch('index.html') 87 | const txt = await index.text() 88 | const parser = new DOMParser() 89 | let newDoc = parser.parseFromString(txt, 'text/html') 90 | 91 | // Replace the gallery with the updated markup 92 | newDoc.querySelector('.gallery').replaceWith(document.querySelector('.gallery').cloneNode(true)) 93 | for (const imgEl of newDoc.querySelectorAll('.gallery img.new')){ 94 | imgEl.src = imgEl.dataset.fileName 95 | delete imgEl.dataset.fileName 96 | imgEl.classList.remove('new') 97 | } 98 | if (!window.origin.startsWith('ipfs://')){ 99 | newDoc.querySelector('.gallery .helpOverlay').remove() 100 | } 101 | formData.append('file', new File([newDoc.documentElement.innerHTML], 'index.html')) 102 | 103 | let postUrl = window.origin 104 | if (!window.origin.startsWith('ipfs://')){ 105 | postUrl = 'ipfs://bafyaabakaieac/' 106 | } 107 | 108 | // Post the new data to save the images and the gallery 109 | const resp = await fetch(postUrl, {method: 'put', body: formData}) 110 | newCid = new URL(resp.headers.get('location')).origin 111 | console.log(`Uploaded ${newImages.length} files to ${newCid}`) 112 | document.querySelector("#idGalleryUrl").classList.remove('hidden') 113 | document.querySelector("#idGalleryUrl a").href = newCid 114 | 115 | } 116 | 117 | function dropListener(e){ 118 | e.preventDefault() 119 | const { dataTransfer } = e 120 | if(!dataTransfer) return 121 | const files = Array.from(dataTransfer.files) 122 | console.log(files) 123 | for (const file of files){ 124 | if (file.type.match(/image.*/)) { 125 | addFileToGallery(file).catch(console.log) 126 | } 127 | } 128 | } 129 | 130 | async function handleHelpClick(e){ 131 | e.stopPropagation() 132 | const opts = { 133 | types: [ 134 | { 135 | description: "Images", 136 | accept: { 137 | "image/*": [".png", ".gif", ".jpeg", ".jpg"], 138 | }, 139 | }, 140 | ], 141 | excludeAcceptAllOption: true, 142 | multiple: true, 143 | startIn: "pictures", 144 | } 145 | let result = await showOpenFilePicker(opts) 146 | document.querySelector('.helpOverlay').classList.add('small') 147 | for (const fileHandle of result){ 148 | let file = await fileHandle.getFile() 149 | await addFileToGallery(file) 150 | } 151 | } 152 | 153 | async function downloadStock(){ 154 | // Initialize the newImages array 155 | window.newImages = window.newImages || [] 156 | 157 | // Get all the stock images 158 | const imageNodes = document.querySelectorAll('.gallery .row.stock img') 159 | for (let i = 0; i < imageNodes.length; ++i){ 160 | let img = imageNodes.item(i) 161 | // Download the image and update the src 162 | let response = await fetch(img.src) 163 | window.newImages.push(new File([await response.blob()], `/stock/image-${i}.jpg`)) 164 | img.src = `/stock/image-${i}.jpg` 165 | } 166 | 167 | // Utilize saveGallery to upload the files and updated markup for us 168 | await saveGallery({preventDefault: () => true}) 169 | console.log('Navigate to ', document.querySelector("#idGalleryUrl a").href) 170 | } 171 | 172 | 173 | function galleryInit(){ 174 | layout() 175 | 176 | for (const img of document.querySelectorAll('.gallery .row img')){ 177 | img.addEventListener('click', e => { 178 | document.querySelector('.lightBox img').src = img.src 179 | document.querySelector('.lightBox').classList.remove('hidden') 180 | }) 181 | } 182 | 183 | document.querySelector('.lightBox img').addEventListener('click', e => 184 | document.querySelector('.lightBox').classList.add('hidden') 185 | ) 186 | document.querySelector(".gallery").addEventListener("dragover", e => e.preventDefault()) 187 | document.querySelector(".gallery").addEventListener("drop", dropListener) 188 | document.querySelector(".helpOverlay").addEventListener('click', e => { 189 | document.querySelector('.helpOverlay').classList.add('small') 190 | }) 191 | document.querySelector(".helpOverlay button").addEventListener("click", handleHelpClick) 192 | document.getElementById("idSaveGalleryForm").addEventListener("submit", saveGallery) 193 | } 194 | 195 | window.addEventListener('load', e => { 196 | galleryInit() 197 | }) 198 | -------------------------------------------------------------------------------- /docs/examples/ipfs-gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Image gallery 4 | 5 | 76 | 77 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/examples/ipfs-pub-sub-chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | PUBSUB chat 4 | 5 | 6 | 7 | 8 |
    9 |

    IPFS PUBSUB chat

    10 |
    11 | 12 |

    13 |
    14 |
    15 | 16 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/examples/ipfs-pub-sub-chat/lib.js: -------------------------------------------------------------------------------- 1 | async function updateSite(filename, content){ 2 | const resp = await fetch(`${window.origin}/${filename}`, {method: 'put', body: content}) 3 | const newLocation = resp.headers.get('location') 4 | window.location = new URL(newLocation).origin 5 | } 6 | 7 | async function publishSite(){ 8 | let resp = await fetch('ipns://localhost/?key=mysite', {method: 'POST'}) 9 | const key = resp.headers.get('location') 10 | resp = await fetch(key, {method: 'POST', body: window.origin}) 11 | window.location = new URL(resp.headers.get('location')).origin 12 | } 13 | 14 | async function loadFile(filename){ 15 | const resp = await fetch(filename) 16 | const content = await resp.text() 17 | document.getElementById('idFilenameInput').value = filename 18 | document.getElementById('idContentInput').value = content 19 | } 20 | 21 | async function listDir(path){ 22 | const resp = await fetch(window.origin + '?noResolve') 23 | const files = await resp.json() 24 | return files 25 | } 26 | 27 | async function showEditor(){ 28 | let editorDiv = document.getElementById("editor") 29 | if (!editorDiv){ 30 | editorDiv = document.createElement('div') 31 | editorDiv.id = 'editor' 32 | } 33 | editorDiv.style = `display: flex; 34 | flex-direction: column; 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | width: 100vw; 39 | height: 100vh; 40 | background-color: rgb(233 233 233 / 95%); 41 | ` 42 | editorDiv.innerHTML = `
    43 |

    Files

    44 |
    45 |
    46 | 47 | 48 | 49 | 50 | 51 |
    52 |
    ` 53 | document.body.appendChild(editorDiv) 54 | const form = document.getElementById('idForm') 55 | form.onsubmit = e => { 56 | e.preventDefault() 57 | const filename = document.getElementById('idFilenameInput').value 58 | const content = document.getElementById('idContentInput').value 59 | updateSite(filename, content) 60 | } 61 | const sidebar = document.getElementById('idSidebar') 62 | const files = await listDir(window.origin) 63 | const list = document.createElement('ul') 64 | list.style = "list-style: none; padding-inline-start: 0;" 65 | files.map( file => { 66 | let li = document.createElement('li') 67 | li.innerHTML = `${file}` 68 | li.querySelector('a').onclick = e => loadFile(file) 69 | list.appendChild(li) 70 | }) 71 | sidebar.appendChild(list) 72 | 73 | if (window.origin.startsWith('ipfs://')){ 74 | const button = document.createElement('button') 75 | button.innerHTML = 'Publish site' 76 | button.onclick = e => { 77 | e.preventDefault() 78 | publishSite() 79 | } 80 | sidebar.appendChild(button) 81 | } 82 | } 83 | 84 | window.addEventListener('load', e => { 85 | document.addEventListener('keydown', e => { 86 | if( e.ctrlKey && e.key == 'i' ){ 87 | showEditor().catch(console.error) 88 | } 89 | }) 90 | }) 91 | 92 | -------------------------------------------------------------------------------- /docs/examples/ipfs-pub-sub-chat/pubsub.js: -------------------------------------------------------------------------------- 1 | class PubSub { 2 | constructor(channelName){ 3 | this.channelName = channelName 4 | this.onopen = this.onopen.bind(this) 5 | this.onmessage = this.onmessage.bind(this) 6 | this.onerror = this.onerror.bind(this) 7 | } 8 | 9 | async listenForMsg() { 10 | let es = new EventSource(`pubsub://${this.channelName}/?format=json`) 11 | es.onmessage = this.onmessage 12 | es.onopen = this.onopen 13 | es.onerror = this.onerror 14 | } 15 | 16 | onopen(e) { 17 | console.log('onOpen', e) 18 | this.myRand = Math.random() 19 | let message = {msg: "hello", rnd: this.myRand} 20 | fetch(`pubsub://${this.channelName}/`, { 21 | method: 'POST', 22 | body: JSON.stringify(message), 23 | }).catch(console.error) 24 | 25 | document.getElementById('setup').classList.add('hidden') 26 | document.getElementById('chat').classList.remove('hidden') 27 | document.getElementById('roomName').innerHTML = this.channelName 28 | 29 | document.querySelector('#chatForm').addEventListener('submit', e => { 30 | e.preventDefault() 31 | let textInput = document.querySelector('#chat input') 32 | fetch(`pubsub://${this.channelName}/`, { 33 | method: 'POST', 34 | body: JSON.stringify({message: textInput.value}), 35 | }).catch(console.error) 36 | textInput.value = '' 37 | }) 38 | } 39 | 40 | onmessage(e) { 41 | console.log('onmessage', e) 42 | try { 43 | let msg = JSON.parse(e.data) 44 | if (!this.whoami && msg.data.rnd && msg.data.rnd == this.myRand){ 45 | console.log('Hello from myself. Yay!') 46 | this.whoami = msg.from 47 | } else if (msg.data.rnd && msg.data.rnd != this.myRand ){ 48 | console.log('Hello from a friend!') 49 | } else { 50 | let textarea = document.querySelector('#chat textarea') 51 | textarea.value = textarea.value + `\n> ${msg.from}: ${msg.data.message}` 52 | } 53 | } catch (error) { 54 | console.log(error) 55 | } 56 | } 57 | 58 | 59 | onerror(e) { 60 | console.log('onmessage', e) 61 | } 62 | 63 | 64 | } 65 | 66 | window.addEventListener('load', (event) => { 67 | const form = document.getElementById('roomNameForm') 68 | form.addEventListener('submit', event => { 69 | event.preventDefault() 70 | const channelName = document.getElementById('channelNameInput').value 71 | console.log('start pubsub', channelName) 72 | window.pubsub = new PubSub(channelName) 73 | window.pubsub.listenForMsg().catch(console.error) 74 | }) 75 | }) 76 | 77 | -------------------------------------------------------------------------------- /docs/examples/ipfs-pub-sub-chat/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin: 0; 4 | } 5 | .container { 6 | display: flex; 7 | flex-direction: column; 8 | height: 100%; 9 | align-items: center; 10 | justify-content: center; 11 | } 12 | 13 | .hidden { 14 | display: none; 15 | } 16 | 17 | #chat textarea { 18 | flex-grow: 1; 19 | margin-bottom: 1em; 20 | width: 80%; 21 | } 22 | 23 | #chatForm { 24 | display: flex; 25 | width: 80%; 26 | margin-bottom: 1em; 27 | } 28 | 29 | #messageInput { 30 | flex-grow: 1; 31 | line-height: 1.7; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /docs/examples/llm-appgen/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mauve Signweaver 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 | -------------------------------------------------------------------------------- /docs/examples/llm-appgen/README.md: -------------------------------------------------------------------------------- 1 | # llm-appgen 2 | Example Agregore app for generating web pages with local LLMs 3 | 4 | ## Aproach 5 | 6 | - Single HTML page, vanilla JS 7 | - Prompt app, preview in iframe 8 | - multi-shot approach 9 | - Plan 10 | - Generate Title / file name 11 | - HTML 12 | - JavaScript 13 | - CSS 14 | - Upload to hyperdrive -------------------------------------------------------------------------------- /docs/examples/llm-chat.html: -------------------------------------------------------------------------------- 1 | 2 | LLM Chat 3 | 39 | 40 | 47 | 48 |
    49 |
    50 | 51 | 52 | 53 |
    54 |
    55 | What is this? 56 |

    57 | This is an example application for using Agregore's built in Local AI capabilities. 58 | This simulates chatting with an occult entity of some sort. 59 | You will be guided through an initial setup the first time you use AI features. 60 | You can read more about the AI support here. 61 |

    62 |

    63 | You can make a custom version of this example app by opening it in the 64 | DWeb Scratchpad

    65 |
    66 | 131 | -------------------------------------------------------------------------------- /docs/examples/llm-echo-chamber.html: -------------------------------------------------------------------------------- 1 | 2 | LLM Echo Chamber 3 | 56 | 57 | 62 | 63 |
    64 |
    65 | 66 | 67 | 68 |
    69 |
    70 | 76 |
    77 |
    78 | How does this work? 79 |

    80 | The echo chamber works by having a language model talk to itself. 81 | You supply the initial topic and it will keep discussing until you stop it. 82 | A reflection is applied between each discussion which can help guide the discussions. 83 |

    84 |
    85 | 86 | -------------------------------------------------------------------------------- /docs/examples/llm-lenses-chat/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /docs/examples/llm-lenses-chat/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mauve Signweaver 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 | -------------------------------------------------------------------------------- /docs/examples/llm-lenses-chat/README.md: -------------------------------------------------------------------------------- 1 | # llm-lenses-chat 2 | Simulate what it's like to be misunderstood by translating what you meant to say in real time 3 | 4 | ## How it works 5 | 6 | - Forked from Agregore's [LLM Chat example](//agregore.mauve.moe/docs/examples/llm-chat.html) 7 | - Before text is sent to the LLM it is translated by a prompt to an LLM 8 | - Users can configure the "tonal lense" they wish to apply to the message 9 | - This simulates what it's like to be misinterpreted by others -------------------------------------------------------------------------------- /docs/examples/llm-lenses-chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | Tonal Lens Simulator 3 | 57 | 58 | 64 | 65 |
    66 |
    67 | 68 | 69 |
    70 |
    71 | 74 | 75 | 76 |
    77 | -------------------------------------------------------------------------------- /docs/examples/llm-vision.html: -------------------------------------------------------------------------------- 1 | 2 | LLM Chat 3 | 74 |
    75 |
    76 | 77 | 78 | 79 |
    80 | 81 | 82 |

    What is this?

    83 |

    84 | This is an example application for using Agregore's built in Local AI capabilities. 85 | This simulates chatting with an occult entity of some sort. 86 | You will be guided through an initial setup the first time you use AI features. 87 | You can read more about the AI support here. 88 |

    89 |

    90 | You can make a custom version of this example app by opening it in the 91 | DWeb Scratchpad. 92 |

    93 | 94 | 95 |
    96 | 97 | 180 | -------------------------------------------------------------------------------- /docs/examples/multitodo/README.md: -------------------------------------------------------------------------------- 1 | # ipfs-multido 2 | 3 | Todo app with multiple "stages" of done-ness. I'm starting with video games. 4 | 5 | https://github.com/judytuna/ipfs-multido 6 | 7 | View it live using the [Agregore browser](https://github.com/AgregoreWeb/agregore-browser) at: `ipns://k51qzi5uqu5dkwoi3kwuekamipebnng03h8qhqblf9xcazdcu2ytn45bgu4ev4` 8 | 9 | Screen Shot 2023-02-11 at 11 57 08 PM 10 | 11 | To find the port my ipfs is on: 12 | 13 | ``` 14 | lsof -i -n -P | grep ipfs 15 | ``` 16 | 17 | and look for LISTEN 18 | -------------------------------------------------------------------------------- /docs/examples/multitodo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | My Multiple-Stage Todo List 4 | 5 | 17 | 18 | 19 | 20 |

    Hello World, this is my multi-stage todo list!

    21 | 22 |

    All games

    23 | 30 | 31 | 32 | 33 |
    34 |

    Legend

    35 | 40 |
    41 | 42 |
    43 | on github 44 |
    45 | 46 | 51 | 52 | 53 | 54 | 144 | 145 | -------------------------------------------------------------------------------- /docs/examples/p2pad/codeEditor.js: -------------------------------------------------------------------------------- 1 | import { $, loadingSpinner, backdrop, iframe } from './common.js'; // Import common functions 2 | 3 | // Attach event listeners directly using the $ selector function 4 | [$('#htmlCode'), $('#javascriptCode'), $('#cssCode')].forEach(element => { 5 | element.addEventListener('input', () => update()); 6 | }); 7 | 8 | // Import CSS from Agregore theme to use in the iframe preview 9 | export let basicCSS = ` 10 | @import url("agregore://theme/style.css"); 11 | body { 12 | font-size: 1.2rem; 13 | margin: 0; 14 | padding: 0; 15 | font-family: var(--ag-theme-font-family); 16 | background: var(--ag-theme-background); 17 | color: var(--ag-theme-text); 18 | } 19 | `; 20 | 21 | //Function for live Rendering 22 | export function update() { 23 | let htmlCode = $('#htmlCode').value; 24 | console.log('HTML Code:', htmlCode); 25 | let cssCode = $('#cssCode').value; 26 | console.log('CSS Code:', cssCode); 27 | let javascriptCode = $('#javascriptCode').value; 28 | console.log('JavaScript Code:', javascriptCode); 29 | // Assemble all elements and Include the basic CSS from Agregore theme 30 | let iframeContent = ` 31 | 32 | 33 | 34 | ${htmlCode} 35 | `; 36 | 37 | let iframeDoc = iframe.contentWindow.document; 38 | iframeDoc.open(); 39 | iframeDoc.write(iframeContent); 40 | iframeDoc.close(); 41 | } 42 | 43 | 44 | // Show or hide the loading spinner 45 | export function showSpinner(show) { 46 | backdrop.style.display = show ? 'block' : 'none'; 47 | loadingSpinner.style.display = show ? 'block' : 'none'; 48 | } -------------------------------------------------------------------------------- /docs/examples/p2pad/common.js: -------------------------------------------------------------------------------- 1 | // Common module for exports 2 | export function $(query) { 3 | return document.querySelector(query); 4 | } 5 | 6 | export const uploadButton = $('#uploadButton'); 7 | export const protocolSelect = $('#protocolSelect'); 8 | export const loadingSpinner = $('#loadingSpinner'); 9 | export const backdrop = $('#backdrop'); 10 | export const iframe = $('#viewer'); 11 | export const fetchButton = $('#fetchButton'); 12 | export const fetchCidInput = $('#fetchCidInput'); 13 | -------------------------------------------------------------------------------- /docs/examples/p2pad/dweb.js: -------------------------------------------------------------------------------- 1 | import { update, showSpinner, basicCSS } from './codeEditor.js'; 2 | import { $, uploadButton, protocolSelect, fetchButton, fetchCidInput } from './common.js'; 3 | 4 | // assemble code before uploading 5 | export async function assembleCode() { 6 | // Display loading spinner 7 | showSpinner(true); 8 | 9 | // Combine your code into a single HTML file 10 | let combinedCode = ` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ${document.getElementById("htmlCode").value} 19 | 20 | 21 | `; 22 | 23 | // Convert the combined code into a Blob 24 | const blob = new Blob([combinedCode], { type: 'text/html' }); 25 | const file = new File([blob], "index.html", { type: 'text/html' }); 26 | 27 | // Upload the file 28 | await uploadFile(file); 29 | showSpinner(false); 30 | } 31 | 32 | uploadButton.addEventListener('click', assembleCode); 33 | 34 | // Upload code to Dweb 35 | async function uploadFile(file) { 36 | const protocol = protocolSelect.value; 37 | 38 | const formData = new FormData(); 39 | 40 | // Append file to the FormData 41 | formData.append('file', file, file.name); 42 | 43 | 44 | // Construct the URL based on the protocol 45 | let url; 46 | if (protocol === 'hyper') { 47 | const hyperdriveUrl = await generateHyperdriveKey('p2pad'); 48 | url = `${hyperdriveUrl}`; 49 | } else { 50 | url = `ipfs://bafyaabakaieac/`; 51 | } 52 | 53 | // Perform the upload for each file 54 | try { 55 | const response = await fetch(url, { 56 | method: 'PUT', 57 | body: formData, 58 | }); 59 | 60 | if (!response.ok) { 61 | addError(file, await response.text()); 62 | } 63 | const urlResponse = protocol === 'hyper' ? response.url : response.headers.get('Location'); 64 | addURL(urlResponse); 65 | } catch (error) { 66 | console.error(`Error uploading ${file}:`, error); 67 | } finally { 68 | showSpinner(false); 69 | } 70 | } 71 | 72 | 73 | 74 | async function generateHyperdriveKey(name) { 75 | try { 76 | const response = await fetch(`hyper://localhost/?key=${name}`, { method: 'POST' }); 77 | if (!response.ok) { 78 | throw new Error(`Failed to generate Hyperdrive key: ${response.statusText}`); 79 | } 80 | return await response.text(); // This returns the hyper:// URL 81 | } catch (error) { 82 | console.error('Error generating Hyperdrive key:', error); 83 | throw error; 84 | } 85 | } 86 | 87 | 88 | function addURL(url) { 89 | const listItem = document.createElement('li'); 90 | const link = document.createElement('a'); 91 | link.href = url; 92 | link.textContent = url; 93 | 94 | const copyContainer = document.createElement('span'); 95 | const copyIcon = '⊕' 96 | copyContainer.innerHTML = copyIcon; 97 | copyContainer.onclick = function() { 98 | navigator.clipboard.writeText(url).then(() => { 99 | copyContainer.textContent = '☑'; 100 | setTimeout(() => { 101 | copyContainer.innerHTML = copyIcon; 102 | }, 3000); 103 | }).catch(err => { 104 | console.error('Error in copying text: ', err); 105 | }); 106 | }; 107 | 108 | listItem.appendChild(link); 109 | listItem.appendChild(copyContainer); 110 | uploadListBox.appendChild(listItem); 111 | } 112 | 113 | 114 | function addError(name, text) { 115 | uploadListBox.innerHTML += `
  • Error in ${name}: ${text}
  • ` 116 | } 117 | 118 | // The fetchFromDWeb function detects which protocol is used and fetches the content 119 | async function fetchFromDWeb(url) { 120 | if (!url) { 121 | alert("Please enter a CID or Name."); 122 | return; 123 | } 124 | 125 | if (!url.startsWith('ipfs://') && !url.startsWith('hyper://')) { 126 | alert("Invalid protocol. URL must start with ipfs:// or hyper://"); 127 | return; 128 | } 129 | 130 | try { 131 | const response = await fetch(url); 132 | const data = await response.text(); 133 | parseAndDisplayData(data); 134 | } catch (error) { 135 | console.error("Error fetching from DWeb:", error); 136 | alert("Failed to fetch from DWeb."); 137 | } 138 | } 139 | 140 | // Modified event listener for fetchButton 141 | fetchButton.addEventListener('click', () => { 142 | const cidOrName = fetchCidInput.value; 143 | fetchFromDWeb(cidOrName); 144 | }); 145 | 146 | // Parse the data and display it in the code editor 147 | function parseAndDisplayData(data) { 148 | const parser = new DOMParser(); 149 | const doc = parser.parseFromString(data, 'text/html'); 150 | 151 | // Extracting CSS 152 | const styleElements = Array.from(doc.querySelectorAll('style')); 153 | 154 | // Remove the first element (agregore theme CSS) 155 | styleElements.shift(); 156 | 157 | // Now combine the CSS from the remaining 33 | 34 | 35 |
    36 | 37 |
    38 | What is this? 39 |

    40 | This is an example application for using Agregore's built in Local AI capabilities. 41 | Describe the sort of code snippet you'd like help making and the AI will generate it for you. 42 | Make sure to specify the programming language you want and be clear about what you want. 43 | You will be guided through an initial setup the first time you use AI features. 44 | You can read more about the AI support here. 45 |

    46 |

    47 | You can make a custom version of this example app by opening it in the 48 | DWeb Scratchpad

    49 |
    50 | 84 | -------------------------------------------------------------------------------- /docs/examples/themebuilder.html: -------------------------------------------------------------------------------- 1 | 2 | Theme Builder 3 | 6 | 7 |

    Theme Builder

    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

    The Idea ✨

    16 |

    17 | Play around with Agregore's built in styles and download a copy of an .agregorerc file to save to your home folder to apply your styles globally. 18 |

    19 |

    20 | For more information, check out the theming documentation. 21 |

    22 | 65 | -------------------------------------------------------------------------------- /docs/extensions.md: -------------------------------------------------------------------------------- 1 | ## Extensions - Agregore Browser 2 | 3 | Here's some info about how to use web extensions in Agregore Browser. 4 | 5 | ### Installing extensions 6 | 7 | Agregore doesn't yet support loading extensions from app stores or zip files, but you can place the extracted extensions inside a folder for it to load. 8 | 9 | Click on `Help > Open Extension Folder` in the application menu on any window to open up your extensions folder. 10 | 11 | You can drop folders in here to have them load when Agregore starts up. 12 | 13 | For a list of APIs that are supported, please look at the [Electron Extensions](https://github.com/sentialx/electron-extensions/issues/14) module. 14 | 15 | ### Built in extensions 16 | 17 | Agregore comes with a few built-in extensions. 18 | 19 | - [UBlock Origin](https://ublockorigin.com/) is a secure ad blocker which makes your default experience on the web a bit safer. 20 | - [ArchiveWeb.page](https://archiveweb.page/) lets you create local copies of websites and either save them to IPFS or as files. 21 | - [Agregore Renderer](https://github.com/AgregoreWeb/extension-agregore-renderer) renders Markdown/Gemini/JSON as well as a "Reader Mode" which extracts the main contents from a web page and renders it with your browser-wide color scheme. 22 | - [QR Share](https://github.com/AgregoreWeb/extension-agregore-qr-share) is a minimal QR code generator and scanner so you can share P2P website links without dictating long strings of letters and numbers. 23 | - [Agregore History](https://github.com/AgregoreWeb/extension-agregore-history) is used by Agregore to track and search through your web browsing history. You can fork and update it to do whatever yopu want without needing to upgrade Agregore as a whole. 24 | 25 | --- 26 | 27 | [Back](/) 28 | -------------------------------------------------------------------------------- /docs/hypercore-protocol-handlers.md: -------------------------------------------------------------------------------- 1 | # 🌐 Hypercore Fetch API 🌐 2 | 3 | A powerful implementation of the Fetch API utilizing the Hyper SDK for peer-to-peer (p2p) content loading in Agregore browser. 4 | 5 | ## Introduction 👋 6 | 7 | ### What is the Hypercore Fetch API? 8 | 9 | The Hypercore Fetch API is a JavaScript interface that extends the familiar Fetch API for use with the Hypercore Protocol. It allows seamless interaction with p2p content, leveraging the capabilities of the Hyper SDK. 10 | 11 | ### Why Use Hypercore Fetch API? 🤔 12 | 13 | This API simplifies the process of loading and managing p2p content, making it an essential tool for developers working with decentralized data structures. 14 | 15 | 16 | ## Using Fetch with Hypercore 🌐 17 | 18 | The Hypercore Fetch API allows you to interact with hyperdrives and hypercores using familiar HTTP-like methods. Here are some of the key operations you can perform: 19 | 20 | ### Loading Data 📨 21 | 22 | Here's how you can request a file from a Hyperdrive using its unique key: 23 | 24 | ```javascript 25 | await fetch('hyper://KEY/example.txt') 26 | ``` 27 | 28 | This will return the content of `example.txt` from the specified hyperdrive. 29 | 30 | ## Generating Keys and Uploading Files 31 | 32 | ### Generating a Hyperdrive Key 33 | 34 | Before uploading files to a Hyperdrive, a unique key is required. Here's how to generate one: 35 | 36 | ```javascript 37 | async function generateHyperdriveKey(name) { 38 | try { 39 | const response = await fetch(`hyper://localhost/?key=${name}`, { method: 'POST' }); 40 | if (!response.ok) { 41 | throw new Error(`Failed to generate Hyperdrive key: ${response.statusText}`); 42 | } 43 | return await response.text(); // This returns the hyper:// URL 44 | } catch (error) { 45 | console.error('Error generating Hyperdrive key:', error); 46 | throw error; 47 | } 48 | } 49 | ``` 50 | 51 | ### Uploading a File to Hyperdrive 📤 52 | 53 | Once you have a Hyperdrive key, you can upload files using fetch. 54 | 55 | The latest hypercore-fetch API supports various data types for the request body, including `String`, `Blob`, `FormData`, or `ReadableStream`. In this guide, we focus on using FormData for uploading files, which is particularly useful for handling multiple files. 56 | 57 | ### Uploading Using FormData 58 | 59 | When uploading files, FormData is an efficient way to bundle and send multiple files in a single request. Here's an updated example illustrating how to upload files using FormData: 60 | 61 | ```javascript 62 | async function uploadFile(files) { 63 | 64 | const formData = new FormData(); 65 | 66 | // Append each file to the FormData 67 | for (const file of files) { 68 | formData.append('file', file, file.name); 69 | } 70 | 71 | // Construct URL with hypercore key (see section above for key generating function) 72 | let url; 73 | const hyperdriveUrl = await generateHyperdriveKey(name); 74 | url = `${hyperdriveUrl}${name}`; 75 | 76 | 77 | // Perform the upload for each file 78 | try { 79 | const response = await fetch(url, { 80 | method: 'PUT', 81 | body: formData, 82 | }); 83 | 84 | if (!response.ok) { 85 | addError(files, await response.text()); 86 | } 87 | 88 | addURL(response.url); 89 | } catch (error) { 90 | console.error(`Error uploading ${files}:`, error); 91 | } 92 | } 93 | ``` 94 | 95 | This function demonstrates how to upload files using FormData, which simplifies the process of sending multiple files in a single HTTP request. 96 | 97 | ## Deleting Data 🗑️ 98 | 99 | The `hypercore-fetch` API allows for deleting files or entire directories within a hyperdrive. Note that these operations require the hyperdrive to be writable (`writable: true`). 100 | 101 | ### Deleting a Single File 102 | 103 | To delete a specific file: 104 | 105 | ```javascript 106 | fetch('hyper://KEY/example.txt', {method: 'DELETE'}) 107 | ``` 108 | 109 | This sends a DELETE request to remove example.txt from the hyperdrive specified by KEY. 110 | 111 | KEY can be a 52 character z32 encoded key or a domain parsed with DNSLink. 112 | 113 | ### Purging All Data in a Hyperdrive 114 | 115 | To delete all contents of a hyperdrive: 116 | 117 | ```javascript 118 | fetch('hyper://KEY/', {method: 'DELETE'}) 119 | ``` 120 | 121 | A DELETE request to the root of the hyperdrive clears all its stored data. 122 | 123 | If it's a writable drive, data will be fully cleared. Attempting to write again may lead to data corruption. 124 | 125 | **Important**: Be cautious when using these delete operations as they cannot be undone and might lead to permanent data loss. 126 | 127 | 128 | ## Resources and Further Reading 📖 129 | 130 | - [Hypercore Fetch GitHub Repository](https://github.com/RangerMauve/hypercore-fetch/blob/master/README.md) 131 | - [Hypercore Protocol Documentation](https://docs.holepunch.to/) 132 | - [Hyper SDK GitHub Repository](https://github.com/RangerMauve/hyper-sdk) 133 | - [Making stuff with Dat-SDK](https://www.youtube.com/watch?v=HyHk4aImd_I&list=PL7sG5SCUNyeYx8wnfMOUpsh7rM_g0w_cu&index=21) 134 | 135 | --- 136 | 137 | [Back](/) 138 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## 📔 Docs 📔 2 | 3 | Here are some docs to get you started with using Agregore. 4 | 5 | **🛠🚧 The following pages are WIPs and are currently being worked on! Check back every now and again for updates 🚧🛠** 6 | 7 | - [Extensions](./extensions) 8 | - [Theming](./theming) 9 | 10 | ### Protocols 11 | 12 | Documentation for P2P protocol handlers that are supported by Agregore. A great place to start if you want to create apps using Agregore ✨ 13 | 14 | - [🪐 IPFS Protocol Handlers](./ipfs-protocol-handlers) 15 | - [🌀 Hypercore Protocol Handlers](./hypercore-protocol-handlers) 16 | - [🌊 Bittorrent Protocol Handlers](./bittorent-protocol-handlers) 17 | 18 | ### Examples 19 | 20 | - [Browser Development Environment](./examples/browser-devenv/) 21 | - [Drag and Drop File Uploader](./examples/drag-and-drop/) 22 | - [PubSub Chat](./examples/ipfs-pub-sub-chat/) 23 | - [Theme Builder](./examples/themebuilder) 24 | - [Browser Development Environment V3](./examples/browser-devenv-v3/) 25 | - [IPFS Gallery](./examples/ipfs-gallery/) 26 | - [LLM App Generator](./examples/llm-appgen/) 27 | - [LLM Chat](./examples/llm-chat.html) 28 | - [LLM Tonal Lenses](./examples/llm-lenses-chat/) 29 | - [LLM Vision](./examples/llm-vision) 30 | 31 | ### Tutorials: 32 | 33 | - [🧱 App and tutorial making process](./tutorials/process) 34 | - [Theme Builder](./tutorials/themebuilder-tutorial) 35 | - IPFS Development Environment [Part 1](./tutorials/ipfs-browser-devenv/part-1), [Part 2](./tutorials/ipfs-browser-devenv/part-2) and [Part 3](./tutorials/ipfs-browser-devenv/part-3) 36 | - [Drag and Drop File Uploader](./tutorials/drag-and-drop) 37 | - [PubSub Chat](./tutorials/ipfs-pub-sub-chat) 38 | - [Directory Uploader](./tutorials/ipfs-dir-upload/) 39 | - [P2Pad Code editor](./tutorials/p2pad-code-editor) 40 | - [Adding a 3rd-party dependency](./tutorials/ipfs-3rdparty-dep/) 41 | - [Building an IPFS gallery](./tutorials/ipfs-gallery/) 42 | - [DLinktree Builder](./tutorials/dlinktree-builder) 43 | 44 | --- 45 | 46 | [Home](/) 47 | -------------------------------------------------------------------------------- /docs/protocols.md: -------------------------------------------------------------------------------- 1 | ## Supported Protocols - Agregore Browser 2 | 3 | Information about the supported protocols within Agregore and their functionality 4 | 5 | - [BitTorrent](http://www.bittorrent.org/index.html) (`bittorrent://` and `magnet:`) [source](https://github.com/RangerMauve/bt-fetch/) 6 | - Able to view contents of torrents 7 | - Stream video and image data 8 | - Translate `magnet:` URIs to `bittorrent:` URLs 9 | - Resolve mutable torrents 10 | - [Hypercore](https://hypercore-protocol.org/) (`hyper://`) [source](https://github.com/RangerMauve/hypercore-fetch) 11 | - Able to read from archives 12 | - Able to resolve `hyper-dns` domains 13 | - Able to write, create and modify archives 14 | - [Gemini](https://gemini.circumlunar.space/) (`gemini://`) [source](https://github.com/RangerMauve/gemini-fetch) 15 | - Able to read from gemini servers 16 | - Render Gemini pages as HTMLs 17 | - No certificate management code yet 18 | - [IPFS](https://ipfs.io/) (`ipfs://` and `ipns://`) [source](https://github.com/RangerMauve/js-ipfs-fetch) 19 | - Able to read from IPFS CIDs, IPLD public keys, and IPLD DNSLink domains. 20 | - Able to `POST` data into IPFS 21 | - Able to `POST` data into IPNS 22 | - [SecureScuttlebutt](https://scuttlebutt.nz) (`ssb://`) [source](https://github.com/av8ta/ssb-fetch/) 23 | - Connect to existing running SSB node 24 | - Able to load `ssb:` URLs (messages, blobs) 25 | - Translate cipherlinks to `ssb:` URLs 26 | - (soon) POST to SSB? 27 | 28 | ### Coming soon 29 | 30 | Here are some protocols we'd like to add, but haven't had the time to yet. Feel free to open a GitHub issue if you'd like to tackle them! 31 | 32 | - [EarthStar](https://github.com/earthstar-project/earthstar) 33 | - [Gopher Protocol](https://en.wikipedia.org/wiki/Gopher_(protocol)) 34 | - [Pigeon Protocol](https://github.com/PigeonProtocolConsortium/pigeon-spec) 35 | 36 | PRs for more protocols are welcome. 37 | 38 | --- 39 | 40 | [Back](/) 41 | -------------------------------------------------------------------------------- /docs/theming.md: -------------------------------------------------------------------------------- 1 | ## Theming - Agregore Browser 2 | 3 | Agregore provides CSS variables for themeing the browser at the URL `agregore://theme/vars.css`. 4 | 5 | The contents of this look something like: 6 | 7 | ```css 8 | :root { 9 | --ag-color-purple: #6e2de5; 10 | --ag-color-black: #111; 11 | --ag-color-white: #f2f2f2; 12 | --ag-color-green: #2de56e; 13 | } 14 | 15 | :root { 16 | --ag-theme-font-family: system-ui; 17 | --ag-theme-background: var(--ag-color-black); 18 | --ag-theme-text: var(--ag-color-white); 19 | --ag-theme-primary: var(--ag-color-purple); 20 | --ag-theme-secondary: var(--ag-color-green); 21 | } 22 | ``` 23 | 24 | These can be imported anywhere you'd like to use browser styling. 25 | 26 | Specifically, you should try to use the `--ag-theme-*` variables for the page when possible. 27 | 28 | You can also make use of the `agregore://theme/style.css` which adds some default styling to stuff like headers, the background/text colors, and links. 29 | 30 | This is useful for styling markdown pages or other pages with basic HTML. You probably shouldn't include this if you're doing something fancy with styling as the styles may change over time. 31 | 32 | The style includes a class called `agregore-header-anchor` which can be used on anchors within headers for linking to headings. Checkout the markdown extension 33 | 34 | ### Customization 35 | 36 | The `--ag-theme-*` variables can me modified in the `.agregorerc` file by clicking `Help > Edit Configuration File` and adding the following content: 37 | 38 | ```javascript 39 | { 40 | "theme": { 41 | "font-family": "system-ui", 42 | "background": "var(--ag-color-black)", 43 | "text": "var(--ag-color-white)", 44 | "primary": "var(--ag-color-purple)", 45 | "secondary": "var(--ag-color-green)" 46 | } 47 | } 48 | ``` 49 | 50 | You can replace the various values with any valid CSS values like Hex codes: `#FFAABB`, or `rgb()`. 51 | 52 | More styles will be added here as needed. If you feel we should standardize on some sort of style, feel free to open an issue talking about what it is and why it should be added. 53 | 54 | ## Syntax Highlighting Font 55 | 56 | Agregore now uses a custom font for syntax highlighting in code blocks. The font file is located at `browser://theme/FontWithASyntaxHighlighter-Regular.woff2`. 57 | 58 | To use this font for `code` elements, you can include the following CSS in your stylesheet: 59 | 60 | ```css 61 | @font-face { 62 | font-family: "FontWithASyntaxHighlighter"; 63 | src: url("browser://theme/FontWithASyntaxHighlighter-Regular.woff2") format("woff2"); 64 | } 65 | 66 | code { 67 | font-family: "FontWithASyntaxHighlighter", monospace; 68 | } 69 | ``` 70 | 71 | This font provides built-in syntax highlighting for code blocks, making it easier to read and understand code snippets. 72 | 73 | ## Theme Protocol (`browser://theme/`) 74 | 75 | ### Overview 76 | 77 | The `browser://theme/` protocol provides a standardized way for web applications to access browser-level CSS styles and theme variables in Agregore and other compatible browsers, such as [Peersky](https://peersky.p2plabs.xyz/). This protocol ensures consistent theming across different browsers by serving CSS files with a common set of variables. It allows developers to build applications that adapt to the browser's theme without needing browser-specific code, making it suitable for any browser that implements the protocol. 78 | 79 | ## Purpose 80 | 81 | The goal of the `browser://theme/` protocol is to: 82 | 83 | - Enable cross-browser compatibility for theming in any browser, including p2p browsers like Peersky and Agregore. 84 | - Provide a unified set of theme variables using standardized `--browser-theme-` prefixes. 85 | - Allow web applications to import styles or variables without hardcoding browser-specific protocols (e.g., `peersky://` or `agregore://`). 86 | 87 | ## Implementation 88 | 89 | ### Protocol Handler 90 | 91 | The `browser://theme/` protocol is implemented in Peersky via a custom Electron protocol handler (`theme-handler.js`). It serves CSS files from the `src/pages/theme/` directory when requests are made to URLs like `browser://theme/vars.css` or `browser://theme/base.css`. 92 | 93 | - **Location**: Files are stored in `src/pages/theme/` (e.g., `vars.css`, `base.css`, `index.css`). 94 | - **URL Structure**: Requests to `browser://theme/` map to `src/pages/theme/`. 95 | - **Example**: `browser://theme/vars.css` serves `src/pages/theme/vars.css`. 96 | 97 | ### Theme Variable Standardization 98 | 99 | The `browser://theme/` protocol provides standardized theme variables prefixed with `--browser-theme-`, such as `--browser-theme-font-family`, `--browser-theme-background`, `--browser-theme-text-color`, `--browser-theme-primary-highlight`, and `--browser-theme-secondary-highlight`. These variables allow web applications to adapt to the host browser's theme without needing browser-specific code. 100 | 101 | Each browser implements these standardized variables by mapping them to their internal theme variables. For example: 102 | 103 | - In Peersky, `--browser-theme-background` is mapped to `--base01`, which is part of the Base16 color palette [Base16 Framework](https://github.com/chriskempson/base16). 104 | - In Agregore, `--browser-theme-background` is mapped to `--ag-theme-background`, which is defined in Agregore's theme configuration. 105 | 106 | This ensures that applications built for one browser can work seamlessly in another, as long as they use the standardized `--browser-theme-` variables. 107 | 108 | ### Cross-Browser Compatibility 109 | 110 | The `browser://theme/` protocol enables apps built for one browser to work seamlessly in another by providing standardized theme variables prefixed with `--browser-theme-`. These variables are mapped to each browser's internal theme variables, ensuring consistent theming across different browsers. 111 | 112 | For example: 113 | 114 | - In Peersky, `--browser-theme-background` is mapped to `--base01`, which is part of the Base16 color palette. 115 | - In Agregore, `--browser-theme-background` is mapped to `--ag-theme-background`, which is defined in Agregore's theme configuration. 116 | 117 | As a result, an app using `--browser-theme-background` will render with the appropriate background color for each browser, whether it's based on Base16 (as in Peersky) or another theme system (as in Agregore). 118 | 119 | Additionally, apps can use the full set of variables provided by each browser for more advanced theming, but for cross-browser compatibility, it's recommended to use the standardized `--browser-theme-` variables. 120 | 121 | ## Usage 122 | 123 | ### Importing Theme Styles 124 | 125 | Web applications can import theme styles or variables using ` 137 | ``` 138 | 139 | - **Import Default Styles**: 140 | 141 | ```html 142 | 143 | ``` 144 | 145 | - **Use Peersky Variables in Agregore**: 146 | ```html 147 | 154 | ``` 155 | 156 | --- 157 | 158 | [Back](/) 159 | -------------------------------------------------------------------------------- /docs/tutorials/ipfs-3rdparty-dep/ace-builds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-3rdparty-dep/ace-builds.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-3rdparty-dep/ace-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-3rdparty-dep/ace-edit.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-3rdparty-dep/devenv-annotated-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-3rdparty-dep/devenv-annotated-v2.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-browser-devenv/agregore-browser-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-browser-devenv/agregore-browser-devtools.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-dir-upload/devenv-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-dir-upload/devenv-annotated.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-dir-upload/template-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-dir-upload/template-site.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-dir-upload/upload-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-dir-upload/upload-form.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-gallery/devenv-3-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-gallery/devenv-3-annotated.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-gallery/devenv-bs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-gallery/devenv-bs.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-gallery/ipfs-gallery-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-gallery/ipfs-gallery-layout.png -------------------------------------------------------------------------------- /docs/tutorials/ipfs-gallery/ipfs-gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/docs/tutorials/ipfs-gallery/ipfs-gallery.png -------------------------------------------------------------------------------- /docs/tutorials/process.md: -------------------------------------------------------------------------------- 1 | # Agregore Web Apps and Tutorials Process 2 | 3 | ## Summary 4 | 5 | This document is part of a [devgrant](https://github.com/ipfs/devgrants/pull/245) with the [Filecoin Foundation](https://fil.org/) for making demos and tutorials for making and using p2p web apps using [IPFS](https://ipfs.tech/) via [Agregore's](https://agregore.mauve.moe/) p2p protocol handler APIs. 6 | 7 | The goal of this document is it outline the process we'll be taking for creating small web apps, making tutorials for the web apps, and writing up retrospectives on what we learn from the process. 8 | 9 | However, if you're interested in following the process to make your own app or tutorial, this document can give you a nice start. 10 | 11 | ## Making Apps 12 | 13 | ### Guidelines for apps 14 | 15 | The main guidline when making apps is to think small. 16 | Ideally want to have things that a person can create in an evening and have some easy wins along the way. 17 | In order to make that more straightforward we'll have a few constraints on the code. 18 | 19 | 1: Aim to make something that would be useful and or fun at the end. 20 | Something which a person might want to use beyond the tutorial or that has room for growth for people to tinker with further. 21 | If it's a note taking app, maybe give them ideas for how to aggregate notes or add custom formatting or text editing functionality. 22 | Useful doesn't necessarily mean that it has to be utilitarian. Apps that exist to be fun to make and play with are also encouraged since the web is useful tool for people to express themselves. 23 | 24 | 2: Avoid external dependencies unless absolutely necessary. 25 | This means reducing your dependency on external CSS and JS libraries like Bootstrap or JQuery. If a library is absolutely necessary (say you're doing stuff with QR code reading and generation), then prefer to show users how to download source files and add them directly to their app's files. This is important to ensure that all the apps we build are fully local first and can load without depending on the cloud. Note that when prompting users to download libraries, you should be specific about which version to use to avoid trouble in the future. 26 | 27 | 3: Use the web platform and it's built in APIs. 28 | This means using stuff like Custom Elements for making reusable components. 29 | Showing people how to use built in CSS features such as [Sticky Headers](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky). There's a lot of cool stuff built into browsers and we can get pretty far by showing people how to use this stuff. 30 | 31 | 4: Avoid using any build tools like webpack/react/npm/etc. 32 | Users should be able to install Agregore, and get going on adding code without needing to worry about additional dependencies or build tools. 33 | Ideally, it should be possible for a person to start off with either Agregore's built in Chromium DevTools, or to use some of the small apps developed as part of these docs. 34 | 35 | 5: Avoid depending on centralized servers whenever possible. 36 | Sometimes you might want to pull data from a server for something like an RSS reader, but you should make sure that your app will at least load, or hopefully be useful in some way if that server isn't available. 37 | 38 | 6: When possible, try using Agregore's built in [theming support](./theming). 39 | This might involve importing the user-configurable CSS variables from `agregore://theme/vars.css` to add splashes of color, or using the entire `agregore://theme/style.css` file to add default styling to headers and code blocks. 40 | This is useful to give apps a similar "agregore-y" look and feel and gives users the option to configure the styles of all the apps they use on the local-first web by modifying their Agregore config with custom colors. 41 | 42 | These aren't hard restrictions and you're free to break outside of them whenever you see fit, but their main purpose is to keep things simple and to keep the apps local-first as much as we can. 43 | 44 | 45 | ### Process 46 | 47 | 1. Describe the idea briefly 48 | 2. Create at least one user story 49 | - e.g. Sammy wants to keep notes on recipes. They go to ipns://cooknot.es to start a notebook. From here they can search for recipes to notate. When they select a recipe a note with the date and a link to the recipe is created 50 | 3. Outline your concept. This could be the app ux, architecture, a description, a wireframe or mockups; 51 | - This should define what the prototype needs to have at a minimum (think about what accomplishes the goal vs. what you can do in two weeks!). 52 | - List anticipated features / challenges e.g. read from external ipfs directory for recipes, save notes to ipfs, create new key for ipns address of site, structure notes, list notes, etc.... 53 | 4. Get feedback from the team 54 | 5. If there are any blockers, discuss what you need with the team to move forward. 55 | 6. If no blockers remain, make the app! 56 | 57 | We want to explore how long it takes devs of different backgrounds to make apps with these P2P protocols. So we'll timebox the apps at three weeks; roughly two to make the apps and then one to finish up the tutorial and write up retrospectives. 58 | 59 | ## Making Tutorials 60 | 61 | ### Some guidelines: 62 | 63 | Tutorials should be easy to follow. 64 | * There should always be a clear next step. 65 | * Common issues should be called out and visually distinct. 66 | 67 | Err on the side of usability. 68 | * A complete beginner should be able to follow the tutorial but someone with more experience should get something out of it too. 69 | 70 | Give the reader a steady path of small wins. 71 | * Be generous with steps that let the reader change the code, refresh the page, and see something new. 72 | 73 | A programming tutorial should not only teach you to accomplish a goal, but to debug along the way. 74 | * One useful interactive technique is to "follow the error". Your tutorial moves forward with intuition as one might when programming, and so you have the reader run into the same errors you might. The learner can see the error message and what caused it, and follow the tutorial for the fix. 75 | 76 | We want to teach people how to design things, and don't need to spend time teaching people the web. 77 | - Start out showing the reader what they're designing, and how they can write and designing their own idea. 78 | - Link out a lot to MDN for more technical resources. 79 | 80 | ## Presenting Your App (Retrospectives) 81 | 82 | #### What was the outcome? 83 | 84 | 1. To share your app with the group, first walk us through the original goal and what you ended up making. 85 | 86 | 2. Did you accomplish more or less than the original app idea? If you started out with a user story, were those aims accomplished? 87 | 88 | 3. Did the idea evolve or change drastically through the process? If so, how did these changes affect the outcome? 89 | 90 | #### What was the process like? 91 | 92 | 1. Describe making the app. Link to the tutorial here. 93 | 2. What else was notable about the process? Some discussion questions: 94 | - What went well? 95 | - What took a lot of time? 96 | - Did you find anything else to be unexpected about making the app? 97 | - If you could give yourself advice when you started making the app, what would you tell yourself? 98 | 99 | #### Discussion 100 | 101 | 1. What would you do with this app if you kept working on it? E.g. future improvements? Demo it with users (if so, who would the users be)? 102 | 103 | 104 | 2. If any technical improvements to Agregore or the underlying protocols could make development easier for this app, please describe them here. 105 | 106 | 107 | ## Publishing 108 | 109 | At the end of each sprint we will be publishing the tutorials on the Agregore [docs](https://agregore.mauve.moe/docs/) section of the website, and the retrospectives on the [Blog](https://agregore.mauve.moe/blog/) section. This will be done by submitting pull requests to the [Website's github repo](https://github.com/AgregoreWeb/website/) which will then auto-publish to the site upon merging. 110 | 111 | The source code for all the apps will be licensed under the [MIT License](https://en.wikipedia.org/wiki/MIT_License) so that it may be reused by others for any purpose. 112 | -------------------------------------------------------------------------------- /explore.md: -------------------------------------------------------------------------------- 1 | ## Explore The Distributed Web - Agregore Browser 2 | 3 | Here's a collection of useful websites on the distributed web. 4 | Feel free to [submit a pull request](https://github.com/AgregoreWeb/website/) on our Github repo to add your own link to this page. 5 | 6 | ### Demos and tools: 7 | 8 | - [Agregore Docs](/docs/) 9 | - [DWeb Scratchpad](/apps/scratchpad.html) 10 | - [DWeb File Viewer](/apps/fileviewer.html) 11 | - [Drag and Drop File Uploads](/docs/examples/drag-and-drop/) 12 | - [Theme Builder](/docs/examples/themebuilder) 13 | - [Sutty CMS, static site generator with dweb publishing](https://sutty.nl/en/) 14 | - [Distributed Press Social Reader](//reader.distributed.press/) 15 | - [Local AI App Generator](/docs//examples/llm-appgen/) 16 | - [Local AI Chat Example](/docs/examples/llm-chat.html) 17 | - [Quick Code Snippet Generator](/docs/examples/quickcode.html) 18 | - [Local AI Vision Demo](/docs/examples/llm-vision.html) 19 | 20 | ### Aggregators and Search 21 | 22 | - [DWeb Explorer](https://explore.distributed.press/) 23 | - ["Totally Legit" Gemini Search](gemini://tlgs.one/search?agregore) 24 | 25 | ### Protocols 26 | 27 | - [Hypercore Protocol](https://hypercore-protocol.org) 28 | - [IPFS](ipns:://ipfs.tech) 29 | - [Gemini Protocol](gemini://geminiprotocol.net/) 30 | - [BitTorrent](https://bittorrent.org/) 31 | 32 | ### Extensions: 33 | 34 | - [DScan: Own Your Identity, Own Your Data](https://chrome.google.com/webstore/detail/dscan-decentralized-qr-co/idpfgkgogjjgklefnkjdpghkifbjenap) 35 | 36 | ### Blogs / Homepages / Magazines 37 | 38 | - [Mauve's Blog](//blog.mauve.moe) 39 | - [Hypha Worker Coop](//hypha.coop) 40 | - [CW Flashing, web animation series](hyper://31fcaz66jb57o9pm1gdook8ftni5q13mwhn9jrbx4pgs8kqod6zy/) 41 | - [COMPOST Issue One](//one.compost.digital), [Issue Two](//two.compost.digital), [Issue Three](//three.compost.digital/) 42 | 43 | ### Tutorials 44 | 45 | - [Agregore Tutorials in our docs](/docs/#tutorials) 46 | - [Making a P2P Gemini blog](https://mastodon.mauve.moe/@mauve/110099166164967631) 47 | 48 | ### Built In Pages 49 | 50 | - [agregore://welcome](agregore://welcome) 51 | - [agregore://settings](agregore://settings) 52 | - [agregore://history](agregore://history) 53 | - [agregore://about](agregore://about) 54 | - [agregore://about](agregore://404) 55 | - [agregore://theme/example](agregore://theme/example) 56 | - [agregore://theme/style.css](agregore://theme/style.css) 57 | - [agregore://theme/vars.css](agregore://theme/vars.css) 58 | 59 | ### Other browsers: 60 | 61 | - [Peersky](https://peersky.p2plabs.xyz/) 62 | - [Galacteek](https://galacteek.gitlab.io/) 63 | - [Hybrid](https://github.com/HybridWare/hybrid-browser) 64 | 65 | --- 66 | 67 | [Home](/) 68 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | # Frequently-asked-Questions 2 | 3 | Here's a list of frequently asked questions from folks new to Agregore. 4 | If you think there's more that we should have on here please [open an issue on github](https://github.com/AgregoreWeb/website/issues/new). 5 | 6 | ### What are the differeneces between Hypercore, Bittorent and IPFS? 7 | + [Bittorent](https://bittorrent.org/) is the oldest out of the three and has the most data. However it does not support files that change over time. 8 | + [Hypercore](https://hypercore-protocol.github.io/new-website/guides/getting-started/) is fast and allows files to change, but it only is available in Javascript. 9 | + IPFS is not very strong with files that change but it does have some basic support unlike Bittorent. 10 | 11 | When peers use **Bittorent** or **Hypercore** to share data, they are seperated by the websites and can't share connections between websites. Where as in **IPFS** you can download data regaurdless of which website it is apart of. In other words, Bittorent and Hypercore find peers per website while IPFS finds peers per file. This also means that IPFS needs more resources to advertise and look up data. 12 | 13 | ### Why are Ad Bloockers good to use? 14 | Ads can be harmful to your computer by giving you viruses and wasting data. Ad companies can also violate your privacy without giving you a choice to say no. An example of a Ad Blocker is [UBlock Origin](https://ublockorigin.com/). 15 | Agregore uses extentions for lots of it's features and developers can add new features without having to change the browsers code. 16 | 17 | [**Web Extentions**](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) are a standard API that want to add new features to browsers. For example, ublock Origin is a extention that adds ad blocking to web browsers. Since Agregore uses the standard API we can use [ublock Origin](https://ublockorigin.com/) for ad blocking.. 18 | 19 | ### What does Markdown mean? 20 | [Markdown](https://daringfireball.net/projects/markdown/) is a text format for documents. Programmers use this for documentation. 21 | 22 | ### What does API stand for and what does it mean? 23 | API stands for *Application Programming Interface*. This is a library of pre-made code that programmers can use. 24 | 25 | ### What is a Large Language Model? 26 | A [Large Language Model](https://developers.google.com/machine-learning/resources/intro-llms) are large nero networks that work on text and is the foundation of chat based AI. 27 | 28 | ### What is Electron Framework? 29 | [Electron Framework](https://www.electronjs.org/) is a program that let's you use JavaScript to make applications. It uses **API's** to create windows and interact with the computer. 30 | 31 | ### What does Protocal Handler mean? 32 | [Protocal Handler](https://www.electronjs.org/docs/latest/api/protocol) ia an *Electron* feature that can register code inorder to handle different protocals besides http on the web. 33 | 34 | -------------------------------------------------------------------------------- /hyper-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/hyper-url.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /index.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Agregore Browser - Website" 3 | } 4 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | # Agregore 24 | 25 | Build the distributed web. 26 | 27 | [Explore](./explore) 28 | [Blog](./blog/) 29 | [Docs](./docs/) 30 | [Videos](./videos) 31 | [FAQ](./faq) 32 | [Download](https://github.com/AgregoreWeb/agregore-browser/releases/latest) 33 | [Mobile](https://github.com/AgregoreWeb/agregore-mobile/) 34 | 35 | Agregore is not just a `Web-Browser`, it's a `Web-Weaver`. 36 | It gives you the tools to load and author content locally without needing the cloud. 37 | Instead of needing to be always connected to a server, you can load from your cache or directly from others. 38 | Agregore automatically reshares P2P websites with others so the more popular a site is, the faster it is to load it. 39 | It works offline, on local networks, and over the internet. 40 | Made to support a resilient and person-first foundation for software. 41 | 42 | ### Features 43 | 44 | - Browse the existing (HTTP) web like usual. 45 | - Browse the [Distributed Web](https://getdweb.net/) on [Hypercore](https://github.com/hypercore-protocol), [BitTorrent](http://bittorrent.org/introduction.html), and [IPFS](https://ipfs.tech/). 46 | - Browse alternate web protocols like [Gemini](https://geminiprotocol.net/) 47 | - Tracking free with a built in [Ad blocker](https://ublockorigin.com/) 48 | - Render [Markdown](https://www.markdownguide.org/basic-syntax/) and JSON natively. 49 | - Archive and save web pages for offline use via [ArchiveWeb.page](https://archiveweb.page/) 50 | - Customizable [color scheme](/docs/theming) which gets applied to all pages 51 | - New web [APIs for creating p2p sites and apps](/docs/#protocols) 52 | - Built in [large language model APIs](/docs/ai) using free to use local models 53 | 54 | ![Screenshot showing Agregore Browser loading a hyper:// URL](screenshot.png) 55 | 56 | ### Watch the 5 Minute Intro 57 | 58 | 59 | 60 | ### How does it work? 61 | 62 | Agregore works by letting you load web pages and content from peer to peer protocols the same way you would load them from a website. 63 | 64 | In the same way as you can navigate to `http://example.com`, you can navigate to `hyper://blog.mauve.moe` and have it load from anybody on the network that has a copy. 65 | This can be done via the different protocols that Agregore supports like [BitTorrent](https://github.com/AgregoreWeb/agregore-markdown-site-generator), [IPFS](https://ipfs.io), and [Hypercore Protocol](https://github.com/AgregoreWeb/agregore-markdown-site-generator). 66 | The web contents are rendered via Chromium using the [Electron framework](https://www.electronjs.org/). 67 | Electron is useful since it's what allows us to publish Agregore on Windows, MacOS, and Linux. 68 | 69 | ### How do I make stuff? 70 | 71 | Agregore not only supports loading data through custom protocols, but it also provides APIs for uploading data into peer to peer protocols. 72 | This is done via the browser's [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) API which is what web developers use to talk to web servers over HTTP or HTTPS. 73 | 74 | You can create your own peer to peer website using this simple code snippet (open dev tools with CTRL+Shift+I): 75 | 76 | ```javascript 77 | // Upload your website page 78 | url = 'ipfs://bafyaabakaieac/example.txt' 79 | response = await fetch(url, { 80 | method: 'PUT', 81 | body: 'Hello World! 👋 🌎🌍🌏' 82 | })// Navigate to the website 83 | window.location.href = response.headers.get('Location') 84 | ``` 85 | 86 | Try making an app in the [DWeb Scratchpad](/apps/scratchpad.html). For more details and demos, check out the the [Videos](videos.html) page, or read the [Fetch API Docs](https://github.com/AgregoreWeb/agregore-browser/tree/master/docs). 87 | 88 | [Source Code](https://github.com/AgregoreWeb/agregore-browser) 89 | [Contact](mailto:agregore@mauve.moe) 90 | [Discord](https://discord.gg/QMthd4Y) 91 | [Matrix](https://matrix.to/#/#agregore:mauve.moe) 92 | [Mastodon](https://mastodon.mauve.moe/@agregore) 93 | [BlueSky](https://bsky.app/profile/agregore.mauve.moe) 94 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | mdspell -naf ./blog/ 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgregoreWeb/website/2bb1484af9f5339bd6ace7d9f975211e64392f4f/screenshot.png -------------------------------------------------------------------------------- /videos.md: -------------------------------------------------------------------------------- 1 | # Videos - Agregore Browser 2 | 3 | Here's a collection of videos related to Agregore. If you'd like to schedule a presentation at your own meetup, feel free to reach out by sending us [an email](mailto:agregore@mauve.moe?subject=Booking%20a%20presentation). 4 | 5 | ### 5 Minute Lightning Talk / Overview - DWeb Meetup 6 | 7 | 8 | 9 | ### LLMs in the user agent 10 | 11 | 12 | 13 | ### How Decentralized Web Apps work - Bloom 14 | 15 | 16 | 17 | ### Making of Agregore - Speakeasy JS 18 | 19 | 20 | 21 | ### Agregore IPFS Intro (live demo) - IPFS Meetup 22 | 23 | 24 | 25 | ### Initial release of Agregore - Dat Conference 26 | 27 | 28 | --------------------------------------------------------------------------------