├── doc.sh ├── renovate.json ├── src ├── vox_providers │ ├── mod.rs │ ├── oku_provider │ │ ├── mod.rs │ │ ├── view_source.rs │ │ └── core.rs │ └── okunet_provider │ │ ├── mod.rs │ │ ├── home.rs │ │ ├── search.rs │ │ ├── tags.rs │ │ ├── core.rs │ │ └── users.rs ├── okunet_pages │ ├── global.toml │ ├── snippets │ │ ├── posts.voxs │ │ ├── tags.voxs │ │ ├── tag.voxs │ │ ├── search.voxs │ │ ├── masthead.html │ │ ├── user_header.html │ │ ├── head.html │ │ ├── follow_button.html │ │ ├── block_button.html │ │ ├── delete_button.html │ │ ├── tab_pages.html │ │ ├── profile.voxs │ │ └── post.voxs │ ├── tags.vox │ ├── home.vox │ └── layouts │ │ ├── default.vox │ │ └── post.vox ├── widgets │ ├── settings │ │ ├── mod.rs │ │ └── core.rs │ ├── mod.rs │ ├── window │ │ ├── note.rs │ │ ├── mod.rs │ │ ├── headerbar.rs │ │ └── suggestions.rs │ └── tag.rs ├── scheme_handlers │ ├── mod.rs │ ├── hive.rs │ ├── hive_path.rs │ ├── view_source.rs │ ├── ipfs.rs │ ├── util.rs │ └── oku_path.rs ├── database │ ├── mod.rs │ └── core.rs ├── browser_pages │ ├── global.toml │ ├── layouts │ │ ├── home.vox │ │ ├── default.vox │ │ └── view_source.vox │ ├── snippets │ │ ├── head.html │ │ ├── hljs.default.min.css │ │ ├── highlightjs-line-numbers.min.js │ │ └── normalise.css │ └── home.vox ├── config │ ├── mod.rs │ └── enums.rs ├── replica_item.rs └── suggestion_item.rs ├── branding ├── banner.png ├── banner.xcf ├── logo-filled.svg └── logo.svg ├── dev_doc.sh ├── _rust-toolchain.toml ├── prebuild.sh ├── data ├── hicolor │ ├── 128x128 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 16x16 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 192x192 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 22x22 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 24x24 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 256x256 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 32x32 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 36x36 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 384x384 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 40x40 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 48x48 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 512x512 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 64x64 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 72x72 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ ├── 96x96 │ │ └── apps │ │ │ └── io.github.OkuBrowser.oku.png │ └── scalable │ │ └── actions │ │ ├── menu-symbolic.svg │ │ ├── zoom-out-symbolic.svg │ │ ├── zoom-in-symbolic.svg │ │ ├── zoom-original-symbolic.svg │ │ ├── ticket-symbolic.svg │ │ ├── first-symbolic.svg │ │ ├── left-symbolic.svg │ │ ├── down-symbolic.svg │ │ ├── right-symbolic.svg │ │ ├── user-info-symbolic.svg │ │ ├── bookmark-filled-symbolic.svg │ │ ├── fullscreen-rectangular-symbolic.svg │ │ ├── unfullscreen-rectangular-symbolic.svg │ │ ├── printer-symbolic.svg │ │ ├── note-symbolic.svg │ │ ├── editor-symbolic.svg │ │ ├── up-symbolic.svg │ │ ├── edit-find-symbolic.svg │ │ ├── window-new-symbolic.svg │ │ ├── tab-new-symbolic.svg │ │ ├── ticket-special-symbolic.svg │ │ ├── folder-new-symbolic.svg │ │ ├── hourglass-symbolic.svg │ │ ├── arrow-pointing-at-line-down-symbolic.svg │ │ ├── wrench-wide-symbolic.svg │ │ ├── copy-symbolic.svg │ │ ├── window-close-symbolic.svg │ │ ├── loop-arrow-symbolic.svg │ │ ├── seek-backward-symbolic.svg │ │ ├── cross-large-symbolic.svg │ │ ├── info-outline-symbolic.svg │ │ ├── shapes-symbolic.svg │ │ ├── people-symbolic.svg │ │ ├── text-underline-symbolic.svg │ │ ├── user-home-symbolic.svg │ │ ├── library-symbolic.svg │ │ ├── user-trash-symbolic.svg │ │ ├── uppercase-symbolic.svg │ │ ├── arrow-circular-top-right-symbolic.svg │ │ ├── screenshot-recorded-symbolic.svg │ │ ├── system-switch-user-symbolic.svg │ │ ├── file-cabinet-symbolic.svg │ │ ├── external-link-symbolic.svg │ │ ├── update-symbolic.svg │ │ ├── folder-remote-symbolic.svg │ │ ├── entry-clear-symbolic.svg │ │ ├── keyboard-shortcuts-symbolic.svg │ │ ├── folder-open-symbolic.svg │ │ ├── screen-privacy7-symbolic.svg │ │ ├── settings-symbolic.svg │ │ └── globe-symbolic.svg ├── io.github.OkuBrowser.oku.desktop └── io.github.OkuBrowser.oku.metainfo.xml ├── precommit.sh ├── lint_flatpak.sh ├── .cargo └── config.toml ├── .whitesource ├── .github ├── workflows-old │ ├── scheduled_audit.yml │ ├── audit_on_change.yml │ ├── annotate_code.yml │ ├── format_code.yml │ ├── fix_code.yml │ ├── check_code.yml │ ├── mac-intel.yml │ ├── mac-apple.yml │ ├── lint_code.yml │ ├── coverage.yml │ ├── shiftleft_analysis.yml │ ├── deb.yml │ ├── aarch64.yml │ ├── x86_64.yml │ ├── flatpak.yml │ ├── gh-pages.yml │ ├── rpm.yml │ └── windows.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── stale.yml ├── FUNDING.yml └── pull_request_template.md ├── flathub ├── fusermount-wrapper.sh └── io.github.OkuBrowser.oku.json ├── install_flatpak.sh ├── NOTICE ├── SECURITY.md ├── README.md ├── .vscode └── launch.json ├── CONTRIBUTING.md ├── generate_logo_png.sh ├── .gitignore ├── BUILDING.md ├── Cargo.toml └── resources.gresource.xml /doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo doc --no-deps -p oku --all-features --release -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/vox_providers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod oku_provider; 2 | pub mod okunet_provider; 3 | -------------------------------------------------------------------------------- /src/vox_providers/oku_provider/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | pub mod view_source; 3 | -------------------------------------------------------------------------------- /branding/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/branding/banner.png -------------------------------------------------------------------------------- /branding/banner.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/branding/banner.xcf -------------------------------------------------------------------------------- /src/okunet_pages/global.toml: -------------------------------------------------------------------------------- 1 | url = "oku:" 2 | author = "Emil Sayahi" 3 | title = "OkuNet" -------------------------------------------------------------------------------- /src/widgets/settings/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod appearance; 2 | pub mod core; 3 | pub mod okunet; 4 | -------------------------------------------------------------------------------- /dev_doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cargo doc --document-private-items --workspace --all-features --release -------------------------------------------------------------------------------- /_rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = [ "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu" ] -------------------------------------------------------------------------------- /src/okunet_pages/snippets/posts.voxs: -------------------------------------------------------------------------------- 1 | {% for post in include.posts %} 2 | {% include post.voxs post = post %} 3 | {% endfor %} -------------------------------------------------------------------------------- /prebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | glib-compile-resources --target="resources.gresource" --sourcedir="data/hicolor/scalable/actions" "resources.gresource.xml" -------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | pub mod home; 3 | pub mod posts; 4 | pub mod search; 5 | pub mod tags; 6 | pub mod users; 7 | -------------------------------------------------------------------------------- /data/hicolor/128x128/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/128x128/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/16x16/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/16x16/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/192x192/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/192x192/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/22x22/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/22x22/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/24x24/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/24x24/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/256x256/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/256x256/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/32x32/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/32x32/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/36x36/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/36x36/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/384x384/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/384x384/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/40x40/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/40x40/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/48x48/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/48x48/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/512x512/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/512x512/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/64x64/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/64x64/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/72x72/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/72x72/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /data/hicolor/96x96/apps/io.github.OkuBrowser.oku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OkuBrowser/oku/HEAD/data/hicolor/96x96/apps/io.github.OkuBrowser.oku.png -------------------------------------------------------------------------------- /precommit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cargo clippy --fix --allow-dirty 3 | # __CARGO_FIX_YOLO=1 cargo clippy --fix --broken-code --allow-dirty 4 | cargo fmt 5 | cargo check -------------------------------------------------------------------------------- /src/scheme_handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod hive; 2 | pub mod hive_path; 3 | pub mod ipfs; 4 | pub mod oku; 5 | pub mod oku_path; 6 | pub mod util; 7 | pub mod view_source; 8 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/tags.voxs: -------------------------------------------------------------------------------- 1 | {% for tag in include.tags %} 2 | #{{ tag }}{% unless forloop.last %}, {% endunless -%} 3 | {% endfor %} -------------------------------------------------------------------------------- /lint_flatpak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest ./build-aux/io.github.OkuBrowser.oku.json 3 | flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | rustflags = ["-C", "target-cpu=native", "-C", "strip=symbols"] 3 | 4 | [target.x86_64-unknown-linux-gnu] 5 | rustflags = ["-C", "target-cpu=native", "-C", "strip=symbols"] -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /src/database/mod.rs: -------------------------------------------------------------------------------- 1 | mod bookmark; 2 | mod core; 3 | mod history_record; 4 | #[allow(unused_imports)] 5 | pub use self::bookmark::*; 6 | #[allow(unused_imports)] 7 | pub use self::core::*; 8 | #[allow(unused_imports)] 9 | pub use self::history_record::*; 10 | pub mod policy; 11 | -------------------------------------------------------------------------------- /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address_entry; 2 | pub mod bookmark_row; 3 | pub mod download_row; 4 | pub mod history_row; 5 | pub mod note_editor; 6 | pub mod replica_row; 7 | pub mod settings; 8 | pub mod suggestion_row; 9 | pub mod tag; 10 | pub mod toybox; 11 | pub mod window; 12 | -------------------------------------------------------------------------------- /src/browser_pages/global.toml: -------------------------------------------------------------------------------- 1 | locale = "en_US" 2 | title = "Oku" 3 | description = "Your new home on the Internet" 4 | author = "Emil Sayahi" 5 | site = "https://okubrowser.github.io" 6 | browser_repository = "https://github.com/OkuBrowser/oku" 7 | sponsor_link = "https://github.com/sponsors/emmyoh" -------------------------------------------------------------------------------- /src/okunet_pages/snippets/tag.voxs: -------------------------------------------------------------------------------- 1 |

#{{ page.data.title }}

2 | 3 |

Posts

4 | 5 | {% if include.posts[0] %} 6 | {% assign posts = include.posts | sort: "date" | reverse %} 7 | {% include posts.voxs posts = posts %} 8 | {% else %} 9 | 10 |

No posts found …

11 | 12 | {% endif %} -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/menu-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/browser_pages/layouts/home.vox: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | {% include head.html %} 7 | 8 | 9 |
10 | {{- layouts | map: "rendered" | first -}} 11 |
12 | 13 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/search.voxs: -------------------------------------------------------------------------------- 1 | {% markdown %} 2 | # Results for `{{ page.data.title }}` 3 | {% endmarkdown %} 4 | 5 | {% if include.posts[0] %} 6 | {% assign posts = include.posts | sort: "date" | reverse %} 7 | {% include posts.voxs posts = posts %} 8 | {% else %} 9 | 10 |

No results …

11 | 12 | {% endif %} -------------------------------------------------------------------------------- /src/okunet_pages/snippets/masthead.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | okunet 4 |

5 | profile 6 | tags 7 |
-------------------------------------------------------------------------------- /data/hicolor/scalable/actions/zoom-out-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/okunet_pages/tags.vox: -------------------------------------------------------------------------------- 1 | --- 2 | layout = "default" 3 | title = "Tags" 4 | permalink = "tags" 5 | depends = ["tag"] 6 | --- 7 | {% markdown %} 8 | # {{ page.data.title }} 9 | 10 | {% if tag[0] %} 11 | {% for tag_i in tag %} 12 | - [#{{ tag_i.data.title }}]({{ tag_i.url | prepend: global.url }}) 13 | {% endfor %} 14 | {% else %} 15 | No tags found … 16 | {% endif %} 17 | {% endmarkdown %} -------------------------------------------------------------------------------- /.github/workflows-old/scheduled_audit.yml: -------------------------------------------------------------------------------- 1 | name: Dependency security audit (scheduled) 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | scheduled_security_audit: 7 | name: Security audit 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions-rs/audit-check@v1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /flathub/fusermount-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$_FUSE_COMMFD" ]; then 4 | FD_ARGS= 5 | else 6 | FD_ARGS="--env=_FUSE_COMMFD=${_FUSE_COMMFD} --forward-fd=${_FUSE_COMMFD}" 7 | fi 8 | 9 | if [ -e /proc/self/fd/3 ] && [ 3 != "$_FUSE_COMMFD" ]; then 10 | FD_ARGS="$FD_ARGS --forward-fd=3" 11 | fi 12 | 13 | exec flatpak-spawn --host --forward-fd=1 --forward-fd=2 $FD_ARGS fusermount3 "$@" -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/zoom-in-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows-old/audit_on_change.yml: -------------------------------------------------------------------------------- 1 | name: Dependency security audit (on change) 2 | on: 3 | push: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | jobs: 8 | security_audit: 9 | name: Security audit 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions-rs/audit-check@v1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/zoom-original-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/ticket-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/user_header.html: -------------------------------------------------------------------------------- 1 |
2 |

{{- page.data.title -}} 

3 | {%- unless page.data.is_me -%} 4 | {%- unless page.data.is_blocked -%} 5 | {%- include follow_button.html author_id = page.data.author_id is_followed = page.data.is_followed -%} 6 | {%- endunless -%} 7 | {%- include block_button.html author_id = page.data.author_id is_blocked = page.data.is_blocked -%} 8 | {%- endunless -%} 9 |
-------------------------------------------------------------------------------- /src/okunet_pages/home.vox: -------------------------------------------------------------------------------- 1 | --- 2 | layout = "default" 3 | title = "OkuNet" 4 | permalink = "home" 5 | depends = ["posts"] 6 | --- 7 |

Posts

8 | 9 | {% if posts[0] %} 10 | {% assign sorted_posts = posts | sort: "date" | reverse %} 11 | {% include posts.voxs posts = sorted_posts %} 12 | {% else %} 13 |

No posts found …

