├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
14 |
15 | © {{ global.author }} {{ meta.date.year }}
16 |
17 |
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 |
2 | {%- if include.is_followed -%}
3 | Unfollow
4 | {%- else -%}
5 | Follow
6 | {%- endif -%}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/data/hicolor/scalable/actions/note-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/okunet_pages/snippets/block_button.html:
--------------------------------------------------------------------------------
1 |
2 | {%- if include.is_blocked -%}
3 | Unblock
4 | {%- else -%}
5 | Block
6 | {%- endif -%}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/okunet_pages/snippets/delete_button.html:
--------------------------------------------------------------------------------
1 | {%- if include.post.data.by_me -%}
2 | {%- assign path = include.post.url | split: "/" -%}
3 |
4 | {%- include user-trash-symbolic.svg -%}
5 |
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 |
14 |
15 | © {{ global.author }} {{ meta.date.year }}
16 |
17 |
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 |
5 |
12 | {{ page.data.note_url }}
13 | {% if page.data.tags[0] %}
14 | — {% include tags.voxs tags = page.data.tags %}
15 | {% endif %}
16 | {{ page.date.short_day }}, {{ page.date.day }} {{ page.date.short_month }} {{ page.date.year }}
17 | {%- if page.rendered.size > 0 -%}
18 |
19 | {{- page.rendered -}}
20 | {%- endif -%}
21 |
--------------------------------------------------------------------------------
/.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 | {{ page.data.author_id }}
3 |
4 |
5 | Posts
6 | Following
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 |
22 | {% markdown %}
23 | ## Following
24 |
25 | {% if page.data.following[0] %}
26 | {% for user in page.data.following %}
27 |
28 | - [{{ user.name }}]({{ user.id | prepend: global.url }})
29 |
30 | {% endfor %}
31 | {% else %}
32 |
33 | ### Not following anyone
34 |
35 | {% endif %}
36 | {% endmarkdown %}
37 |
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 |
9 | {{ include.post.data.note_url }}
10 | {% if include.post.data.tags[0] %}
11 | — {% include tags.voxs tags = include.post.data.tags %}
12 | {% endif %}
13 | {{ include.post.date.short_day }}, {{ include.post.date.day }} {{ include.post.date.short_month }} {{ include.post.date.year }}
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 | background Layer 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 |
--------------------------------------------------------------------------------