14 | {% markdown %} 15 | > Welcome to OkuNet. Posts from your network will appear here. 16 | {% endmarkdown %} 17 | {% endif %} -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/first-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/head.html: -------------------------------------------------------------------------------- 1 | {% if page.data.title %} 2 | {{ page.data.title }} 3 | {% else %} 4 | {{ global.title }} 5 | {% endif %} 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/left-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/browser_pages/layouts/default.vox: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | {% include head.html %} 7 | 8 | 9 |
10 | {{- layouts | map: "rendered" | first -}} 11 |
12 | 13 | 18 | -------------------------------------------------------------------------------- /src/browser_pages/snippets/head.html: -------------------------------------------------------------------------------- 1 | {% if page.data.title %} 2 | {{ page.data.title }} 3 | {% endif %} 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/down-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/right-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/user-info-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/bookmark-filled-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/fullscreen-rectangular-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/unfullscreen-rectangular-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/printer-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/follow_button.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/note-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/block_button.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/delete_button.html: -------------------------------------------------------------------------------- 1 | {%- if include.post.data.by_me -%} 2 | {%- assign path = include.post.url | split: "/" -%} 3 | 6 | 7 | 15 | {%- endif -%} -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/editor-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /src/widgets/window/note.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::widgets; 3 | use glib::clone; 4 | use gtk::glib; 5 | use gtk::subclass::prelude::*; 6 | use libadwaita::prelude::*; 7 | 8 | impl Window { 9 | pub fn setup_note_button_clicked(&self) { 10 | let imp = self.imp(); 11 | 12 | imp.note_button.connect_clicked(clone!( 13 | #[weak(rename_to = this)] 14 | self, 15 | move |_| { 16 | widgets::note_editor::NoteEditor::new(Some(&this), None); 17 | } 18 | )); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/okunet_pages/layouts/default.vox: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | {% include head.html %} 7 | 8 | 9 | {% include masthead.html %} 10 |
11 | {{- layouts | map: "rendered" | first -}} 12 |
13 | 18 | 19 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/up-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/edit-find-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/window-new-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/tab-new-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/ticket-special-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/okunet_pages/snippets/tab_pages.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/browser_pages/layouts/view_source.vox: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | {{ page.data.title }} 7 | 10 | 16 | 17 | 18 |
19 |             
20 | {{- layouts | map: "rendered" | first | escape -}}
21 |             
22 |         
23 | 24 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/folder-new-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /install_flatpak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./prebuild.sh 3 | curl --output "flatpak-cargo-generator.py" "https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py" 4 | python3 ./flatpak-cargo-generator.py ./Cargo.lock -o "./build-aux/cargo-sources.json" 5 | flatpak run org.flatpak.Builder --force-clean --user --install --install-deps-from=flathub --ccache --mirror-screenshots-url=https://dl.flathub.org/media/ --repo=repo builddir build-aux/io.github.OkuBrowser.oku.json 6 | flatpak build-bundle repo oku.flatpak io.github.OkuBrowser.oku --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo 7 | ostree commit --repo=repo --canonical-permissions --branch=screenshots/x86_64 builddir/files/share/app-info/media -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[REQUEST] - " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (C) Emil Sayahi 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU Affero General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU Affero General Public License for more details. 12 | 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | The Oku team and community take security issues seriously. Your efforts to responsibly disclose your findings are appreciated, and every effort will be made to acknowledge your contributions. 4 | 5 | To report a security issue, please use the GitHub Security Advisory ['Report a Vulnerability'](https://github.com/OkuBrowser/oku/security/advisories/new) tab. 6 | 7 | The Oku team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 8 | 9 | Report security issues in third-party dependencies to the maintainers of the dependency. -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/hourglass-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows-old/annotate_code.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: Annotate code 3 | jobs: 4 | clippy_check: 5 | name: Annotate code 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v3 9 | - name: Install development dependencies 10 | run: | 11 | sudo apt-get update 12 | sudo apt-get install -qq gtk3.0 gtk2.0 libgtk-3-dev libgtk2.0-dev libglib2.0-dev glade libsoup-gnome2.4-dev libwebkit2gtk-4.0-dev > /dev/null 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: clippy 17 | override: true 18 | - uses: actions-rs/clippy-check@v1 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | args: --all-features -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/arrow-pointing-at-line-down-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/wrench-wide-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/vox_providers/oku_provider/view_source.rs: -------------------------------------------------------------------------------- 1 | use super::core::OkuProvider; 2 | use uuid::Uuid; 3 | use vox::provider::VoxProvider; 4 | 5 | impl OkuProvider { 6 | pub fn view_source(&self, html: String, uri: String) -> miette::Result { 7 | let file_id = Uuid::now_v7(); 8 | let file_path = format!("{}.vox", file_id); 9 | let mut table = toml::Table::new(); 10 | table.insert("layout".into(), "view_source".into()); 11 | table.insert("permalink".into(), format!("{}.html", file_id).into()); 12 | table.insert("title".into(), uri.into()); 13 | self.0 14 | .write_file(file_path.clone(), format!("---\n{}\n---\n{}", table, html))?; 15 | self.render_and_get(format!("output/{}.html", file_id)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/copy-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/window-close-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] - " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Arch Linux] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/loop-arrow-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/seek-backward-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/cross-large-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/info-outline-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/home.rs: -------------------------------------------------------------------------------- 1 | use super::core::OkuNetProvider; 2 | use crate::NODE; 3 | use rayon::prelude::*; 4 | use rayon::slice::ParallelSliceMut; 5 | use std::cmp::Reverse; 6 | 7 | impl OkuNetProvider { 8 | pub async fn view_home(&self) -> miette::Result { 9 | let node = NODE 10 | .get() 11 | .ok_or(miette::miette!("No running Oku node … "))?; 12 | 13 | tokio::spawn(node.refresh_users()); 14 | 15 | // Posts 16 | let mut posts = Vec::from_par_iter(node.all_posts().await); 17 | posts.par_sort_unstable_by_key(|x| Reverse(x.entry.timestamp())); 18 | for post in posts.iter() { 19 | self.create_post_page(&post.user(), post, Some("posts".into())) 20 | .await?; 21 | } 22 | 23 | self.render_and_get("output/home") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/shapes-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/people-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/text-underline-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/io.github.OkuBrowser.oku.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Oku 3 | GenericName=Web Browser 4 | Comment=Browse & express yourself 5 | Type=Application 6 | Exec=oku %U 7 | Terminal=false 8 | Categories=Network;WebBrowser;GTK;GNOME; 9 | Keywords=web;dweb;hive;iroh;ipfs;internet;oku; 10 | Icon=io.github.OkuBrowser.oku 11 | StartupNotify=true 12 | X-GNOME-UsesNotifications=true 13 | MimeType=x-scheme-handler/unknown;x-scheme-handler/about;text/html;text/xml;application/xhtml_xml;image/webp;x-scheme-handler/http;x-scheme-handler/https;application/x-xpinstall;application/pdf;application/json;application/xhtml+xml;multipart/related;application/x-mimearchive;message/rfc822; 14 | Actions=new-window;new-private-window; 15 | X-Purism-FormFactor=Workstation;Mobile; 16 | 17 | [Desktop Action new-window] 18 | Name=New Window 19 | Exec=oku --new-window %U 20 | 21 | [Desktop Action new-private-window] 22 | Name=New Private Window 23 | Exec=oku --new-private-window %U 24 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/user-home-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [emmyoh] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /src/widgets/window/mod.rs: -------------------------------------------------------------------------------- 1 | mod colour; 2 | mod core; 3 | mod dialogs; 4 | mod downloads; 5 | mod finding; 6 | mod headerbar; 7 | mod menu; 8 | mod navigation; 9 | mod note; 10 | mod sidebar; 11 | mod suggestions; 12 | mod tabs; 13 | mod view; 14 | #[allow(unused_imports)] 15 | pub use self::colour::*; 16 | #[allow(unused_imports)] 17 | pub use self::core::*; 18 | #[allow(unused_imports)] 19 | pub use self::dialogs::*; 20 | #[allow(unused_imports)] 21 | pub use self::downloads::*; 22 | #[allow(unused_imports)] 23 | pub use self::finding::*; 24 | #[allow(unused_imports)] 25 | pub use self::headerbar::*; 26 | #[allow(unused_imports)] 27 | pub use self::menu::*; 28 | #[allow(unused_imports)] 29 | pub use self::navigation::*; 30 | #[allow(unused_imports)] 31 | pub use self::note::*; 32 | #[allow(unused_imports)] 33 | pub use self::sidebar::*; 34 | #[allow(unused_imports)] 35 | pub use self::suggestions::*; 36 | #[allow(unused_imports)] 37 | pub use self::tabs::*; 38 | #[allow(unused_imports)] 39 | pub use self::view::*; 40 | -------------------------------------------------------------------------------- /src/okunet_pages/layouts/post.vox: -------------------------------------------------------------------------------- 1 | --- 2 | layout = "default" 3 | --- 4 | -------------------------------------------------------------------------------- /.github/workflows-old/format_code.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Format codebase 7 | 8 | jobs: 9 | code_format: 10 | name: Format codebase 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - name: Checkout codebase 14 | uses: actions/checkout@v3 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | - name: Setup Rust toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: nightly 21 | profile: minimal 22 | components: clippy, rustfmt 23 | - name: Format code files 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: fmt 27 | - name: Commit changes to code, if any 28 | run: | 29 | git config --global user.name 'Oku' 30 | git config --global user.email 'OkuBrowser@users.noreply.github.com' 31 | git diff --quiet && git diff --staged --quiet || git commit -am "Automatically enforce Rust styleguide" 32 | git push -------------------------------------------------------------------------------- /src/okunet_pages/snippets/profile.voxs: -------------------------------------------------------------------------------- 1 | {% include user_header.html %} 2 | 3 | 4 |
5 | 6 | 7 |
8 | 9 |
10 |

Posts

11 | {% if include.posts[0] %} 12 | {% assign posts = include.posts | sort: "date" | reverse %} 13 | {% include posts.voxs posts = posts %} 14 | {% else %} 15 | 16 |

No posts

17 | 18 | {% endif %} 19 |
20 | 21 | 38 | 39 | {%- include tab_pages.html -%} -------------------------------------------------------------------------------- /.github/workflows-old/fix_code.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Revise codebase 7 | 8 | jobs: 9 | code_fix: 10 | name: Revise codebase 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout codebase 14 | uses: actions/checkout@v3 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | - name: Setup Rust toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: nightly 21 | profile: minimal 22 | components: clippy, rustfmt 23 | - name: Revise code files 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: fix 27 | args: --edition --edition-idioms 28 | - name: Commit changes to code, if any 29 | run: | 30 | git config --global user.name 'Oku' 31 | git config --global user.email 'OkuBrowser@users.noreply.github.com' 32 | git diff --quiet && git diff --staged --quiet || git commit -am "Automatically apply compiler suggestions" 33 | git push -------------------------------------------------------------------------------- /.github/workflows-old/check_code.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Evaluate codebase 4 | 5 | jobs: 6 | code_check: 7 | name: Evaluate codebase 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install -qq gtk3.0 gtk2.0 libgtk-3-dev libgtk2.0-dev libglib2.0-dev glade libsoup-gnome2.4-dev libwebkit2gtk-4.0-dev > /dev/null 16 | - name: Setup Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | components: clippy, rustfmt 21 | profile: minimal 22 | - name: Check codebase is properly formatted 23 | uses: actions-rs/cargo@v1 24 | with: 25 | command: fmt 26 | args: -- --check 27 | - name: Check codebase using linter 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: clippy 31 | args: --all-targets --all-features -- -D warnings -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/library-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/user-trash-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/uppercase-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/arrow-circular-top-right-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Oku](https://okubrowser.github.io) 2 | ## Browse & express yourself 3 | 4 | Oku is a browser that offers a space that exists parallel to the web, where sites are shared between peers and found through social bookmarking.\ 5 | While you may still browse traditional sites, Oku enables you to create, share, and find content without being beholden to centralised corporate interests.\ 6 | Unleash your creativity and join a network of users supporting an independent effort to reintroduce control, privacy, & free expression online. 7 | 8 | ## Build instructions 9 | 10 | If you are interested in using Oku, please follow the [documented build instructions](https://github.com/OkuBrowser/oku/blob/master/BUILDING.md). 11 | 12 | ## Installation 13 | - Linux: [Flathub](https://flathub.org/apps/io.github.OkuBrowser.oku) 14 | - macOS: [Waitlist](https://forms.gle/toyf1N6zdWzSAdpH8) 15 | - Windows: [Waitlist](https://forms.gle/toyf1N6zdWzSAdpH8) 16 | - Other: [Waitlist](https://forms.gle/toyf1N6zdWzSAdpH8) 17 | 18 | --- 19 | 20 | ## License 21 | 22 | This software is available under the [GNU Affero General Public License, Version 3](https://www.gnu.org/licenses/agpl-3.0.en.html). -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/screenshot-recorded-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/browser_pages/snippets/hljs.default.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Theme: Default 3 | Description: Original highlight.js style 4 | Author: (c) Ivan Sagalaev 5 | Maintainer: @highlightjs/core-team 6 | Website: https://highlightjs.org/ 7 | License: see project LICENSE 8 | Touched: 2021 9 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} -------------------------------------------------------------------------------- /src/okunet_pages/snippets/post.voxs: -------------------------------------------------------------------------------- 1 |
2 |
3 | {%- include delete_button.html post = include.post -%} 4 |

{{ include.post.data.title }}

5 | 8 |
9 | 10 | {% if include.post.data.tags[0] %} 11 | 12 | {% endif %} 13 | 14 | {%- if include.post.rendered.size > 0 -%} 15 |
16 | {{- include.post.rendered | truncatewords: 56, " … " -}} 17 | {%- endif -%} 18 |
-------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::CONFIG_DIR; 2 | use glib::subclass::object::ObjectImpl; 3 | use glib::subclass::types::ObjectSubclass; 4 | use glib::value::ToValue; 5 | use glib::ParamSpec; 6 | use glib::ParamSpecBoolean; 7 | use glib::ParamSpecBuilderExt; 8 | use glib::Value; 9 | use glib::{ParamSpecEnum, ParamSpecInt}; 10 | use log::error; 11 | use serde::Deserialize; 12 | use serde::Serialize; 13 | use std::cell::RefCell; 14 | use std::sync::LazyLock; 15 | 16 | pub mod enums; 17 | pub mod imp; 18 | 19 | glib::wrapper! { 20 | pub struct Config(ObjectSubclass); 21 | } 22 | 23 | impl Default for Config { 24 | fn default() -> Self { 25 | let config = imp::Config::new(); 26 | glib::Object::builder::() 27 | .property("colour-per-domain", config.colour_per_domain()) 28 | .property("colour-scheme", config.colour_scheme()) 29 | .property("palette", config.palette()) 30 | .property("width", config.width()) 31 | .property("height", config.height()) 32 | .property("is-maximised", config.is_maximised()) 33 | .property("is-fullscreen", config.is_fullscreen()) 34 | .build() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/system-switch-user-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/file-cabinet-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/external-link-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows-old/mac-intel.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Build: macOS (x86_64)' 4 | 5 | jobs: 6 | mac_x86-64: 7 | name: macOS (x86_64) 8 | runs-on: macos-latest 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | brew install gtk+3 libsoup libpsl icu4c 15 | brew link icu4c --force 16 | - name: Setup Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | target: x86_64-apple-darwin 21 | default: true 22 | profile: minimal 23 | - name: Build Oku 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: build 27 | args: --release --all-features --target x86_64-apple-darwin 28 | - name: Prepare Oku for upload 29 | run: | 30 | cd ./target/x86_64-apple-darwin/release/ 31 | strip ./oku 32 | chmod +x ./oku 33 | tar -cvf x86_64-oku.osx.tar \ 34 | oku 35 | - name: Upload Oku build artifacts to GitHub 36 | uses: actions/upload-artifact@v3 37 | with: 38 | name: x86_64-oku.osx 39 | path: ./target/x86_64-apple-darwin/release/x86_64-oku.osx.tar 40 | if-no-files-found: error -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/update-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows-old/mac-apple.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Build: macOS (ARM64)' 4 | 5 | jobs: 6 | mac_aarch64: 7 | name: macOS (ARM64) 8 | runs-on: macos-latest 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | brew install gtk+3 libsoup libpsl icu4c 15 | brew link icu4c --force 16 | - name: Setup Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | target: aarch64-apple-darwin 21 | default: true 22 | profile: minimal 23 | - name: Build Oku 24 | uses: actions-rs/cargo@v1 25 | with: 26 | command: build 27 | args: --release --all-features --target aarch64-apple-darwin 28 | - name: Prepare Oku for upload 29 | run: | 30 | cd ./target/aarch64-apple-darwin/release/ 31 | strip ./oku 32 | chmod +x ./oku 33 | tar -cvf aarch64-oku.osx.tar \ 34 | oku 35 | - name: Upload Oku build artifacts to GitHub 36 | uses: actions/upload-artifact@v3 37 | with: 38 | name: aarch64-oku.osx 39 | path: ./target/aarch64-apple-darwin/release/aarch64-oku.osx.tar 40 | if-no-files-found: error 41 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/folder-remote-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/browser_pages/home.vox: -------------------------------------------------------------------------------- 1 | --- 2 | layout = "home" 3 | permalink = "home.html" 4 | title = "Oku" 5 | --- 6 | 7 | 8 | 9 | {% markdown %} 10 | 11 | ### [User guide →]({{ global.site }}/guide) 12 | 13 | #### Make & share your own sites. 14 | 15 | - Oku lets you create [*replicas*]({{ global.site }}/guide/Replicas.html), sites that live on your computer and are published online instantly. 16 | - You can share editing rights to your replicas with your friends and collaborate in real-time. 17 | 18 | #### Create your own invite-only social network. 19 | 20 | - Via [the *OkuNet*]({{ global.site }}/guide/OkuNet.html), you can share [*notes*]({{ global.site }}/guide/Notes.html) you take on pages you've seen. 21 | - Your feed shows notes from users you follow and whoever they follow; you're in control of what you see. 22 | 23 | #### Keep your searches on-device. 24 | 25 | - When your computer learns about new sites shared over the OkuNet, it can search through them. 26 | - Your searches are done on your computer, privately. 27 | 28 | --- 29 | 30 | ### Looking to contribute? 31 | 32 | Oku is free and open-source software, [accepting code contributions on GitHub]({{ global.browser_repository }}). 33 | 34 | Oku is supported by [donations from users like you]({{ global.sponsor_link }}). 35 | 36 | {% endmarkdown %} -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | # How has this been tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | **Test Configuration**: 24 | * Hardware: 25 | * Toolchain: 26 | 27 | # Checklist: 28 | 29 | - [ ] My code follows [the style guidelines of this project](https://doc.rust-lang.org/nightly/style-guide/) 30 | - [ ] I have performed a self-review of my own code 31 | - [ ] I have commented my code, particularly in hard-to-understand areas 32 | - [ ] I have made corresponding changes to the documentation 33 | - [ ] My changes generate no new warnings 34 | - [ ] Any dependent changes have been merged 35 | -------------------------------------------------------------------------------- /.github/workflows-old/lint_code.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Lint codebase 7 | 8 | jobs: 9 | code_lint: 10 | name: Lint codebase 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - name: Checkout codebase 14 | uses: actions/checkout@v3 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | - name: Install development dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install -qq gtk3.0 gtk2.0 libgtk-3-dev libgtk2.0-dev libglib2.0-dev glade libsoup-gnome2.4-dev libwebkit2gtk-4.0-dev > /dev/null 21 | - name: Setup Rust toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: nightly 25 | default: true 26 | profile: minimal 27 | components: clippy, rustfmt 28 | - name: Correct code files 29 | uses: actions-rs/cargo@v1 30 | with: 31 | command: clippy 32 | args: --fix -Z unstable-options 33 | - name: Commit changes to code, if any 34 | run: | 35 | git config --global user.name 'Oku' 36 | git config --global user.email 'OkuBrowser@users.noreply.github.com' 37 | git diff --quiet && git diff --staged --quiet || git commit -am "Automatically approved suggested code corrections by linter" 38 | git push -------------------------------------------------------------------------------- /.github/workflows-old/coverage.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Code coverage 4 | 5 | jobs: 6 | codecovio: 7 | name: Code coverage 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v3 12 | 13 | - name: Install development dependencies 14 | run: | 15 | sudo apt-get update 16 | sudo apt-get install -qq gtk3.0 gtk2.0 libgtk-3-dev libgtk2.0-dev libglib2.0-dev glade libsoup-gnome2.4-dev libwebkit2gtk-4.0-dev > /dev/null 17 | 18 | - name: Install nightly toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: nightly 22 | default: true 23 | 24 | - name: Run cargo-tarpaulin 25 | uses: actions-rs/tarpaulin@v0.1 26 | with: 27 | args: '--out Lcov' 28 | 29 | - name: Upload to codecov.io 30 | uses: codecov/codecov-action@v3.1.1 31 | with: 32 | token: ${{secrets.CODECOV_TOKEN}} 33 | 34 | - name: Upload to Coveralls 35 | uses: coverallsapp/github-action@master 36 | with: 37 | github-token: ${{ secrets.GITHUB_TOKEN }} 38 | path-to-lcov: './lcov.info' 39 | 40 | - name: Archive code coverage results 41 | uses: actions/upload-artifact@v3 42 | with: 43 | name: code-coverage-report 44 | path: cobertura.xml -------------------------------------------------------------------------------- /src/scheme_handlers/hive.rs: -------------------------------------------------------------------------------- 1 | use super::{hive_path::HivePath, util::SchemeRequest}; 2 | use crate::NODE; 3 | use bytes::Bytes; 4 | use webkit2gtk::functions::uri_for_display; 5 | 6 | pub async fn node_scheme(request: SchemeRequest) { 7 | let bytes_result = node_scheme_handler(request.clone()).await; 8 | request.finish(bytes_result); 9 | } 10 | 11 | pub async fn node_scheme_handler(request: SchemeRequest) -> miette::Result> { 12 | let request_uri = request.uri().ok_or(miette::miette!( 13 | "Could read request URI ({:?}) … ", 14 | request.uri() 15 | ))?; 16 | let decoded_url = uri_for_display(&request_uri) 17 | .ok_or(miette::miette!( 18 | "Could display request URI safely ({}) … ", 19 | request_uri 20 | ))? 21 | .replacen("hive://", "", 1); 22 | let url_path = HivePath::parse(decoded_url)?; 23 | let node = NODE 24 | .get() 25 | .ok_or(miette::miette!("Oku node has not yet started … "))?; 26 | match url_path { 27 | HivePath::ByTicket(ticket, replica_path) => node 28 | .fetch_file_with_ticket(&ticket, &replica_path, &None) 29 | .await 30 | .map_err(|e| miette::miette!("{}", e)), 31 | HivePath::ById(namespace_id, replica_path) => node 32 | .fetch_file(&namespace_id, &replica_path, &None) 33 | .await 34 | .map_err(|e| miette::miette!("{}", e)), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'oku'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=oku", 15 | "--package=oku" 16 | ], 17 | "filter": { 18 | "name": "oku", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}" 24 | }, 25 | { 26 | "type": "lldb", 27 | "request": "launch", 28 | "name": "Debug unit tests in executable 'oku'", 29 | "cargo": { 30 | "args": [ 31 | "test", 32 | "--no-run", 33 | "--bin=oku", 34 | "--package=oku" 35 | ], 36 | "filter": { 37 | "name": "oku", 38 | "kind": "bin" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.github/workflows-old/shiftleft_analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow integrates Scan with GitHub's code scanning feature 2 | # Scan is a free open-source security tool for modern DevOps teams from ShiftLeft 3 | # Visit https://slscan.io/en/latest/integrations/code-scan for help 4 | name: SL Scan 5 | 6 | # This section configures the trigger for the workflow. Feel free to customize depending on your convention 7 | on: push 8 | 9 | jobs: 10 | Scan-Build: 11 | name: Scan 12 | # Scan runs on ubuntu, mac and windows 13 | runs-on: ubuntu-20.04 14 | steps: 15 | - uses: actions/checkout@v3 16 | # Instructions 17 | # 1. Setup JDK, Node.js, Python etc depending on your project type 18 | # 2. Compile or build the project before invoking scan 19 | # Example: mvn compile, or npm install or pip install goes here 20 | # 3. Invoke Scan with the github token. Leave the workspace empty to use relative url 21 | 22 | - name: Perform Scan 23 | uses: ShiftLeftSecurity/scan-action@master 24 | env: 25 | WORKSPACE: "" 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | SCAN_AUTO_BUILD: true 28 | with: 29 | output: reports 30 | # Scan auto-detects the languages in your project. To override uncomment the below variable and set the type 31 | # type: credscan,java 32 | # type: python 33 | 34 | - name: Upload report 35 | uses: github/codeql-action/upload-sarif@v2 36 | with: 37 | sarif_file: reports 38 | -------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/search.rs: -------------------------------------------------------------------------------- 1 | use super::core::OkuNetProvider; 2 | use oku_fs::database::core::OkuDatabase; 3 | use vox::provider::VoxProvider; 4 | 5 | impl OkuNetProvider { 6 | pub fn get_search_frontmatter(&self, query: &str) -> miette::Result { 7 | let mut table = toml::Table::new(); 8 | table.insert("layout".into(), "default".into()); 9 | table.insert("depends".into(), vec!["search"].into()); 10 | table.insert("permalink".into(), "search".into()); 11 | table.insert("title".into(), query.into()); 12 | Ok(table) 13 | } 14 | 15 | pub fn create_search_page(&self, query: &str) -> miette::Result<()> { 16 | let table = self.get_search_frontmatter(query)?; 17 | let page_contents = format!( 18 | "--- 19 | {} 20 | --- 21 | {{% if search[0] %}} 22 | {{% include search.voxs posts = search %}} 23 | {{% else %}} 24 | {{% include search.voxs posts = \"\" %}} 25 | {{% endif %}} 26 | ", 27 | table 28 | ); 29 | self.0.write_file("search.vox", page_contents)?; 30 | Ok(()) 31 | } 32 | pub async fn search(&self, query: String) -> miette::Result { 33 | let search_results = OkuDatabase::search_posts(&query, &None)?; 34 | for post in search_results.iter() { 35 | self.create_post_page(&post.user(), post, None).await?; 36 | } 37 | self.create_search_page(&query)?; 38 | self.render_and_get("output/search") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/entry-clear-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows-old/deb.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Package: Debian GNU + Linux (x86_64)' 4 | 5 | jobs: 6 | deb_x86-64: 7 | name: Debian GNU + Linux (x86_64) 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | sudo apt-get update > /dev/null 15 | sudo apt-get install -qq libgtk-4-0 libgtk-4-dev libglib2.0-dev > /dev/null 16 | git clone https://gitlab.gnome.org/GNOME/glib.git && cd glib > /dev/null 17 | meson _build && ninja -C _build 18 | sudo ninja -C _build install 19 | ./build-dependencies.sh 20 | - name: Setup Rust toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: nightly 24 | target: x86_64-unknown-linux-gnu 25 | default: true 26 | profile: minimal 27 | - name: Install 'cargo-deb' 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: install 31 | args: cargo-deb 32 | - name: Build & package Oku 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: deb 36 | args: --separate-debug-symbols -- --all-features --target x86_64-unknown-linux-gnu 37 | - name: Upload Oku build artifact to GitHub 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: x86_64-oku.deb.gnu+linux 41 | path: ./target/debian/oku_*_amd64.deb 42 | if-no-files-found: error -------------------------------------------------------------------------------- /.github/workflows-old/aarch64.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Build: GNU + Linux (ARM64)' 4 | 5 | jobs: 6 | linux_aarch64: 7 | name: GNU + Linux (ARM64) 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | sudo apt-get update > /dev/null 15 | sudo apt-get install -qq libgtk-4-0 libgtk-4-dev libglib2.0-dev > /dev/null 16 | git clone https://gitlab.gnome.org/GNOME/glib.git && cd glib > /dev/null 17 | meson _build && ninja -C _build 18 | sudo ninja -C _build install 19 | ./build-dependencies.sh 20 | - name: Setup Rust toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: nightly 24 | target: aarch64-unknown-linux-gnu 25 | default: true 26 | profile: minimal 27 | - name: Build Oku 28 | uses: actions-rs/cargo@v1 29 | with: 30 | cross: true 31 | command: build 32 | args: --release --all-features --target aarch64-unknown-linux-gnu 33 | - name: Prepare Oku for upload 34 | run: | 35 | cd ./target/aarch64-unknown-linux-gnu/release/ 36 | chmod +x ./oku 37 | tar -cvf aarch64-oku.gnu+linux.tar \ 38 | oku 39 | - name: Upload Oku build artifacts to GitHub 40 | uses: actions/upload-artifact@v3 41 | with: 42 | name: aarch64-oku.gnu+linux 43 | path: ./target/aarch64-unknown-linux-gnu/release/aarch64-oku.gnu+linux.tar 44 | if-no-files-found: error 45 | -------------------------------------------------------------------------------- /.github/workflows-old/x86_64.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Build: GNU + Linux (x86_64)' 4 | 5 | jobs: 6 | linux_x86-64: 7 | name: GNU + Linux (x86_64) 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout codebase 11 | uses: actions/checkout@v3 12 | - name: Install development dependencies 13 | run: | 14 | sudo apt-get update > /dev/null 15 | sudo apt-get install -qq libgtk-4-0 libgtk-4-dev libglib2.0-dev > /dev/null 16 | git clone https://gitlab.gnome.org/GNOME/glib.git && cd glib > /dev/null 17 | meson _build && ninja -C _build 18 | sudo ninja -C _build install 19 | ./build-dependencies.sh 20 | - name: Setup Rust toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: nightly 24 | target: x86_64-unknown-linux-gnu 25 | default: true 26 | profile: minimal 27 | - name: Build Oku 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: build 31 | args: --release --all-features --target x86_64-unknown-linux-gnu 32 | - name: Prepare Oku for upload 33 | run: | 34 | cd ./target/x86_64-unknown-linux-gnu/release/ 35 | strip -v --strip-all ./oku 36 | chmod +x ./oku 37 | tar -cvf x86_64-oku.gnu+linux.tar \ 38 | oku 39 | - name: Upload Oku build artifacts to GitHub 40 | uses: actions/upload-artifact@v3 41 | with: 42 | name: x86_64-oku.gnu+linux 43 | path: ./target/x86_64-unknown-linux-gnu/release/x86_64-oku.gnu+linux.tar 44 | if-no-files-found: error -------------------------------------------------------------------------------- /.github/workflows-old/flatpak.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master] 4 | pull_request: 5 | name: 'Package: Flatpak for GNU + Linux (x86_64)' 6 | jobs: 7 | flatpak-builder: 8 | name: GNU + Linux (x86_64) 9 | runs-on: ubuntu-20.04 10 | container: 11 | image: bilelmoussaoui/flatpak-github-actions:gnome-nightly 12 | options: --privileged 13 | steps: 14 | - name: Checkout codebase 15 | uses: actions/checkout@v3 16 | - name: Setup Rust toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | target: x86_64-unknown-linux-gnu 21 | default: true 22 | profile: minimal 23 | - name: Prepare for building 24 | run: | 25 | sudo dnf -q -y install gcc gcc-c++ gtk4 gtk4-devel clutter-devel libgda-devel gobject-introspection-devel nghttp2 gnutls openssl openssl-devel perl-JSON-PP git-all cmake meson ninja-build ruby > /dev/null 26 | ./build-dependencies.sh 27 | cargo update 28 | curl -s https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py --output flatpak-cargo-generator.py > /dev/null 29 | pip3 install --user --upgrade --quiet setuptools 30 | pip3 install --user --quiet siphash toml aiohttp 31 | python3 ./flatpak-cargo-generator.py ./Cargo.lock -o ./build-aux/cargo-sources.json 32 | cp -avr ./data/hicolor /usr/share/icons/hicolor 33 | - name: Build & package Oku 34 | uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v5 35 | with: 36 | bundle: "oku.flatpak" 37 | manifest-path: "build-aux/io.github.OkuBrowser.oku.json" 38 | -------------------------------------------------------------------------------- /src/scheme_handlers/hive_path.rs: -------------------------------------------------------------------------------- 1 | use oku_fs::iroh_docs::{DocTicket, NamespaceId}; 2 | use std::{path::PathBuf, str::FromStr}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum HivePath { 6 | ByTicket(Box, PathBuf), 7 | ById(NamespaceId, PathBuf), 8 | } 9 | 10 | impl HivePath { 11 | pub fn parse(path: impl AsRef) -> miette::Result { 12 | let url_components: Vec<_> = path 13 | .as_ref() 14 | .components() 15 | .map(|x| PathBuf::from(x.as_os_str())) 16 | .collect(); 17 | let first_component = url_components.first().ok_or(miette::miette!( 18 | "{:?} does not contain a replica ID or ticket … ", 19 | path.as_ref() 20 | ))?; 21 | let second_component = url_components.get(1); 22 | let replica_path = second_component 23 | .and_then(|_x| path.as_ref().strip_prefix(first_component.clone()).ok()) 24 | .map(|x| PathBuf::from("/").join(x)) 25 | .unwrap_or("/".into()); 26 | if let Ok(ticket) = DocTicket::from_str(&first_component.to_string_lossy()) { 27 | Ok(Self::ByTicket(Box::new(ticket), replica_path)) 28 | } else if let Ok(namespace_id_bytes) = 29 | oku_fs::fs::util::parse_array_hex_or_base32::<32>(&first_component.to_string_lossy()) 30 | { 31 | Ok(Self::ById( 32 | NamespaceId::from(namespace_id_bytes), 33 | replica_path, 34 | )) 35 | } else { 36 | Err(miette::miette!( 37 | "{:?} does not contain a replica ID or ticket … ", 38 | path.as_ref() 39 | )) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/keyboard-shortcuts-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/folder-open-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Oku 2 | 3 | #### **Did you find a bug?** 4 | 5 | * **Do not open up a GitHub issue if the bug is a security vulnerability 6 | in Oku**, and instead to refer to our [security policy](https://github.com/OkuBrowser/oku/blob/master/SECURITY.md). 7 | 8 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/OkuBrowser/oku/issues). 9 | 10 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/OkuBrowser/oku/issues/new/choose). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 11 | 12 | #### **Did you write a patch that fixes a bug?** 13 | 14 | * Open a new GitHub pull request with the patch. 15 | 16 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable. 17 | 18 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 19 | 20 | Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Oku will generally not be accepted (read more about [the rationale behind this, from the Ruby on Rails project](https://github.com/rails/rails/pull/13771#issuecomment-32746700)). 21 | 22 | #### **Do you intend to add a new feature or change an existing one?** 23 | 24 | * If someone hasn't already suggested the same thing, [open a new issue](https://github.com/OkuBrowser/oku/issues/new/choose) and start writing code. 25 | 26 | Oku is a volunteer effort. We encourage you to pitch in and join [The Team](https://github.com/OkuBrowser/oku/graphs/contributors)! 27 | 28 | Merci! 29 | 30 | The Team 31 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/screen-privacy7-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/scheme_handlers/view_source.rs: -------------------------------------------------------------------------------- 1 | use super::util::SchemeRequest; 2 | use crate::vox_providers::oku_provider::core::OkuProvider; 3 | use bytes::Bytes; 4 | use miette::IntoDiagnostic; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | use webkit2gtk::prelude::WebViewExt; 9 | 10 | pub struct SendFuture(Pin>>); 11 | unsafe impl Send for SendFuture {} 12 | impl Future for SendFuture { 13 | type Output = T; 14 | fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { 15 | self.0.as_mut().poll(context) 16 | } 17 | } 18 | 19 | #[derive(Clone)] 20 | pub struct Resource(pub webkit2gtk::WebResource); 21 | unsafe impl Send for Resource {} 22 | unsafe impl Sync for Resource {} 23 | impl Resource { 24 | pub async fn data(&self) -> SendFuture, glib::Error>> { 25 | SendFuture(Box::pin(self.0.data_future())) 26 | } 27 | } 28 | 29 | pub async fn view_source_scheme(request: SchemeRequest) { 30 | let bytes_result = view_source_scheme_handler(request.clone()).await; 31 | request.finish(bytes_result); 32 | } 33 | 34 | pub async fn view_source_scheme_handler( 35 | request: SchemeRequest, 36 | ) -> miette::Result> { 37 | let web_view = request.0.web_view().ok_or(miette::miette!(""))?; 38 | let resource = Resource( 39 | web_view 40 | .main_resource() 41 | .ok_or(miette::miette!("No resource loaded to view source of … "))?, 42 | ); 43 | let data = glib::spawn_future(resource.data().await) 44 | .await 45 | .map_err(|e| miette::miette!("{}", e))? 46 | .map_err(|e| miette::miette!("{}", e))?; 47 | let html = std::str::from_utf8(&data).into_diagnostic()?.to_string(); 48 | let uri = request.uri().unwrap_or_default(); 49 | OkuProvider::new().view_source(html, uri) 50 | } 51 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/settings-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows-old/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Publish documentation to GitHub Pages 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-20.04 6 | name: "Publish documentation" 7 | steps: 8 | - name: Install development dependencies 9 | run: | 10 | sudo apt-get update 11 | sudo apt-get install -qq gtk3.0 gtk2.0 libgtk-3-dev libgtk2.0-dev libglib2.0-dev glade libsoup-gnome2.4-dev libwebkit2gtk-4.0-dev > /dev/null 12 | - name: Setup Rust toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | target: x86_64-unknown-linux-gnu 17 | default: true 18 | profile: default 19 | - name: Checkout codebase 20 | uses: actions/checkout@v3 21 | with: 22 | path: ./oku 23 | - name: Checkout GitHub Pages environment 24 | uses: actions/checkout@v3 25 | with: 26 | ref: gh-pages 27 | path: ./gh-pages 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Generate documentation 30 | run: | 31 | sudo apt-get -qq install tree > /dev/null 32 | printf "Codebase:\n" && tree ./oku 33 | rm -rf ./gh-pages/code/ 34 | 35 | cd ./oku 36 | printf "\nGenerating documentation … " 37 | time cargo doc --no-deps --document-private-items --release --quiet 38 | cd ../ 39 | 40 | mkdir -p ./gh-pages/code 41 | cp -ar ./oku/target/doc/* ./gh-pages/code 42 | printf "\nDocumentation:\n" && tree ./gh-pages/code 43 | - name: Publish 44 | run: | 45 | cd ./gh-pages 46 | git config --global user.name 'Oku' 47 | git config --global user.email 'OkuBrowser@users.noreply.github.com' 48 | git add -A 49 | git diff --quiet && git diff --staged --quiet || git commit -am "Publish documentation to GitHub Pages" 50 | git push 51 | -------------------------------------------------------------------------------- /.github/workflows-old/rpm.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Package: RPM for GNU + Linux (x86_64)' 4 | 5 | jobs: 6 | rpm_x86-64: 7 | name: Fedora GNU + Linux (x86_64) 8 | runs-on: ubuntu-20.04 9 | container: 10 | image: fedora:latest 11 | volumes: 12 | - /proc:/proc 13 | - /sys/fs/cgroup/systemd/actions_job:/sys/fs/cgroup/systemd/actions_job 14 | - /sys/fs/cgroup:/sys/fs/cgroup 15 | options: --privileged 16 | steps: 17 | - name: Checkout codebase 18 | uses: actions/checkout@v3 19 | - name: Install compiler toolchain 20 | run: | 21 | sudo dnf -q -y install gcc rpm-build > /dev/null 22 | - name: Install development dependencies 23 | run: | 24 | sudo dnf -q -y install gcc gcc-c++ gtk4 gtk4-devel clutter-devel libgda-devel gobject-introspection-devel nghttp2 openssl openssl-devel perl-JSON-PP git-all cmake meson ninja-build > /dev/null 25 | ./build-dependencies.sh 26 | - name: Setup Rust toolchain 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: nightly 30 | target: x86_64-unknown-linux-gnu 31 | default: true 32 | profile: minimal 33 | - name: Install 'cargo-rpm' 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: install 37 | args: cargo-rpm 38 | - name: Generate RPM package specification 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: rpm 42 | args: init 43 | - name: Build & package Oku 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: rpm 47 | args: build -v 48 | - name: Upload Oku build artifact to GitHub 49 | uses: actions/upload-artifact@v3 50 | with: 51 | name: x86_64-oku.rpm.gnu+linux 52 | path: ./target/release/rpmbuild/RPMS/x86_64/oku-*.x86_64.rpm 53 | if-no-files-found: error -------------------------------------------------------------------------------- /.github/workflows-old/windows.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: 'Build: Windows (x86_64)' 4 | 5 | jobs: 6 | windows_x86-64: 7 | name: Windows (x86_64) 8 | runs-on: windows-latest 9 | defaults: 10 | run: 11 | shell: msys2 {0} 12 | steps: 13 | - name: "MINGW64: Install, setup, and switch to" 14 | uses: msys2/setup-msys2@v2 15 | with: 16 | update: true 17 | install: >- 18 | git 19 | base-devel 20 | glib2 21 | glib2-devel 22 | mingw-w64-x86_64-toolchain 23 | mingw-w64-x86_64-gtk3 24 | mingw-w64-x86_64-gtk2 25 | mingw-w64-x86_64-glib2 26 | mingw-w64-x86_64-pkg-config 27 | - name: Setup environment variables 28 | run: | 29 | echo "../../_temp/msys/msys64/mingw64/bin" >> $GITHUB_PATH 30 | LIBRARY_PATH="../../_temp/msys/msys64/mingw64/bin" 31 | GTK_LIB_DIR="../../_temp/msys/msys64/mingw64/bin" PKG_CONFIG_ALLOW_CROSS=1 32 | PKG_CONFIG="../../_temp/msys/msys64/mingw64/bin" PKG_CONFIG_ALLOW_CROSS=1 33 | source ~/.bashrc 34 | - name: Checkout codebase 35 | uses: actions/checkout@v3 36 | - name: Setup Rust toolchain 37 | uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: nightly 40 | target: x86_64-pc-windows-gnu 41 | default: true 42 | profile: minimal 43 | - name: Build Oku 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: build 47 | args: --release --all-features --target x86_64-pc-windows-gnu 48 | - name: Prepare Oku for upload 49 | run: | 50 | cd ./target/x86_64-pc-windows-gnu/release/ 51 | tar -czvf x86_64-oku.win32.zip ` 52 | oku.exe 53 | - name: Upload Oku build artifacts to GitHub 54 | uses: actions/upload-artifact@v3 55 | with: 56 | name: x86_64-oku.win32 57 | path: ./target/x86_64-pc-windows-gnu/release/x86_64-oku.win32.zip 58 | if-no-files-found: error 59 | -------------------------------------------------------------------------------- /generate_logo_png.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | inkscape --export-type=png -o data/hicolor/16x16/apps/io.github.OkuBrowser.oku.png -w 16 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 3 | inkscape --export-type=png -o data/hicolor/22x22/apps/io.github.OkuBrowser.oku.png -w 22 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 4 | inkscape --export-type=png -o data/hicolor/24x24/apps/io.github.OkuBrowser.oku.png -w 24 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 5 | inkscape --export-type=png -o data/hicolor/32x32/apps/io.github.OkuBrowser.oku.png -w 32 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 6 | inkscape --export-type=png -o data/hicolor/36x36/apps/io.github.OkuBrowser.oku.png -w 36 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 7 | inkscape --export-type=png -o data/hicolor/40x40/apps/io.github.OkuBrowser.oku.png -w 40 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 8 | inkscape --export-type=png -o data/hicolor/48x48/apps/io.github.OkuBrowser.oku.png -w 48 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 9 | inkscape --export-type=png -o data/hicolor/64x64/apps/io.github.OkuBrowser.oku.png -w 64 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 10 | inkscape --export-type=png -o data/hicolor/72x72/apps/io.github.OkuBrowser.oku.png -w 72 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 11 | inkscape --export-type=png -o data/hicolor/96x96/apps/io.github.OkuBrowser.oku.png -w 96 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 12 | inkscape --export-type=png -o data/hicolor/128x128/apps/io.github.OkuBrowser.oku.png -w 128 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 13 | inkscape --export-type=png -o data/hicolor/192x192/apps/io.github.OkuBrowser.oku.png -w 192 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 14 | inkscape --export-type=png -o data/hicolor/256x256/apps/io.github.OkuBrowser.oku.png -w 256 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 15 | inkscape --export-type=png -o data/hicolor/384x384/apps/io.github.OkuBrowser.oku.png -w 384 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg 16 | inkscape --export-type=png -o data/hicolor/512x512/apps/io.github.OkuBrowser.oku.png -w 512 data/hicolor/scalable/apps/io.github.OkuBrowser.oku.svg -------------------------------------------------------------------------------- /src/scheme_handlers/ipfs.rs: -------------------------------------------------------------------------------- 1 | use super::util::SchemeRequest; 2 | use bytes::Bytes; 3 | use futures::pin_mut; 4 | use ipfs::Ipfs; 5 | use miette::IntoDiagnostic; 6 | use tokio_stream::StreamExt; 7 | use webkit2gtk::functions::uri_for_display; 8 | 9 | pub async fn ipfs_scheme(ipfs: &Ipfs, request: SchemeRequest) { 10 | let bytes_result = ipfs_scheme_handler(ipfs, request.clone()).await; 11 | request.finish(bytes_result); 12 | } 13 | 14 | pub async fn ipns_scheme(ipfs: &Ipfs, request: SchemeRequest) { 15 | let bytes_result = ipns_scheme_handler(ipfs, request.clone()).await; 16 | request.finish(bytes_result); 17 | } 18 | 19 | pub async fn ipfs_scheme_handler( 20 | ipfs: &Ipfs, 21 | request: SchemeRequest, 22 | ) -> miette::Result> { 23 | let request_uri = request.uri().ok_or(miette::miette!( 24 | "Could read request URI ({:?}) … ", 25 | request.uri() 26 | ))?; 27 | let decoded_url = uri_for_display(&request_uri) 28 | .ok_or(miette::miette!( 29 | "Could display request URI safely ({}) … ", 30 | request_uri 31 | ))? 32 | .replacen("ipfs://", "", 1) 33 | .parse::() 34 | .map_err(|e| miette::miette!("{}", e))?; 35 | cat_unixfs(ipfs, decoded_url).await 36 | } 37 | 38 | pub async fn ipns_scheme_handler( 39 | ipfs: &Ipfs, 40 | request: SchemeRequest, 41 | ) -> miette::Result> { 42 | let request_uri = request.uri().ok_or(miette::miette!( 43 | "Could read request URI ({:?}) … ", 44 | request.uri() 45 | ))?; 46 | let uri_for_display = uri_for_display(&request_uri).ok_or(miette::miette!( 47 | "Could display request URI safely ({}) … ", 48 | request_uri 49 | ))?; 50 | let decoded_url = format!("/ipns/{}", uri_for_display.replacen("ipns://", "", 1)) 51 | .parse::() 52 | .map_err(|e| miette::miette!("{}", e))?; 53 | cat_unixfs(ipfs, decoded_url).await 54 | } 55 | 56 | pub async fn cat_unixfs(ipfs: &Ipfs, path: ipfs::IpfsPath) -> miette::Result> { 57 | let ipfs_stream = ipfs.cat_unixfs(path); 58 | let mut bytes_vec: Vec = vec![]; 59 | pin_mut!(ipfs_stream); 60 | while let Some(bytes_result) = ipfs_stream.next().await { 61 | bytes_vec.extend(bytes_result.into_diagnostic()?) 62 | } 63 | Ok(bytes_vec) 64 | } 65 | -------------------------------------------------------------------------------- /data/hicolor/scalable/actions/globe-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/scheme_handlers/util.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | hive::node_scheme, 3 | ipfs::{ipfs_scheme, ipns_scheme}, 4 | oku::oku_scheme, 5 | view_source::view_source_scheme, 6 | }; 7 | use bytes::Bytes; 8 | use ipfs::Ipfs; 9 | use log::error; 10 | use tokio::runtime::Handle; 11 | use webkit2gtk::URISchemeRequest; 12 | 13 | pub enum RequestScheme { 14 | Oku, 15 | Hive, 16 | Ipfs, 17 | Ipns, 18 | ViewSource, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct SchemeRequest(pub URISchemeRequest); 23 | unsafe impl Send for SchemeRequest {} 24 | unsafe impl Sync for SchemeRequest {} 25 | impl SchemeRequest { 26 | pub fn uri(&self) -> Option { 27 | self.0.uri().map(|uri| uri.to_string()) 28 | } 29 | pub fn http_method(&self) -> Option { 30 | self.0 31 | .http_method() 32 | .map(|http_method| http_method.to_string()) 33 | } 34 | pub fn finish(&self, bytes_result: miette::Result>) { 35 | match bytes_result { 36 | Ok(bytes) => { 37 | let byte_vec = bytes.into().to_vec(); 38 | let bytes_size = byte_vec.len(); 39 | let content_type = tree_magic_mini::from_u8(&byte_vec); 40 | let mem_stream = 41 | gio::MemoryInputStream::from_bytes(&glib::Bytes::from_owned(byte_vec)); 42 | self.0.finish( 43 | &mem_stream, 44 | bytes_size.try_into().unwrap_or(-1), 45 | Some(content_type), 46 | ); 47 | } 48 | Err(e) => { 49 | error!("{}", e); 50 | self.0.finish_error(&mut glib::error::Error::new( 51 | webkit2gtk::NetworkError::Failed, 52 | &e.to_string(), 53 | )); 54 | } 55 | } 56 | } 57 | } 58 | 59 | pub fn handle_request(ipfs: Ipfs, request: SchemeRequest, request_scheme: RequestScheme) { 60 | let handle = Handle::current(); 61 | std::thread::spawn(move || { 62 | handle.block_on(handle_request_tokio(&ipfs, request.clone(), request_scheme)); 63 | }); 64 | } 65 | 66 | pub async fn handle_request_tokio( 67 | ipfs: &Ipfs, 68 | request: SchemeRequest, 69 | request_scheme: RequestScheme, 70 | ) { 71 | match request_scheme { 72 | RequestScheme::Hive => node_scheme(request).await, 73 | RequestScheme::Oku => oku_scheme(request).await, 74 | RequestScheme::Ipfs => ipfs_scheme(ipfs, request).await, 75 | RequestScheme::Ipns => ipns_scheme(ipfs, request).await, 76 | RequestScheme::ViewSource => view_source_scheme(request).await, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Generated by Cargo 5 | # will have compiled files and executables 6 | debug/ 7 | target/ 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Generated by Glade 13 | src/*.glade~ 14 | src/#*.glade# 15 | src/*.ui~ 16 | src/#*.ui# 17 | 18 | # Generated by flatpak-builder 19 | .flatpak-builder 20 | repo 21 | build 22 | builddir 23 | 24 | # These are files used for local Flatpak creation 25 | flatpak-cargo-generator.py 26 | build-aux/cargo-sources.json 27 | 28 | # Generated when building WebKit 29 | gtk4-dependencies 30 | 31 | ## JetBrains 32 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 33 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 34 | 35 | # User-specific stuff 36 | .idea/**/workspace.xml 37 | .idea/**/tasks.xml 38 | .idea/**/usage.statistics.xml 39 | .idea/**/dictionaries 40 | .idea/**/shelf 41 | 42 | # AWS User-specific 43 | .idea/**/aws.xml 44 | 45 | # Generated files 46 | .idea/**/contentModel.xml 47 | 48 | # Sensitive or high-churn files 49 | .idea/**/dataSources/ 50 | .idea/**/dataSources.ids 51 | .idea/**/dataSources.local.xml 52 | .idea/**/sqlDataSources.xml 53 | .idea/**/dynamic.xml 54 | .idea/**/uiDesigner.xml 55 | .idea/**/dbnavigator.xml 56 | 57 | # Gradle 58 | .idea/**/gradle.xml 59 | .idea/**/libraries 60 | 61 | # Gradle and Maven with auto-import 62 | # When using Gradle or Maven with auto-import, you should exclude module files, 63 | # since they will be recreated, and may cause churn. Uncomment if using 64 | # auto-import. 65 | # .idea/artifacts 66 | # .idea/compiler.xml 67 | # .idea/jarRepositories.xml 68 | # .idea/modules.xml 69 | # .idea/*.iml 70 | # .idea/modules 71 | # *.iml 72 | # *.ipr 73 | 74 | # CMake 75 | cmake-build-*/ 76 | 77 | # Mongo Explorer plugin 78 | .idea/**/mongoSettings.xml 79 | 80 | # File-based project format 81 | *.iws 82 | 83 | # IntelliJ 84 | out/ 85 | 86 | # mpeltonen/sbt-idea plugin 87 | .idea_modules/ 88 | 89 | # JIRA plugin 90 | atlassian-ide-plugin.xml 91 | 92 | # Cursive Clojure plugin 93 | .idea/replstate.xml 94 | 95 | # SonarLint plugin 96 | .idea/sonarlint/ 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | 104 | # Editor-based Rest Client 105 | .idea/httpRequests 106 | 107 | # Android studio 3.1+ serialized cache file 108 | .idea/caches/build_file_checksums.ser 109 | 110 | # Development 111 | tree.txt 112 | target-old 113 | test.rs 114 | test/ 115 | .oku 116 | oku.flatpak 117 | resources.gresource 118 | src/browser_pages/output/ 119 | *_old.rs 120 | *-keyspace 121 | .fastembed_cache 122 | .idea 123 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | Note: Currently, the Oku browser is only available on operating systems using the Linux kernel. 4 | The [protocol included with Oku](https://github.com/OkuBrowser/oku-fs) may be used via a command-line frontend, available on Linux, macOS, and Windows. 5 | 6 | ## Binary 7 | 8 | ### Prerequisites 9 | 10 | To build, please install: 11 | * A copy of the [Rust toolchain](https://www.rust-lang.org/tools/install) 12 | * It is recommended that you install Rust using [`rustup.rs`](https://rustup.rs/), though many Linux distributions also package the Rust toolchain as well. 13 | * [GTK and its dependencies](https://www.gtk.org/docs/installations/linux), including GDK, GLib, and Pango 14 | * It is recommended that you obtain these development packages from your distribution. 15 | * [WebKitGTK](https://webkitgtk.org/) 16 | * Some distributions provide multiple versions of WebKitGTK; look for packages resembling `webkitgtk-6.0`. 17 | * [`libfuse`](https://github.com/libfuse/libfuse/) 18 | * It is recommended that you obtain this development package from your distribution. 19 | 20 | ### Commands 21 | 22 | Note: Before new builds, please run `./prebuild.sh` to complete necessary pre-compilation tasks. 23 | 24 | After pre-requisites are installed and pre-compilation tasks are complete, you may run: 25 | * `cargo build` for debug builds. 26 | * `cargo build --release` for release builds. 27 | * `cargo install --path .` to install Oku. 28 | 29 | ### Troubleshooting 30 | 31 | #### Missing Icon 32 | 33 | If the browser's icon is missing at runtime, copy the icon files to the appropriate location on your system (eg, `cp -avr ./data/hicolor /app/share/icons/`). 34 | 35 | #### Broken Replica Mount After Crash 36 | 37 | If the browser has been restarted after a crash and replicas are now inaccessible in the file manager, try running `umount ~/.local/share/oku/mount`. 38 | 39 | ## Flatpak 40 | 41 | ### Prerequisites 42 | 43 | In addition to the prerequisites above, the Flatpak build requires: 44 | 45 | * [Flatpak](https://flatpak.org/setup/) 46 | * `flatpak-builder` 47 | * Run `flatpak install org.flatpak.Builder` to install. 48 | 49 | ### Commands 50 | 51 | Run `./install_flatpak.sh`, assuming prerequisites are installed. 52 | This will output `oku.flatpak` and install the Flatpak. 53 | 54 | ### Troubleshooting 55 | 56 | #### Not Opening 57 | 58 | Try starting Oku from the command-line by running `flatpak run io.github.OkuBrowser.oku`. 59 | This should reveal any runtime issues. 60 | 61 | #### Crashing Upon Start with Ubuntu 62 | 63 | See [the following](https://github.com/OkuBrowser/oku/issues/290#issuecomment-2380092489) for a workaround. 64 | 65 | #### Broken Replica Mount After Crash 66 | 67 | If the browser has been restarted after a crash and replicas are now inaccessible in the file manager, try running `umount ~/.var/app/io.github.OkuBrowser.oku/data/oku/mount`. -------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/tags.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use super::core::OkuNetProvider; 4 | use crate::NODE; 5 | use oku_fs::database::posts::core::OkuPost; 6 | use rayon::iter::FromParallelIterator; 7 | use vox::provider::VoxProvider; 8 | 9 | impl OkuNetProvider { 10 | pub async fn get_tag_frontmatter( 11 | &self, 12 | tag: String, 13 | posts: Vec, 14 | ) -> miette::Result { 15 | let mut tag_post_frontmatter: Vec = Vec::new(); 16 | for post in posts.iter() { 17 | if let Ok(post_frontmatter) = self.get_post_frontmatter(&post.user(), post).await { 18 | tag_post_frontmatter.push(post_frontmatter); 19 | } 20 | } 21 | let mut table = toml::Table::new(); 22 | table.insert("layout".into(), "default".into()); 23 | table.insert("depends".into(), vec![tag.clone()].into()); 24 | table.insert("permalink".into(), format!("tag/{}", tag).into()); 25 | table.insert("title".into(), tag.into()); 26 | table.insert("posts".into(), tag_post_frontmatter.into()); 27 | Ok(table) 28 | } 29 | pub async fn create_tag_page(&self, tag: String) -> miette::Result<()> { 30 | let node = NODE 31 | .get() 32 | .ok_or(miette::miette!("No running Oku node … "))?; 33 | let tag_posts = node 34 | .posts_with_tags( 35 | &Vec::from_par_iter(node.all_posts().await), 36 | &HashSet::from_par_iter(vec![tag.clone()]), 37 | ) 38 | .await; 39 | for post in tag_posts.iter() { 40 | self.create_post_page(&post.user(), post, Some(tag.clone())) 41 | .await?; 42 | } 43 | let page_path = format!("/tag/{}.vox", tag); 44 | let table = self.get_tag_frontmatter(tag.clone(), tag_posts).await?; 45 | let page_contents = format!( 46 | "--- 47 | {0} 48 | --- 49 | {{% if {1}[0] %}} 50 | {{% include tag.voxs posts = {1} %}} 51 | {{% else %}} 52 | {{% include tag.voxs posts = \"\" %}} 53 | {{% endif %}} 54 | ", 55 | table, tag 56 | ); 57 | self.0.write_file(page_path, page_contents)?; 58 | Ok(()) 59 | } 60 | 61 | pub async fn view_tag(&self, tag: String) -> miette::Result { 62 | self.create_tag_page(tag.clone()).await?; 63 | self.render_and_get(format!("output/tag/{}", tag)) 64 | } 65 | 66 | pub async fn view_tags(&self) -> miette::Result { 67 | let node = NODE 68 | .get() 69 | .ok_or(miette::miette!("No running Oku node … "))?; 70 | let tags = node.all_tags(&node.all_posts().await).await; 71 | for tag in tags { 72 | self.create_tag_page(tag.clone()).await?; 73 | } 74 | self.render_and_get("output/tags") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/vox_providers/oku_provider/core.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; 2 | use vox::{provider::VoxProvider, ram_provider::RamProvider}; 3 | 4 | pub static OKU_VOX_FILES: LazyLock> = LazyLock::new(|| { 5 | HashMap::from([ 6 | ( 7 | "global.toml".into(), 8 | include_str!("../../browser_pages/global.toml").into(), 9 | ), 10 | ( 11 | "home.vox".into(), 12 | include_str!("../../browser_pages/home.vox").into(), 13 | ), 14 | ( 15 | "layouts/view_source.vox".into(), 16 | include_str!("../../browser_pages/layouts/view_source.vox").into(), 17 | ), 18 | ( 19 | "layouts/default.vox".into(), 20 | include_str!("../../browser_pages/layouts/default.vox").into(), 21 | ), 22 | ( 23 | "layouts/home.vox".into(), 24 | include_str!("../../browser_pages/layouts/home.vox").into(), 25 | ), 26 | ( 27 | "snippets/head.html".into(), 28 | include_str!("../../browser_pages/snippets/head.html").into(), 29 | ), 30 | ( 31 | "snippets/highlight.min.js".into(), 32 | include_str!("../../browser_pages/snippets/highlight.min.js").into(), 33 | ), 34 | ( 35 | "snippets/highlightjs-line-numbers.min.js".into(), 36 | include_str!("../../browser_pages/snippets/highlightjs-line-numbers.min.js").into(), 37 | ), 38 | ( 39 | "snippets/hljs.default.min.css".into(), 40 | include_str!("../../browser_pages/snippets/hljs.default.min.css").into(), 41 | ), 42 | ( 43 | "snippets/logo.svg".into(), 44 | include_str!("../../browser_pages/snippets/logo.svg").into(), 45 | ), 46 | ( 47 | "snippets/normalise.css".into(), 48 | include_str!("../../browser_pages/snippets/normalise.css").into(), 49 | ), 50 | ( 51 | "snippets/style.css".into(), 52 | include_str!("../../browser_pages/snippets/style.css").into(), 53 | ), 54 | ]) 55 | }); 56 | 57 | #[derive(Debug, Clone)] 58 | pub struct OkuProvider(pub RamProvider); 59 | 60 | impl Default for OkuProvider { 61 | fn default() -> Self { 62 | Self::new() 63 | } 64 | } 65 | 66 | impl OkuProvider { 67 | pub fn new() -> Self { 68 | Self(RamProvider::new(Some(OKU_VOX_FILES.clone()))) 69 | } 70 | 71 | pub fn render_and_get(&self, path: impl AsRef) -> miette::Result { 72 | let parser = self.0.create_liquid_parser()?; 73 | let global = self.0.get_global_context()?; 74 | let (dag, _pages, _layouts) = self.0.generate_dag()?; 75 | let (_updated_pages, _updated_dag) = self.0.generate_site( 76 | parser.clone(), 77 | global.0.clone(), 78 | global.1, 79 | dag, 80 | false, 81 | false, 82 | )?; 83 | self.0.read_to_string(path) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/database/core.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::database::policy::PolicySettingRecord; 3 | use crate::{suggestion_item::SuggestionItem, DATA_DIR}; 4 | use miette::IntoDiagnostic; 5 | use native_db::*; 6 | use oku_fs::database::core::OkuDatabase; 7 | use std::{path::PathBuf, sync::LazyLock}; 8 | use webkit2gtk::FaviconDatabase; 9 | 10 | pub(crate) static DATABASE_PATH: LazyLock = 11 | LazyLock::new(|| DATA_DIR.join("OKU_DATABASE")); 12 | pub(crate) static DATABASE: LazyLock = 13 | LazyLock::new(|| BrowserDatabase::new().unwrap()); 14 | pub(crate) static MODELS: LazyLock = LazyLock::new(|| { 15 | let mut models = Models::new(); 16 | models.define::().unwrap(); 17 | models.define::().unwrap(); 18 | models.define::().unwrap(); 19 | models 20 | }); 21 | 22 | pub struct BrowserDatabase { 23 | pub(super) database: Database<'static>, 24 | pub history_sender: tokio::sync::watch::Sender<()>, 25 | pub bookmark_sender: tokio::sync::watch::Sender<()>, 26 | } 27 | 28 | impl BrowserDatabase { 29 | pub fn new() -> miette::Result { 30 | let database = Self { 31 | database: native_db::Builder::new() 32 | .create(&MODELS, &*DATABASE_PATH) 33 | .into_diagnostic()?, 34 | history_sender: tokio::sync::watch::channel(()).0, 35 | bookmark_sender: tokio::sync::watch::channel(()).0, 36 | }; 37 | if database.get_history_records()?.len() as u64 38 | != HISTORY_RECORD_INDEX_READER.searcher().num_docs() 39 | { 40 | database.rebuild_history_record_index()?; 41 | } 42 | if database.get_bookmarks()?.len() as u64 != BOOKMARK_INDEX_READER.searcher().num_docs() { 43 | database.rebuild_bookmark_index()?; 44 | } 45 | Ok(database) 46 | } 47 | 48 | pub fn search( 49 | &self, 50 | query_string: String, 51 | favicon_database: &FaviconDatabase, 52 | ) -> miette::Result> { 53 | let history_records = Self::search_history_records(query_string.clone(), None)?; 54 | let bookmarks = Self::search_bookmarks(query_string.clone(), None)?; 55 | let okunet_posts = OkuDatabase::search_posts(&query_string, &None)?; 56 | 57 | let history_record_suggestions: Vec<_> = history_records 58 | .into_iter() 59 | .map(|x| SuggestionItem::new(x.title.unwrap_or_default(), x.uri, favicon_database)) 60 | .collect(); 61 | let bookmark_suggestions = bookmarks 62 | .into_iter() 63 | .map(|x| SuggestionItem::new(x.title, x.url, favicon_database)) 64 | .collect(); 65 | let okunet_post_suggestions = okunet_posts 66 | .into_iter() 67 | .map(|x| SuggestionItem::new(x.note.title, x.note.url.to_string(), favicon_database)) 68 | .collect(); 69 | 70 | Ok([ 71 | history_record_suggestions, 72 | bookmark_suggestions, 73 | okunet_post_suggestions, 74 | ] 75 | .concat()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/browser_pages/snippets/highlightjs-line-numbers.min.js: -------------------------------------------------------------------------------- 1 | !function(r,o){"use strict";var e,i="hljs-ln",l="hljs-ln-line",h="hljs-ln-code",s="hljs-ln-numbers",c="hljs-ln-n",m="data-line-number",a=/\r\n|\r|\n/g;function u(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var o=parseInt(t.dataset.lineNumber),a=parseInt(r.dataset.lineNumber);if(o==a)return n;var i,l=t.textContent,s=r.textContent;for(a
{6}',[l,s,c,m,h,o+n.startFrom,0{1}',[i,r])}return e}(e.innerHTML,o)}function v(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r{1}\n',[n,0) 49 | @extends libadwaita::PreferencesDialog, libadwaita::Dialog, gtk::Widget; 50 | } 51 | 52 | pub fn apply_appearance_config( 53 | style_manager: &StyleManager, 54 | window: &crate::widgets::window::Window, 55 | ) { 56 | style_manager.set_color_scheme(window.imp().config.imp().colour_scheme().into()); 57 | let web_view = window.get_view(); 58 | window.update_color(&web_view, style_manager); 59 | } 60 | 61 | impl Settings { 62 | pub fn new(app: &libadwaita::Application, window: &crate::widgets::window::Window) -> Self { 63 | let this: Self = glib::Object::builder::().build(); 64 | this.set_title("Settings"); 65 | 66 | let style_manager = app.style_manager(); 67 | 68 | this.setup_main_page(&style_manager, window); 69 | 70 | this.set_visible(true); 71 | this.present(Some(window)); 72 | 73 | this 74 | } 75 | 76 | pub fn setup_main_page( 77 | &self, 78 | style_manager: &StyleManager, 79 | window: &crate::widgets::window::Window, 80 | ) { 81 | let imp = self.imp(); 82 | 83 | self.setup_appearance_group(style_manager, window); 84 | self.setup_okunet_group(); 85 | 86 | imp.main_page.add(&imp.appearance_group); 87 | imp.main_page.add(&imp.okunet_group); 88 | self.add(&imp.main_page); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /flathub/io.github.OkuBrowser.oku.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "io.github.OkuBrowser.oku", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "47", 5 | "sdk" : "org.gnome.Sdk", 6 | "sdk-extensions" : [ 7 | "org.freedesktop.Sdk.Extension.rust-stable" 8 | ], 9 | "command" : "oku", 10 | "finish-args" : [ 11 | "--share=network", 12 | "--share=ipc", 13 | "--socket=fallback-x11", 14 | "--socket=wayland", 15 | "--device=all", 16 | "--socket=pulseaudio", 17 | "--socket=cups", 18 | "--filesystem=host", 19 | "--filesystem=xdg-run/gvfs", 20 | "--filesystem=xdg-run/gvfsd", 21 | "--talk-name=org.freedesktop.Flatpak", 22 | "--talk-name=org.freedesktop.Notifications", 23 | "--talk-name=org.gtk.vfs.*" 24 | ], 25 | "cleanup": [ 26 | "/include", 27 | "/lib/pkgconfig", 28 | "/man", 29 | "/share/doc", 30 | "/share/gtk-doc", 31 | "/share/man", 32 | "/share/pkgconfig", 33 | "*.la", 34 | "*.a" 35 | ], 36 | "build-options" : { 37 | "append-path" : "/usr/lib/sdk/rust-stable/bin", 38 | "env" : { 39 | "RUSTFLAGS" : "--remap-path-prefix =../", 40 | "CARGO_HOME" : "/run/build/oku/cargo" 41 | } 42 | }, 43 | "modules" : [ 44 | { 45 | "name": "libfuse", 46 | "buildsystem": "meson", 47 | "config-opts": [ 48 | "-Dexamples=false", 49 | "-Dutils=false" 50 | ], 51 | "sources": [ 52 | { 53 | "type": "git", 54 | "url": "https://github.com/libfuse/libfuse.git", 55 | "tag": "fuse-3.16.1", 56 | "commit": "1f0dfae4084577997291bb0e1b94aeff89a5e70f" 57 | } 58 | ] 59 | }, 60 | { 61 | "name" : "oku", 62 | "buildsystem" : "simple", 63 | "build-commands" : [ 64 | "install -Dm755 fusermount-wrapper.sh /app/bin/fusermount3", 65 | "./prebuild.sh", 66 | "cargo --offline fetch --manifest-path Cargo.toml --verbose", 67 | "cargo --offline build --release --verbose", 68 | "install -Dm755 ./target/release/oku -t /app/bin/", 69 | "install -Dm755 ./resources.gresource -t /app/bin/", 70 | "install -Dm644 ./data/${FLATPAK_ID}.metainfo.xml -t /app/share/metainfo/", 71 | "install -Dm644 ./data/${FLATPAK_ID}.desktop -t /app/share/applications/", 72 | "mkdir -p /app/share/icons/hicolor", 73 | "cp -avr ./data/hicolor /app/share/icons/", 74 | "install -Dm644 ./README.md -t /app/share/doc/oku/", 75 | "install -Dm644 ./COPYING -t /app/share/doc/oku/" 76 | ], 77 | "sources" : [ 78 | { 79 | "type": "file", 80 | "path": "./fusermount-wrapper.sh" 81 | }, 82 | { 83 | "type" : "git", 84 | "url" : "https://github.com/OkuBrowser/oku.git", 85 | "tag": "v0.1.1" 86 | }, 87 | "cargo-sources.json" 88 | ] 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /src/browser_pages/snippets/normalise.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | html { 3 | line-height: 1.15; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } 10 | 11 | main { 12 | display: block; 13 | } 14 | 15 | h1 { 16 | font-size: 2em; 17 | margin: 0.67em 0; 18 | } 19 | 20 | hr { 21 | box-sizing: content-box; 22 | height: 0; 23 | overflow: visible; 24 | } 25 | 26 | pre { 27 | font-family: monospace, monospace; 28 | font-size: 1em; 29 | } 30 | 31 | a { 32 | background-color: transparent; 33 | } 34 | 35 | abbr[title] { 36 | border-bottom: none; 37 | text-decoration: underline; 38 | text-decoration: underline dotted; 39 | } 40 | 41 | b, 42 | strong { 43 | font-weight: bolder; 44 | } 45 | 46 | code, 47 | kbd, 48 | samp { 49 | font-family: monospace, monospace; 50 | font-size: 1em; 51 | } 52 | 53 | small { 54 | font-size: 80%; 55 | } 56 | 57 | sub, 58 | sup { 59 | font-size: 75%; 60 | line-height: 0; 61 | position: relative; 62 | vertical-align: baseline; 63 | } 64 | 65 | sub { 66 | bottom: -0.25em; 67 | } 68 | 69 | sup { 70 | top: -0.5em; 71 | } 72 | 73 | img { 74 | border-style: none; 75 | } 76 | 77 | button, 78 | input, 79 | optgroup, 80 | select, 81 | textarea { 82 | font-family: inherit; 83 | font-size: 100%; 84 | line-height: 1.15; 85 | margin: 0; 86 | } 87 | 88 | button, 89 | input { 90 | overflow: visible; 91 | } 92 | 93 | button, 94 | select { 95 | text-transform: none; 96 | } 97 | 98 | button, 99 | [type="button"], 100 | [type="reset"], 101 | [type="submit"] { 102 | -webkit-appearance: button; 103 | } 104 | 105 | button::-moz-focus-inner, 106 | [type="button"]::-moz-focus-inner, 107 | [type="reset"]::-moz-focus-inner, 108 | [type="submit"]::-moz-focus-inner { 109 | border-style: none; 110 | padding: 0; 111 | } 112 | 113 | button:-moz-focusring, 114 | [type="button"]:-moz-focusring, 115 | [type="reset"]:-moz-focusring, 116 | [type="submit"]:-moz-focusring { 117 | outline: 1px dotted ButtonText; 118 | } 119 | 120 | fieldset { 121 | padding: 0.35em 0.75em 0.625em; 122 | } 123 | 124 | legend { 125 | box-sizing: border-box; 126 | color: inherit; 127 | display: table; 128 | max-width: 100%; 129 | padding: 0; 130 | white-space: normal; 131 | } 132 | 133 | progress { 134 | vertical-align: baseline; 135 | } 136 | 137 | textarea { 138 | overflow: auto; 139 | } 140 | 141 | [type="checkbox"], 142 | [type="radio"] { 143 | box-sizing: border-box; 144 | padding: 0; 145 | } 146 | 147 | [type="number"]::-webkit-inner-spin-button, 148 | [type="number"]::-webkit-outer-spin-button { 149 | height: auto; 150 | } 151 | 152 | [type="search"] { 153 | -webkit-appearance: textfield; 154 | outline-offset: -2px; 155 | } 156 | 157 | [type="search"]::-webkit-search-decoration { 158 | -webkit-appearance: none; 159 | } 160 | 161 | ::-webkit-file-upload-button { 162 | -webkit-appearance: button; 163 | font: inherit; 164 | } 165 | 166 | details { 167 | display: block; 168 | } 169 | 170 | summary { 171 | display: list-item; 172 | } 173 | 174 | template { 175 | display: none; 176 | } 177 | 178 | 179 | [hidden] { 180 | display: none; 181 | } -------------------------------------------------------------------------------- /src/replica_item.rs: -------------------------------------------------------------------------------- 1 | use glib::property::PropertySet; 2 | use glib::subclass::object::ObjectImpl; 3 | use glib::subclass::types::ObjectSubclass; 4 | use glib::subclass::types::ObjectSubclassExt; 5 | use glib::subclass::types::ObjectSubclassIsExt; 6 | use glib::value::ToValue; 7 | use glib::ParamSpec; 8 | use glib::ParamSpecBoolean; 9 | use glib::ParamSpecBuilderExt; 10 | use glib::ParamSpecString; 11 | use glib::Value; 12 | use std::cell::RefCell; 13 | use std::sync::LazyLock; 14 | 15 | pub mod imp { 16 | use super::*; 17 | 18 | #[derive(Default, Debug)] 19 | pub struct ReplicaItem { 20 | pub(crate) id: RefCell, 21 | pub(crate) writable: RefCell, 22 | pub(crate) home: RefCell, 23 | } 24 | 25 | #[glib::object_subclass] 26 | impl ObjectSubclass for ReplicaItem { 27 | const NAME: &'static str = "OkuReplicaItem"; 28 | type Type = super::ReplicaItem; 29 | } 30 | 31 | impl ObjectImpl for ReplicaItem { 32 | fn properties() -> &'static [ParamSpec] { 33 | static PROPERTIES: LazyLock> = LazyLock::new(|| { 34 | vec![ 35 | ParamSpecString::builder("id").readwrite().build(), 36 | ParamSpecBoolean::builder("writable").readwrite().build(), 37 | ParamSpecBoolean::builder("home").readwrite().build(), 38 | ] 39 | }); 40 | PROPERTIES.as_ref() 41 | } 42 | 43 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 44 | match pspec.name() { 45 | "id" => { 46 | let id = value.get::().unwrap(); 47 | self.id.set(html_escape::encode_text(&id).to_string()); 48 | } 49 | "writable" => { 50 | let writable = value.get::().unwrap(); 51 | self.writable.set(writable); 52 | } 53 | "home" => { 54 | let home = value.get::().unwrap(); 55 | self.home.set(home); 56 | } 57 | _ => unimplemented!(), 58 | } 59 | } 60 | 61 | fn property(&self, _id: usize, pspec: &ParamSpec) -> Value { 62 | let obj = self.obj(); 63 | match pspec.name() { 64 | "id" => obj.id().to_value(), 65 | "writable" => obj.writable().to_value(), 66 | "home" => obj.home().to_value(), 67 | _ => unimplemented!(), 68 | } 69 | } 70 | } 71 | } 72 | 73 | glib::wrapper! { 74 | pub struct ReplicaItem(ObjectSubclass); 75 | } 76 | 77 | unsafe impl Send for ReplicaItem {} 78 | unsafe impl Sync for ReplicaItem {} 79 | 80 | impl ReplicaItem { 81 | pub fn id(&self) -> String { 82 | self.imp().id.borrow().to_string() 83 | } 84 | pub fn writable(&self) -> bool { 85 | *self.imp().writable.borrow() 86 | } 87 | pub fn home(&self) -> bool { 88 | *self.imp().home.borrow() 89 | } 90 | pub fn new(id: String, writable: bool, home: bool) -> Self { 91 | let replica_item = glib::Object::builder::() 92 | .property("id", id) 93 | .property("writable", writable) 94 | .property("home", home) 95 | .build(); 96 | 97 | replica_item 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oku" 3 | version = "0.1.1" 4 | authors = ["Emil Sayahi "] 5 | edition = "2021" 6 | exclude = [ 7 | "/.github/**/*", 8 | "/.cargo/**/*", 9 | "/branding/**/*", 10 | "/rust-toolchain", 11 | "/.gitignore", 12 | "/.whitesource", 13 | "/renovate.json", 14 | "/CODE_OF_CONDUCT.md", 15 | "/CONTRIBUTING.md", 16 | "/LICENSE.md", 17 | "/SECURITY.md", 18 | "/COPYING", 19 | "/NOTICE", 20 | ] 21 | license = "AGPL-3.0-or-later" 22 | description = "Browse & express yourself" 23 | repository = "https://github.com/OkuBrowser/oku" 24 | homepage = "https://okubrowser.github.io/" 25 | readme = "README.md" 26 | resolver = "2" 27 | 28 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 29 | 30 | [dependencies] 31 | chrono = { version = "0.4.39", features = ["unstable-locales", "serde"] } 32 | cid = "0.11.1" 33 | directories-next = "2.0.0" 34 | futures = "0.3.31" 35 | gdk = { version = "*", package = "gdk4", features = ["v4_14"] } 36 | gio = { version = "*", features = ["v2_80"] } 37 | glib = { version = "*", features = ["v2_80"] } 38 | glib-macros = { version = "*" } 39 | gtk = { version = "*", package = "gtk4", features = [ 40 | "gnome_47", 41 | ], default-features = false } 42 | ipfs = { git = "https://github.com/dariusc93/rust-ipfs.git", rev = "c80e24e73124fa40c2168760eef3b837888e9ae7", package = "rust-ipfs" } 43 | libadwaita = { version = "*", features = ["v1_6", "gio_v2_80"] } 44 | oku-fs = { git = "https://github.com/OkuBrowser/oku-fs", features = ["fuse"] } 45 | # oku-fs = { path = "/home/emil/Documents/GitHub/oku-fs", features = ["fuse"] } 46 | pango = { version = "*" } 47 | url = "2.5.4" 48 | tokio = { version = "1.43.0", features = ["full"] } 49 | tokio-stream = "0.1.17" 50 | webkit2gtk = { version = "*", package = "webkit6", features = ["v2_44"] } 51 | tree_magic_mini = { version = "3.1.6", features = ["with-gpl-data"] } 52 | open = "5.3.2" 53 | env_logger = "0.11.6" 54 | log = "0.4.25" 55 | toml = "0.8.19" 56 | serde = "1.0.217" 57 | uuid = { version = "1.12.1", features = ["v7", "fast-rng", "serde"] } 58 | miette = "7.4.0" 59 | html-escape = "0.2.13" 60 | bytes = "1.9.0" 61 | native_db = "0.8.1" 62 | native_model = "0.4.20" 63 | rayon = "1.10.0" 64 | tantivy = "0.22.0" 65 | # vox = { path = "/home/emil/Documents/GitHub/vox", features = ["ram_provider"] } 66 | vox = { git = "https://github.com/emmyoh/vox", features = ["ram_provider"] } 67 | bs58 = "0.5.1" 68 | showfile = { version = "0.1.1", features = ["gio"], default-features = false } 69 | opengraph = "0.2.4" 70 | 71 | [profile.release] 72 | codegen-units = 1 73 | opt-level = 3 74 | lto = true 75 | debug = 0 76 | 77 | [package.metadata.deb] 78 | section = "utility" 79 | priority = "optional" 80 | assets = [ 81 | [ 82 | "target/x86_64-unknown-linux-gnu/release/oku", 83 | "usr/bin/", 84 | "755", 85 | ], 86 | [ 87 | "README.md", 88 | "usr/share/doc/oku/README", 89 | "644", 90 | ], 91 | [ 92 | "COPYING", 93 | "usr/share/doc/oku/COPYING", 94 | "644", 95 | ], 96 | [ 97 | "data/hicolor/**/*", 98 | "/usr/share/icons/hicolor/", 99 | "644", 100 | ], 101 | ] 102 | 103 | [package.metadata.rpm] 104 | package = "oku" 105 | 106 | [package.metadata.rpm.cargo] 107 | buildflags = ["--release"] 108 | 109 | [package.metadata.rpm.targets] 110 | oku = { path = "/usr/bin/oku" } 111 | -------------------------------------------------------------------------------- /src/config/enums.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde::Serialize; 3 | 4 | #[derive( 5 | Default, 6 | Debug, 7 | Eq, 8 | PartialEq, 9 | Ord, 10 | PartialOrd, 11 | Hash, 12 | Clone, 13 | Copy, 14 | Serialize, 15 | Deserialize, 16 | glib::Enum, 17 | )] 18 | #[enum_type(name = "OkuColourScheme")] 19 | #[non_exhaustive] 20 | #[repr(i32)] 21 | pub enum ColourScheme { 22 | #[default] 23 | Default, 24 | ForceLight, 25 | PreferLight, 26 | PreferDark, 27 | ForceDark, 28 | } 29 | 30 | impl From<&str> for ColourScheme { 31 | fn from(value: &str) -> Self { 32 | match value { 33 | "Automatic" => Self::Default, 34 | "Force Light" => Self::ForceLight, 35 | "Prefer Light" => Self::PreferLight, 36 | "Prefer Dark" => Self::PreferDark, 37 | "Force Dark" => Self::ForceDark, 38 | _ => Self::default(), 39 | } 40 | } 41 | } 42 | 43 | impl From for ColourScheme { 44 | fn from(value: libadwaita::ColorScheme) -> Self { 45 | match value { 46 | libadwaita::ColorScheme::Default => Self::Default, 47 | libadwaita::ColorScheme::ForceLight => Self::ForceLight, 48 | libadwaita::ColorScheme::PreferLight => Self::PreferLight, 49 | libadwaita::ColorScheme::PreferDark => Self::PreferDark, 50 | libadwaita::ColorScheme::ForceDark => Self::ForceDark, 51 | _ => Self::default(), 52 | } 53 | } 54 | } 55 | 56 | impl From for libadwaita::ColorScheme { 57 | fn from(val: ColourScheme) -> Self { 58 | match val { 59 | ColourScheme::Default => libadwaita::ColorScheme::Default, 60 | ColourScheme::ForceLight => libadwaita::ColorScheme::ForceLight, 61 | ColourScheme::PreferLight => libadwaita::ColorScheme::PreferLight, 62 | ColourScheme::PreferDark => libadwaita::ColorScheme::PreferDark, 63 | ColourScheme::ForceDark => libadwaita::ColorScheme::ForceDark, 64 | } 65 | } 66 | } 67 | 68 | #[derive( 69 | Default, 70 | Debug, 71 | Eq, 72 | PartialEq, 73 | Ord, 74 | PartialOrd, 75 | Hash, 76 | Clone, 77 | Copy, 78 | Serialize, 79 | Deserialize, 80 | glib::Enum, 81 | )] 82 | #[enum_type(name = "OkuPalette")] 83 | #[repr(i32)] 84 | pub enum Palette { 85 | #[default] 86 | None, 87 | Blue, 88 | Green, 89 | Yellow, 90 | Orange, 91 | Red, 92 | Purple, 93 | Brown, 94 | } 95 | 96 | impl From<&str> for Palette { 97 | fn from(value: &str) -> Self { 98 | match value { 99 | "None" => Self::None, 100 | "Blue" => Self::Blue, 101 | "Green" => Self::Green, 102 | "Yellow" => Self::Yellow, 103 | "Orange" => Self::Orange, 104 | "Red" => Self::Red, 105 | "Purple" => Self::Purple, 106 | "Brown" => Self::Brown, 107 | _ => Self::default(), 108 | } 109 | } 110 | } 111 | 112 | impl Palette { 113 | pub fn hue(&self) -> u64 { 114 | match self { 115 | Self::None => unreachable!(), 116 | Self::Blue => 213, 117 | Self::Green => 152, 118 | Self::Yellow => 42, 119 | Self::Orange => 21, 120 | Self::Red => 353, 121 | Self::Purple => 274, 122 | Self::Brown => 27, 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/scheme_handlers/oku_path.rs: -------------------------------------------------------------------------------- 1 | use miette::IntoDiagnostic; 2 | use oku_fs::iroh_docs::AuthorId; 3 | use std::path::PathBuf; 4 | 5 | #[derive(PartialEq, Debug, Clone)] 6 | pub enum OkuPath { 7 | Home, 8 | Me(Option), 9 | Tag(String), 10 | Tags, 11 | User(AuthorId, Option), 12 | ToggleFollow(AuthorId), 13 | ToggleBlock(AuthorId), 14 | Delete(PathBuf), 15 | Search(String), 16 | } 17 | 18 | impl OkuPath { 19 | pub fn parse(path: impl AsRef) -> miette::Result { 20 | let url_components: Vec<_> = path 21 | .as_ref() 22 | .components() 23 | .map(|x| PathBuf::from(x.as_os_str())) 24 | .collect(); 25 | let first_component = url_components 26 | .first() 27 | .map(|x| x.to_path_buf()) 28 | .unwrap_or(PathBuf::from("home")); 29 | let second_component = url_components.get(1); 30 | let replica_path = second_component 31 | .and_then(|_x| path.as_ref().strip_prefix(first_component.clone()).ok()) 32 | .map(|x| x.to_path_buf()); 33 | Ok( 34 | match first_component 35 | .as_os_str() 36 | .to_string_lossy() 37 | .to_string() 38 | .as_str() 39 | { 40 | "home" => OkuPath::Home, 41 | "tags" => OkuPath::Tags, 42 | "tag" => second_component 43 | .map(|x| OkuPath::Tag(x.to_string_lossy().to_string())) 44 | .unwrap_or(OkuPath::Tags), 45 | "me" => OkuPath::Me(replica_path), 46 | "follow" => OkuPath::ToggleFollow(AuthorId::from( 47 | oku_fs::fs::util::parse_array_hex_or_base32::<32>( 48 | second_component 49 | .ok_or(miette::miette!("Missing author ID … "))? 50 | .as_os_str() 51 | .to_string_lossy() 52 | .to_string() 53 | .as_str(), 54 | )?, 55 | )), 56 | "block" => OkuPath::ToggleBlock(AuthorId::from( 57 | oku_fs::fs::util::parse_array_hex_or_base32::<32>( 58 | second_component 59 | .ok_or(miette::miette!("Missing author ID … "))? 60 | .as_os_str() 61 | .to_string_lossy() 62 | .to_string() 63 | .as_str(), 64 | )?, 65 | )), 66 | "delete" => { 67 | OkuPath::Delete(replica_path.ok_or(miette::miette!("Missing post path … "))?) 68 | } 69 | "search" => OkuPath::Search( 70 | path.as_ref() 71 | .strip_prefix("search/") 72 | .into_diagnostic()? 73 | .to_string_lossy() 74 | .to_string(), 75 | ), 76 | _ => OkuPath::User( 77 | AuthorId::from(oku_fs::fs::util::parse_array_hex_or_base32::<32>( 78 | first_component 79 | .as_os_str() 80 | .to_string_lossy() 81 | .to_string() 82 | .as_str(), 83 | )?), 84 | replica_path, 85 | ), 86 | }, 87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /resources.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | arrow-pointing-at-line-down-symbolic.svg 5 | external-link-symbolic.svg 6 | file-cabinet-symbolic.svg 7 | globe-symbolic.svg 8 | keyboard-shortcuts-symbolic.svg 9 | library-symbolic.svg 10 | people-symbolic.svg 11 | screen-privacy7-symbolic.svg 12 | update-symbolic.svg 13 | uppercase-symbolic.svg 14 | user-trash-symbolic.svg 15 | hourglass-symbolic.svg 16 | ticket-special-symbolic.svg 17 | ticket-symbolic.svg 18 | copy-symbolic.svg 19 | user-home-symbolic.svg 20 | note-symbolic.svg 21 | folder-remote-symbolic.svg 22 | bookmark-filled-symbolic.svg 23 | editor-symbolic.svg 24 | window-close-symbolic.svg 25 | user-info-symbolic.svg 26 | system-switch-user-symbolic.svg 27 | up-symbolic.svg 28 | down-symbolic.svg 29 | first-symbolic.svg 30 | text-underline-symbolic.svg 31 | seek-backward-symbolic.svg 32 | loop-arrow-symbolic.svg 33 | arrow-circular-top-right-symbolic.svg 34 | tab-new-symbolic.svg 35 | edit-find-symbolic.svg 36 | menu-symbolic.svg 37 | zoom-in-symbolic.svg 38 | zoom-out-symbolic.svg 39 | zoom-original-symbolic.svg 40 | fullscreen-rectangular-symbolic.svg 41 | unfullscreen-rectangular-symbolic.svg 42 | printer-symbolic.svg 43 | screenshot-recorded-symbolic.svg 44 | window-new-symbolic.svg 45 | settings-symbolic.svg 46 | info-outline-symbolic.svg 47 | left-symbolic.svg 48 | right-symbolic.svg 49 | cross-large-symbolic.svg 50 | folder-new-symbolic.svg 51 | folder-open-symbolic.svg 52 | shapes-symbolic.svg 53 | entry-clear-symbolic.svg 54 | wrench-wide-symbolic.svg 55 | 56 | -------------------------------------------------------------------------------- /src/widgets/window/headerbar.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::Ordering; 2 | 3 | use super::*; 4 | use crate::{MOUNT_DIR, REPLICAS_MOUNTED}; 5 | use glib::clone; 6 | use gtk::glib; 7 | use gtk::subclass::prelude::*; 8 | use libadwaita::prelude::*; 9 | 10 | impl Window { 11 | pub fn setup_left_headerbar(&self) { 12 | self.setup_navigation_buttons(); 13 | let imp = self.imp(); 14 | 15 | // Refresh button 16 | imp.refresh_button.set_can_focus(true); 17 | imp.refresh_button.set_receives_default(true); 18 | imp.refresh_button 19 | .set_icon_name("arrow-circular-top-right-symbolic"); 20 | 21 | // Add Tab button 22 | imp.add_tab_button.set_can_focus(true); 23 | imp.add_tab_button.set_receives_default(true); 24 | imp.add_tab_button.set_icon_name("tab-new-symbolic"); 25 | 26 | // Sidebar button 27 | imp.sidebar_button.set_can_focus(true); 28 | imp.sidebar_button.set_receives_default(true); 29 | imp.sidebar_button.set_icon_name("library-symbolic"); 30 | 31 | // Left header buttons 32 | imp.left_header_buttons.append(&imp.navigation_buttons); 33 | imp.left_header_buttons.append(&imp.refresh_button); 34 | imp.left_header_buttons.append(&imp.add_tab_button); 35 | imp.left_header_buttons.append(&imp.sidebar_button); 36 | } 37 | 38 | pub fn setup_right_headerbar(&self) { 39 | let imp = self.imp(); 40 | 41 | // Overview button 42 | imp.overview_button.set_can_focus(true); 43 | imp.overview_button.set_receives_default(true); 44 | imp.overview_button.set_view(Some(&imp.tab_view)); 45 | 46 | // Note button 47 | imp.note_button.set_can_focus(true); 48 | imp.note_button.set_receives_default(true); 49 | imp.note_button.set_icon_name("note-symbolic"); 50 | 51 | // Find button 52 | imp.find_button.set_can_focus(true); 53 | imp.find_button.set_receives_default(true); 54 | imp.find_button.set_icon_name("edit-find-symbolic"); 55 | 56 | // Replica menu button 57 | imp.replicas_button.set_can_focus(true); 58 | imp.replicas_button.set_receives_default(true); 59 | imp.replicas_button.set_icon_name("file-cabinet-symbolic"); 60 | imp.replicas_button 61 | .set_visible(REPLICAS_MOUNTED.load(Ordering::Relaxed)); 62 | 63 | // Menu button 64 | imp.menu_button.set_can_focus(true); 65 | imp.menu_button.set_receives_default(true); 66 | imp.menu_button.set_icon_name("menu-symbolic"); 67 | 68 | imp.right_header_buttons.append(&imp.overview_button); 69 | imp.right_header_buttons.append(&imp.note_button); 70 | imp.right_header_buttons.append(&imp.find_button); 71 | imp.right_header_buttons.append(&imp.replicas_button); 72 | imp.right_header_buttons.append(&imp.menu_button); 73 | } 74 | 75 | pub fn setup_headerbar(&self) { 76 | self.setup_left_headerbar(); 77 | self.setup_right_headerbar(); 78 | let imp = self.imp(); 79 | // HeaderBar 80 | imp.headerbar.set_can_focus(true); 81 | imp.headerbar.set_title_widget(Some(&imp.nav_entry)); 82 | imp.headerbar.pack_start(&imp.left_header_buttons); 83 | imp.headerbar.pack_end(&imp.right_header_buttons); 84 | } 85 | 86 | pub fn setup_overview_button_clicked(&self) { 87 | let imp = self.imp(); 88 | 89 | imp.overview_button.connect_clicked(clone!( 90 | #[weak(rename_to = tab_overview)] 91 | imp.tab_overview, 92 | move |_| { 93 | tab_overview.set_open(!tab_overview.is_open()); 94 | } 95 | )); 96 | } 97 | 98 | pub fn setup_replicas_button_clicked(&self) { 99 | let imp = self.imp(); 100 | 101 | imp.replicas_button.connect_clicked(clone!(move |_| { 102 | let _ = open::that_detached(MOUNT_DIR.to_path_buf()); 103 | })); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/widgets/window/suggestions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::suggestion_item::SuggestionItem; 3 | use crate::widgets; 4 | use glib::clone; 5 | use gtk::subclass::prelude::*; 6 | use gtk::{gio, glib}; 7 | use libadwaita::prelude::*; 8 | use std::cell::Ref; 9 | use std::rc::Rc; 10 | use webkit2gtk::functions::uri_for_display; 11 | 12 | impl Window { 13 | pub fn suggestions_store(&self) -> Ref { 14 | let suggestions_store = self.imp().suggestions_store.borrow(); 15 | 16 | Ref::map(suggestions_store, |suggestions_store| { 17 | let suggestions_store = suggestions_store.as_deref().unwrap(); 18 | suggestions_store 19 | }) 20 | } 21 | 22 | pub fn setup_suggestions_popover(&self) { 23 | let imp = self.imp(); 24 | 25 | let suggestions_store = gio::ListStore::new::(); 26 | imp.suggestions_store 27 | .replace(Some(Rc::new(suggestions_store))); 28 | 29 | imp.suggestions_model 30 | .set_model(Some(&self.suggestions_store().clone())); 31 | imp.suggestions_model.set_autoselect(false); 32 | imp.suggestions_model.connect_selected_item_notify(clone!( 33 | #[weak] 34 | imp, 35 | move |suggestions_model| { 36 | if let Some(item) = suggestions_model.selected_item() { 37 | let suggestion_item = item.downcast_ref::().unwrap(); 38 | let encoded_uri = suggestion_item.uri(); 39 | let decoded_uri = html_escape::decode_html_entities(&encoded_uri); 40 | imp.nav_entry 41 | .set_text(&uri_for_display(&decoded_uri).unwrap_or(decoded_uri.into())); 42 | } 43 | } 44 | )); 45 | 46 | imp.suggestions_factory 47 | .connect_setup(clone!(move |_, item| { 48 | let row = widgets::suggestion_row::SuggestionRow::new(); 49 | let list_item = item.downcast_ref::().unwrap(); 50 | list_item.set_child(Some(&row)); 51 | list_item 52 | .property_expression("item") 53 | .chain_property::("title") 54 | .bind(&row, "title-property", gtk::Widget::NONE); 55 | list_item 56 | .property_expression("item") 57 | .chain_property::("uri") 58 | .bind(&row, "uri", gtk::Widget::NONE); 59 | list_item 60 | .property_expression("item") 61 | .chain_property::("favicon") 62 | .bind(&row, "favicon", gtk::Widget::NONE); 63 | })); 64 | 65 | imp.suggestions_view.set_model(Some(&imp.suggestions_model)); 66 | imp.suggestions_view 67 | .set_factory(Some(&imp.suggestions_factory)); 68 | imp.suggestions_view.set_enable_rubberband(false); 69 | imp.suggestions_view 70 | .set_hscroll_policy(gtk::ScrollablePolicy::Natural); 71 | imp.suggestions_view 72 | .set_vscroll_policy(gtk::ScrollablePolicy::Natural); 73 | 74 | imp.suggestions_scrolled_window 75 | .set_child(Some(&imp.suggestions_view)); 76 | imp.suggestions_scrolled_window 77 | .set_orientation(gtk::Orientation::Horizontal); 78 | imp.suggestions_scrolled_window.set_maximum_size(1000); 79 | imp.suggestions_scrolled_window 80 | .set_tightening_threshold(1000); 81 | 82 | imp.suggestions_popover 83 | .set_child(Some(&imp.suggestions_scrolled_window)); 84 | imp.suggestions_popover.set_parent(&imp.nav_entry); 85 | imp.suggestions_popover.add_css_class("menu"); 86 | imp.suggestions_popover.add_css_class("suggestions"); 87 | imp.suggestions_popover.set_has_arrow(false); 88 | imp.suggestions_popover.set_autohide(false); 89 | imp.suggestions_popover.set_can_focus(false); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/widgets/tag.rs: -------------------------------------------------------------------------------- 1 | use glib::subclass::object::ObjectImpl; 2 | use glib::subclass::types::ObjectSubclass; 3 | use glib::subclass::types::ObjectSubclassExt; 4 | use glib::subclass::types::ObjectSubclassIsExt; 5 | use glib::value::ToValue; 6 | use glib::ParamSpec; 7 | use glib::ParamSpecString; 8 | use glib::Value; 9 | use gtk::prelude::BoxExt; 10 | use gtk::prelude::ButtonExt; 11 | use gtk::prelude::GObjectPropertyExpressionExt; 12 | use gtk::prelude::OrientableExt; 13 | use gtk::prelude::WidgetExt; 14 | use gtk::subclass::prelude::*; 15 | use std::cell::RefCell; 16 | use std::sync::LazyLock; 17 | 18 | pub mod imp { 19 | use super::*; 20 | 21 | #[derive(Debug, Default)] 22 | pub struct Tag { 23 | pub(crate) text: RefCell, 24 | pub(crate) text_label: gtk::Label, 25 | pub(crate) delete_button: gtk::Button, 26 | } 27 | 28 | impl Tag {} 29 | 30 | #[glib::object_subclass] 31 | impl ObjectSubclass for Tag { 32 | const NAME: &'static str = "OkuTag"; 33 | type Type = super::Tag; 34 | type ParentType = gtk::Box; 35 | 36 | fn class_init(klass: &mut Self::Class) { 37 | klass.set_layout_manager_type::(); 38 | klass.set_accessible_role(gtk::AccessibleRole::Generic); 39 | } 40 | } 41 | 42 | impl ObjectImpl for Tag { 43 | fn dispose(&self) { 44 | while let Some(child) = self.obj().first_child() { 45 | child.unparent(); 46 | } 47 | } 48 | 49 | fn constructed(&self) { 50 | self.parent_constructed(); 51 | 52 | self.obj().setup(); 53 | } 54 | 55 | fn properties() -> &'static [ParamSpec] { 56 | static PROPERTIES: LazyLock> = 57 | LazyLock::new(|| vec![ParamSpecString::builder("text").build()]); 58 | PROPERTIES.as_ref() 59 | } 60 | 61 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 62 | match pspec.name() { 63 | "text" => { 64 | let text = value.get::().unwrap(); 65 | self.obj().set_text(text); 66 | } 67 | _ => unimplemented!(), 68 | } 69 | } 70 | 71 | fn property(&self, _id: usize, pspec: &ParamSpec) -> Value { 72 | match pspec.name() { 73 | "text" => self.obj().text().to_value(), 74 | _ => unimplemented!(), 75 | } 76 | } 77 | } 78 | impl WidgetImpl for Tag {} 79 | impl BoxImpl for Tag {} 80 | } 81 | 82 | glib::wrapper! { 83 | pub struct Tag(ObjectSubclass) 84 | @extends gtk::Box, gtk::Widget, 85 | @implements gtk::Accessible, gtk::Actionable, gtk::Orientable, gtk::Buildable, gtk::ConstraintTarget; 86 | } 87 | 88 | impl Default for Tag { 89 | fn default() -> Self { 90 | glib::Object::new() 91 | } 92 | } 93 | 94 | impl Tag { 95 | pub fn new() -> Self { 96 | Self::default() 97 | } 98 | 99 | pub fn setup(&self) { 100 | let imp = self.imp(); 101 | 102 | self.property_expression("text") 103 | .bind(&imp.text_label, "label", gtk::Widget::NONE); 104 | imp.text_label.set_xalign(0.0); 105 | imp.text_label.set_ellipsize(pango::EllipsizeMode::End); 106 | imp.text_label.set_hexpand(true); 107 | 108 | imp.delete_button.set_icon_name("window-close-symbolic"); 109 | imp.delete_button.add_css_class("flat"); 110 | imp.delete_button.add_css_class("circular"); 111 | 112 | self.add_css_class("toolbar"); 113 | self.add_css_class("osd"); 114 | self.set_orientation(gtk::Orientation::Horizontal); 115 | self.append(&imp.text_label); 116 | self.append(&imp.delete_button); 117 | } 118 | pub fn text(&self) -> String { 119 | self.imp().text.borrow().to_owned() 120 | } 121 | 122 | pub fn set_text(&self, text: String) { 123 | let imp = self.imp(); 124 | 125 | imp.text.replace(text); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/suggestion_item.rs: -------------------------------------------------------------------------------- 1 | use glib::clone; 2 | use glib::object::ObjectExt; 3 | use glib::property::PropertySet; 4 | use glib::subclass::object::ObjectImpl; 5 | use glib::subclass::types::ObjectSubclass; 6 | use glib::subclass::types::ObjectSubclassExt; 7 | use glib::subclass::types::ObjectSubclassIsExt; 8 | use glib::value::ToValue; 9 | use glib::ParamSpec; 10 | use glib::ParamSpecBuilderExt; 11 | use glib::ParamSpecObject; 12 | use glib::ParamSpecString; 13 | use glib::Value; 14 | use std::cell::RefCell; 15 | use std::sync::LazyLock; 16 | use webkit2gtk::functions::uri_for_display; 17 | 18 | pub mod imp { 19 | use super::*; 20 | 21 | #[derive(Default, Debug)] 22 | pub struct SuggestionItem { 23 | pub(crate) title: RefCell, 24 | pub(crate) uri: RefCell, 25 | pub(crate) favicon: RefCell>, 26 | } 27 | 28 | #[glib::object_subclass] 29 | impl ObjectSubclass for SuggestionItem { 30 | const NAME: &'static str = "OkuSuggestionItem"; 31 | type Type = super::SuggestionItem; 32 | } 33 | 34 | impl ObjectImpl for SuggestionItem { 35 | fn properties() -> &'static [ParamSpec] { 36 | static PROPERTIES: LazyLock> = LazyLock::new(|| { 37 | vec![ 38 | ParamSpecString::builder("title").readwrite().build(), 39 | ParamSpecString::builder("uri").readwrite().build(), 40 | ParamSpecObject::builder::("favicon") 41 | .readwrite() 42 | .build(), 43 | ] 44 | }); 45 | PROPERTIES.as_ref() 46 | } 47 | 48 | fn set_property(&self, _id: usize, value: &Value, pspec: &ParamSpec) { 49 | match pspec.name() { 50 | "uri" => { 51 | let uri = value.get::().unwrap(); 52 | self.uri.set( 53 | html_escape::encode_text(&uri_for_display(&uri).unwrap_or(uri.into())) 54 | .to_string(), 55 | ); 56 | } 57 | "title" => { 58 | let title = value.get::().unwrap(); 59 | self.title.set(html_escape::encode_text(&title).to_string()); 60 | } 61 | "favicon" => { 62 | let favicon = value.get::().unwrap(); 63 | self.favicon.set(Some(favicon)); 64 | } 65 | _ => unimplemented!(), 66 | } 67 | } 68 | 69 | fn property(&self, _id: usize, pspec: &ParamSpec) -> Value { 70 | let obj = self.obj(); 71 | match pspec.name() { 72 | "title" => obj.title().to_value(), 73 | "uri" => obj.uri().to_value(), 74 | "favicon" => obj.favicon().to_value(), 75 | _ => unimplemented!(), 76 | } 77 | } 78 | } 79 | } 80 | 81 | glib::wrapper! { 82 | pub struct SuggestionItem(ObjectSubclass); 83 | } 84 | 85 | impl SuggestionItem { 86 | pub fn title(&self) -> String { 87 | self.imp().title.borrow().to_string() 88 | } 89 | pub fn uri(&self) -> String { 90 | self.imp().uri.borrow().to_string() 91 | } 92 | pub fn favicon(&self) -> Option { 93 | self.imp().favicon.borrow().clone() 94 | } 95 | pub fn new(title: String, uri: String, favicon_database: &webkit2gtk::FaviconDatabase) -> Self { 96 | let suggestion_item = glib::Object::builder::() 97 | .property("title", title) 98 | .property("uri", uri.clone()) 99 | .build(); 100 | 101 | favicon_database.favicon( 102 | &uri, 103 | Some(&gio::Cancellable::new()), 104 | clone!( 105 | #[weak] 106 | suggestion_item, 107 | move |favicon_result| { 108 | if let Ok(favicon) = favicon_result { 109 | suggestion_item.set_property("favicon", favicon); 110 | } 111 | } 112 | ), 113 | ); 114 | 115 | suggestion_item 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /branding/logo-filled.svg: -------------------------------------------------------------------------------- 1 | backgroundLayer 1 -------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/core.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; 2 | use vox::{provider::VoxProvider, ram_provider::RamProvider}; 3 | 4 | pub static OKUNET_VOX_FILES: LazyLock> = LazyLock::new(|| { 5 | HashMap::from([ 6 | ( 7 | "global.toml".into(), 8 | include_str!("../../okunet_pages/global.toml").into(), 9 | ), 10 | ( 11 | "layouts/default.vox".into(), 12 | include_str!("../../okunet_pages/layouts/default.vox").into(), 13 | ), 14 | ( 15 | "layouts/post.vox".into(), 16 | include_str!("../../okunet_pages/layouts/post.vox").into(), 17 | ), 18 | ( 19 | "snippets/profile.voxs".into(), 20 | include_str!("../../okunet_pages/snippets/profile.voxs").into(), 21 | ), 22 | ( 23 | "snippets/logo.svg".into(), 24 | include_str!("../../browser_pages/snippets/logo.svg").into(), 25 | ), 26 | ( 27 | "snippets/head.html".into(), 28 | include_str!("../../okunet_pages/snippets/head.html").into(), 29 | ), 30 | ( 31 | "snippets/normalise.css".into(), 32 | include_str!("../../browser_pages/snippets/normalise.css").into(), 33 | ), 34 | ( 35 | "snippets/post.voxs".into(), 36 | include_str!("../../okunet_pages/snippets/post.voxs").into(), 37 | ), 38 | ( 39 | "snippets/posts.voxs".into(), 40 | include_str!("../../okunet_pages/snippets/posts.voxs").into(), 41 | ), 42 | ( 43 | "snippets/style.css".into(), 44 | include_str!("../../okunet_pages/snippets/style.css").into(), 45 | ), 46 | ( 47 | "snippets/tag.voxs".into(), 48 | include_str!("../../okunet_pages/snippets/tag.voxs").into(), 49 | ), 50 | ( 51 | "snippets/tags.voxs".into(), 52 | include_str!("../../okunet_pages/snippets/tags.voxs").into(), 53 | ), 54 | ( 55 | "snippets/search.voxs".into(), 56 | include_str!("../../okunet_pages/snippets/search.voxs").into(), 57 | ), 58 | ( 59 | "snippets/follow_button.html".into(), 60 | include_str!("../../okunet_pages/snippets/follow_button.html").into(), 61 | ), 62 | ( 63 | "snippets/block_button.html".into(), 64 | include_str!("../../okunet_pages/snippets/block_button.html").into(), 65 | ), 66 | ( 67 | "snippets/delete_button.html".into(), 68 | include_str!("../../okunet_pages/snippets/delete_button.html").into(), 69 | ), 70 | ( 71 | "snippets/user_header.html".into(), 72 | include_str!("../../okunet_pages/snippets/user_header.html").into(), 73 | ), 74 | ( 75 | "snippets/tab_pages.html".into(), 76 | include_str!("../../okunet_pages/snippets/tab_pages.html").into(), 77 | ), 78 | ( 79 | "snippets/masthead.html".into(), 80 | include_str!("../../okunet_pages/snippets/masthead.html").into(), 81 | ), 82 | ( 83 | "snippets/user-trash-symbolic.svg".into(), 84 | include_str!("../../../data/hicolor/scalable/actions/user-trash-symbolic.svg").into(), 85 | ), 86 | ( 87 | "tags.vox".into(), 88 | include_str!("../../okunet_pages/tags.vox").into(), 89 | ), 90 | ( 91 | "home.vox".into(), 92 | include_str!("../../okunet_pages/home.vox").into(), 93 | ), 94 | ]) 95 | }); 96 | 97 | #[derive(Debug, Clone)] 98 | pub struct OkuNetProvider(pub RamProvider); 99 | 100 | impl Default for OkuNetProvider { 101 | fn default() -> Self { 102 | Self::new() 103 | } 104 | } 105 | 106 | impl OkuNetProvider { 107 | pub fn new() -> Self { 108 | Self(RamProvider::new(Some(OKUNET_VOX_FILES.clone()))) 109 | } 110 | pub fn render_and_get(&self, path: impl AsRef) -> miette::Result { 111 | let parser = self.0.create_liquid_parser()?; 112 | let global = self.0.get_global_context()?; 113 | let (dag, _pages, _layouts) = self.0.generate_dag()?; 114 | let (_updated_pages, _updated_dag) = self.0.generate_site( 115 | parser.clone(), 116 | global.0.clone(), 117 | global.1, 118 | dag, 119 | false, 120 | false, 121 | )?; 122 | self.0.read_to_string(path) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /branding/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/io.github.OkuBrowser.oku.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.github.OkuBrowser.oku 4 | CC0 5 | AGPL-3.0+ 6 | Oku 7 | Browse & express yourself 8 | 9 |

Oku is a browser that offers a space that exists parallel to the web, where sites are shared between peers and found through social bookmarking.

10 |

While you may still browse traditional sites, Oku enables you to create, share, and find content without being beholden to centralised corporate interests.

11 |

Unleash your creativity and join a network of users supporting an independent effort to reintroduce control, privacy, & free expression online.

12 |
13 | https://okubrowser.github.io 14 | https://github.com/OkuBrowser/oku/issues 15 | https://github.com/sponsors/emmyoh 16 | https://okubrowser.github.io/faq 17 | https://okubrowser.github.io/contribute 18 | https://github.com/OkuBrowser/oku 19 | 20 | #bf4040 21 | #4d1a57 22 | 23 | 27 | 28 | 29 | 30 | 31 |

Added download manager to library. Added toolbox to address bar. Added overlays for entering fullscreen, fetching from the OkuNet, and when a page is unresponsive.

32 |
33 |
34 | 35 | 36 |

Initial release.

37 |
38 |
39 |
40 | 41 | 42 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/light/main.png 43 | Browser window 44 | 45 | 46 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/dark/bookmarks.png 47 | Library page showing bookmarks 48 | 49 | 50 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/light/history.png 51 | Library page showing browser history 52 | 53 | 54 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/dark/okunet_home.png 55 | An OkuNet home feed 56 | 57 | 58 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/light/okunet_me.png 59 | An OkuNet profile 60 | 61 | 62 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/dark/page.png 63 | Web page 64 | 65 | 66 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/light/replicas.png 67 | Library page showing replicas 68 | 69 | 70 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/dark/settings.png 71 | Browser settings 72 | 73 | 74 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/light/tab_overview.png 75 | Tab overview 76 | 77 | 78 | https://raw.githubusercontent.com/OkuBrowser/okubrowser.github.io/refs/heads/main/assets/screenshots/dark/note_editor.png 79 | Writing a note 80 | 81 | 82 | 86 | 87 | ModernToolkit 88 | HiDpiIcon 89 | HighContrast 90 | 91 | 92 | Emil Sayahi 93 | 94 | limesayahi@gmail.com 95 | io.github.OkuBrowser.oku.desktop 96 | 97 | keyboard 98 | pointing 99 | touch 100 | 101 | 102 | io.github.OkuBrowser.oku.desktop 103 | oku 104 | 105 |
-------------------------------------------------------------------------------- /src/vox_providers/okunet_provider/users.rs: -------------------------------------------------------------------------------- 1 | use super::core::OkuNetProvider; 2 | use crate::NODE; 3 | use oku_fs::{ 4 | database::{posts::core::OkuPost, users::OkuUser}, 5 | iroh_docs::AuthorId, 6 | }; 7 | use vox::provider::VoxProvider; 8 | 9 | impl OkuNetProvider { 10 | pub async fn get_user_frontmatter( 11 | &self, 12 | user: &OkuUser, 13 | posts: Vec, 14 | ) -> miette::Result { 15 | let user_name = match &user.identity { 16 | Some(identity) => identity.name.clone(), 17 | None => oku_fs::fs::util::fmt(user.author_id), 18 | }; 19 | let node = NODE 20 | .get() 21 | .ok_or(miette::miette!("No running Oku node … "))?; 22 | let mut following: Vec<_> = Vec::new(); 23 | if let Some(identity) = user.identity.clone() { 24 | for followed_user in identity.following { 25 | let followed_user_information = node.get_or_fetch_user(&followed_user).await?; 26 | let mut followed_user_table = toml::Table::new(); 27 | followed_user_table.insert( 28 | "id".into(), 29 | oku_fs::fs::util::fmt(followed_user_information.author_id).into(), 30 | ); 31 | match followed_user_information.identity { 32 | Some(discovered_identity) => { 33 | followed_user_table.insert("name".into(), discovered_identity.name.into()); 34 | followed_user_table.insert( 35 | "following".into(), 36 | discovered_identity 37 | .following 38 | .into_iter() 39 | .map(oku_fs::fs::util::fmt) 40 | .collect::>() 41 | .into(), 42 | ); 43 | } 44 | None => { 45 | followed_user_table.insert( 46 | "name".into(), 47 | oku_fs::fs::util::fmt(followed_user_information.author_id).into(), 48 | ); 49 | followed_user_table.insert("following".into(), Vec::::new().into()); 50 | } 51 | }; 52 | following.push(followed_user_table); 53 | } 54 | } 55 | let mut table = toml::Table::new(); 56 | table.insert("layout".into(), "default".into()); 57 | table.insert( 58 | "permalink".into(), 59 | format!("{}.html", oku_fs::fs::util::fmt(user.author_id)).into(), 60 | ); 61 | table.insert("title".into(), user_name.into()); 62 | table.insert( 63 | "author_id".into(), 64 | oku_fs::fs::util::fmt(user.author_id).into(), 65 | ); 66 | table.insert( 67 | "is_followed".into(), 68 | node.is_followed(&user.author_id).await.into(), 69 | ); 70 | table.insert( 71 | "is_blocked".into(), 72 | node.is_blocked(&user.author_id).await.into(), 73 | ); 74 | table.insert("is_me".into(), node.is_me(&user.author_id).await.into()); 75 | if !posts.is_empty() { 76 | table.insert( 77 | "depends".into(), 78 | vec![oku_fs::fs::util::fmt(user.author_id)].into(), 79 | ); 80 | } else { 81 | table.insert("empty".into(), Vec::::new().into()); 82 | } 83 | table.insert("following".into(), following.into()); 84 | Ok(table) 85 | } 86 | pub async fn create_profile_page( 87 | &self, 88 | user: &OkuUser, 89 | posts: Option>, 90 | ) -> miette::Result<()> { 91 | let user_posts = posts.unwrap_or( 92 | oku_fs::database::core::DATABASE 93 | .get_posts_by_author(&user.author_id) 94 | .unwrap_or_default(), 95 | ); 96 | for post in user_posts.iter() { 97 | self.create_post_page(user, post, None).await?; 98 | } 99 | let page_path = format!("{}.vox", oku_fs::fs::util::fmt(user.author_id)); 100 | let include_argument = if !user_posts.is_empty() { 101 | oku_fs::fs::util::fmt(user.author_id) 102 | } else { 103 | "empty".into() 104 | }; 105 | let table = self.get_user_frontmatter(user, user_posts).await?; 106 | let page_contents = format!( 107 | "--- 108 | {} 109 | --- 110 | {{% include profile.voxs posts = {} %}} 111 | ", 112 | table, include_argument 113 | ); 114 | self.0.write_file(page_path, page_contents)?; 115 | Ok(()) 116 | } 117 | 118 | pub async fn view_user(&self, author_id: AuthorId) -> miette::Result { 119 | let node = NODE 120 | .get() 121 | .ok_or(miette::miette!("No running Oku node … "))?; 122 | let user = node.get_or_fetch_user(&author_id).await?; 123 | let posts = node.posts_from_user(&user).await?; 124 | self.create_profile_page(&user, Some(posts)).await?; 125 | self.render_and_get(format!( 126 | "output/{}.html", 127 | oku_fs::fs::util::fmt(user.author_id) 128 | )) 129 | } 130 | 131 | pub async fn view_self(&self) -> miette::Result { 132 | let node = NODE 133 | .get() 134 | .ok_or(miette::miette!("No running Oku node … "))?; 135 | let me = node.user().await?; 136 | let posts = node.posts_from_user(&me).await.ok(); 137 | self.create_profile_page(&me, posts).await?; 138 | self.render_and_get(format!( 139 | "output/{}.html", 140 | oku_fs::fs::util::fmt(me.author_id) 141 | )) 142 | } 143 | } 144 | --------------------------------------------------------------------------------