├── .editorconfig
├── .github
└── workflows
│ ├── flatpak-builder.yml
│ └── snap-builder.yml
├── .gitignore
├── .gitlab-ci.yml
├── AppImage
├── AppRun.shim
└── folio.build.appimage.sh
├── COPYING
├── README.md
├── build.flatpak.sh
├── com.toolstack.Folio.json
├── data
├── app.desktop.in
├── app.gschema.xml
├── app.metainfo.xml.in
├── app.service.in
├── editor.desktop.in
├── folio_markdown.xml
├── folio_markdown_dark.xml
├── icons
│ ├── hicolor
│ │ ├── scalable
│ │ │ └── apps
│ │ │ │ └── com.toolstack.Folio.svg
│ │ └── symbolic
│ │ │ └── apps
│ │ │ └── com.toolstack.Folio-symbolic.svg
│ └── meson.build
├── markdownpp.lang
└── meson.build
├── lib
├── meson.build
├── note.vala
├── note_container.vala
├── notebook
│ ├── local_notebook.vala
│ ├── notebook.vala
│ └── trashed_notebook.vala
├── notebook_icon_type.vala
├── notebook_info.vala
├── provider.vala
├── trash.vala
└── util
│ ├── NaturalCollate.vala
│ ├── file_utils.vala
│ ├── fuzzy_matching.vala
│ └── sugar.vala
├── meson.build
├── meta
├── folio-desktop-dark.png
├── folio-desktop.png
├── folio-mobile.png
├── folio-preferences.png
└── folio-three-pane-mode.png
├── po
├── LINGUAS
├── POTFILES
├── ar.po
├── ca.po
├── com.toolstack.Folio.pot
├── cs.po
├── de.po
├── es.po
├── fa.po
├── fi.po
├── fr.po
├── generate-POT-file.sh
├── gl.po
├── hi.po
├── it.po
├── ko.po
├── meson.build
├── nb.po
├── nl_BE.po
├── nl_NL.po
├── oc.po
├── pl.po
├── print-source-files.sh
├── pt_BR.po
├── ru.po
├── sr.po
├── tok.po
├── tr.po
├── uk.po
└── zh_CN.po
├── search-provider
├── SearchProvider.service.in
├── meson.build
├── org.gnome.Shell.SearchProvider2.xml
├── search-provider.ini
└── search-provider.vala
├── snap
└── snapcraft.yaml
├── src
├── application.vala
├── color.vala
├── config.vapi
├── css
│ ├── style-black-hc.css
│ ├── style-black.css
│ ├── style-dark.css
│ └── style.css
├── folio.gresource.xml
├── graphics
│ └── scalable
│ │ └── actions
│ │ ├── apply-symbolic.svg
│ │ ├── dark-mode-symbolic.svg
│ │ ├── document-save-symbolic.svg
│ │ ├── empty-trash-symbolic.svg
│ │ ├── font-x-generic-symbolic.svg
│ │ ├── format-text-highlight-symbolic.svg
│ │ ├── icon-car-symbolic.svg
│ │ ├── icon-code-symbolic.svg
│ │ ├── icon-food-symbolic.svg
│ │ ├── icon-gaming-symbolic.svg
│ │ ├── icon-heart-symbolic.svg
│ │ ├── icon-home-symbolic.svg
│ │ ├── icon-like-symbolic.svg
│ │ ├── icon-music-symbolic.svg
│ │ ├── icon-nature-symbolic.svg
│ │ ├── icon-patch-symbolic.svg
│ │ ├── icon-pin-symbolic.svg
│ │ ├── icon-plant-symbolic.svg
│ │ ├── icon-plus-symbolic.svg
│ │ ├── icon-school-symbolic.svg
│ │ ├── icon-science-symbolic.svg
│ │ ├── icon-settings-symbolic.svg
│ │ ├── icon-skull-symbolic.svg
│ │ ├── icon-sport-symbolic.svg
│ │ ├── icon-star-symbolic.svg
│ │ ├── icon-toki-pona-symbolic.svg
│ │ ├── icon-toki-symbolic.svg
│ │ ├── icon-travel-symbolic.svg
│ │ ├── icon-work-symbolic.svg
│ │ ├── insert-code-symbolic.svg
│ │ ├── insert-horizontal-rule-symbolic.svg
│ │ ├── object-flip-horizontal-symbolic.svg
│ │ ├── reset-symbolic.svg
│ │ ├── stretch-horizontal-symbolic.svg
│ │ ├── toolbar-symbolic.svg
│ │ ├── view-list-ordered-symbolic.svg
│ │ └── view-sort-descending-symbolic.svg
├── main.vala
├── meson.build
└── ui
│ ├── edit_view
│ ├── cheatsheet
│ │ ├── markdown_cheatsheet.blp
│ │ ├── markdown_cheatsheet.md
│ │ └── markdown_cheatsheet.vala
│ ├── edit_view.blp
│ ├── edit_view.vala
│ └── toolbar
│ │ ├── toolbar.blp
│ │ └── toolbar.vala
│ ├── file_editor
│ ├── file_editor_window.blp
│ └── file_editor_window.vala
│ ├── fuzzy_string_sorter.vala
│ ├── meson.build
│ ├── popup
│ ├── notebook_selection_popup
│ │ ├── notebook_list_item.blp
│ │ ├── notebook_list_item.vala
│ │ ├── notebook_selection_popup.blp
│ │ └── notebook_selection_popup.vala
│ └── shortcut_window.blp
│ ├── preferences
│ ├── preferences.blp
│ └── preferences.vala
│ ├── strings.vala
│ ├── widgets
│ ├── markdown
│ │ ├── heading_popover.blp
│ │ ├── heading_popover.vala
│ │ ├── markdown_buffer.vala
│ │ └── markdown_view.vala
│ ├── notebook_icon
│ │ ├── notebook_preview.blp
│ │ └── notebook_preview.vala
│ └── theme_selector
│ │ ├── theme_selector.blp
│ │ └── theme_selector.vala
│ └── window
│ ├── app_menu
│ ├── app_menu.blp
│ └── app_menu.vala
│ ├── font_scale
│ ├── font_scale.blp
│ └── font_scale.vala
│ ├── notebooks_bar
│ ├── icon.blp
│ ├── icon.vala
│ ├── notebook_create_popup.blp
│ ├── notebook_create_popup.vala
│ ├── notebook_menu.blp
│ ├── notebook_menu.vala
│ ├── notebook_sidebar_item.vala
│ ├── notebooks_bar.blp
│ └── notebooks_bar.vala
│ ├── sidebar
│ ├── note_card.blp
│ ├── note_card.vala
│ ├── note_create_popup.blp
│ ├── note_create_popup.vala
│ ├── note_menu.blp
│ └── note_menu.vala
│ ├── window.blp
│ ├── window.vala
│ └── window_model.vala
└── subprojects
└── blueprint-compiler.wrap
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | indent_style = tab
9 | indent_size = 4
10 |
11 | [*.po]
12 | insert_final_newline = false
13 |
14 | [*.pot]
15 | insert_final_newline = false
16 |
17 | [*.build]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.xml]
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.blp]
26 | indent_style = space
27 | indent_size = 2
28 |
--------------------------------------------------------------------------------
/.github/workflows/flatpak-builder.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [published]
4 | name: Build flapak on release
5 | jobs:
6 | flatpak:
7 | name: "Flatpak"
8 | runs-on: ubuntu-latest
9 | container:
10 | image: bilelmoussaoui/flatpak-github-actions:gnome-48
11 | options: --privileged
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: flatpak/flatpak-github-actions/flatpak-builder@v6.3
15 | with:
16 | bundle: Folio.flatpak
17 | manifest-path: com.toolstack.Folio.json
18 | cache-key: flatpak-builder-${{ github.sha }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/snap-builder.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [published]
4 | name: Build snap on release
5 | jobs:
6 | snap:
7 | name: Build snap
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: snapcore/action-build@v1
12 | id: snapcraft
13 | - uses: actions/upload-artifact@v4
14 | with:
15 | name: snap
16 | path: ${{ steps.snapcraft.outputs.snap }}
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*~
2 | /subprojects/*/
3 | _build
4 | .flatpak
5 | .var
6 | .flatpak-builder
7 | flatpak
8 | AppImage/build
9 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | # You can override the included template(s) by including variable overrides
2 | # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
3 | # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
4 | # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
5 | # Note that environment variables can be set in several places
6 | # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
7 | stages:
8 | - test
9 | sast:
10 | stage: test
11 | include:
12 | - template: Security/SAST.gitlab-ci.yml
13 |
--------------------------------------------------------------------------------
/AppImage/AppRun.shim:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Shamelessly stolen from the linuxdeploy gtk plugin
4 |
5 | # make sure errors in sourced scripts will cause this script to stop
6 | set -e
7 |
8 | # we need to tell gnome where to look for the Folio settings schema
9 | # since it's not in the system directory. Instead use the one we have
10 | # packaged with the appimage.
11 | this_dir="$(readlink -f "$(dirname "$0")")"
12 | export GSETTINGS_SCHEMA_DIR=${this_dir}/usr/local/share/glib-2.0/schemas/
13 |
14 | exec "$this_dir"/usr/bin/com.toolstack.Folio "$@"
15 |
--------------------------------------------------------------------------------
/AppImage/folio.build.appimage.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Store the current working directory
4 | export ROOTDIR=$PWD
5 |
6 | # Get our current base directory name
7 | export BASEDIR=${PWD##*/}
8 |
9 | if [ "$BASEDIR" != "AppImage" ]; then
10 | echo "Not in AppImage directory... aborting!"
11 | exit
12 | fi
13 |
14 | # Create/clean the buid directory
15 | if [ -d "build" ]; then
16 | rm -rf build
17 | fi
18 | mkdir build
19 |
20 | if [ -f "Folio-x86_64.AppImage" ]; then
21 | rm Folio-x86_64.AppImage
22 | fi
23 |
24 | # Setup our export variables for the build
25 | export DESTDIR="../AppImage/build"
26 | export NO_STRIP=true
27 |
28 | # Execute the build/install
29 | cd $ROOTDIR/../build
30 | ninja install
31 |
32 | # Complile the gschema
33 | cd $ROOTDIR/build/usr/local/share/glib-2.0/schemas
34 | glib-compile-schemas .
35 |
36 | # Change back to the build directory
37 | cd $ROOTDIR/build
38 |
39 | # Create the appimage
40 | linuxdeploy --appdir=. -d usr/local/share/applications/com.toolstack.Folio.desktop -i usr/local/share/icons/hicolor/scalable/apps/com.toolstack.Folio.svg -e usr/local/bin/com.toolstack.Folio --custom-apprun=../AppRun.shim --output appimage
41 |
42 | # Move the appimage up one level.
43 | mv Folio-x86_64.AppImage ..
44 |
45 | # Cleanup
46 | cd $ROOTDIR
47 | rm -rf build
48 |
--------------------------------------------------------------------------------
/build.flatpak.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | # Store the current working directory
4 | export ROOTDIR=$PWD
5 |
6 | # Get our current base directory name
7 | export BASEDIR=${PWD##*/}
8 |
9 | if [ "$BASEDIR" != "Folio" ]; then
10 | echo "Not in Folio root directory... aborting!"
11 | exit
12 | fi
13 |
14 | # Create/clean the build directory
15 | if [ -d "flatpak" ]; then
16 | rm -rf flatpak
17 | fi
18 |
19 | mkdir flatpak
20 | cd flatpak
21 |
22 | flatpak-builder --repo=repo --force-clean flatpak ../com.toolstack.Folio.json
23 | flatpak build-bundle repo Folio-x86_64.flatpak --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo --arch=x86_64 com.toolstack.Folio master
24 |
--------------------------------------------------------------------------------
/com.toolstack.Folio.json:
--------------------------------------------------------------------------------
1 | {
2 | "app-id" : "com.toolstack.Folio",
3 | "runtime" : "org.gnome.Platform",
4 | "runtime-version" : "48",
5 | "sdk" : "org.gnome.Sdk",
6 | "command" : "com.toolstack.Folio",
7 | "finish-args" : [
8 | "--share=ipc",
9 | "--socket=fallback-x11",
10 | "--device=dri",
11 | "--socket=wayland",
12 | "--filesystem=home"
13 | ],
14 | "cleanup" : [
15 | "/include",
16 | "/lib/pkgconfig",
17 | "/man",
18 | "/share/doc",
19 | "/share/gtk-doc",
20 | "/share/man",
21 | "/share/pkgconfig",
22 | "/share/vala",
23 | "*.la",
24 | "*.a"
25 | ],
26 | "modules": [
27 | {
28 | "name" : "blueprint-compiler",
29 | "buildsystem" : "meson",
30 | "cleanup": [
31 | "*"
32 | ],
33 | "sources" : [{
34 | "type" : "git",
35 | "url" : "https://gitlab.gnome.org/jwestman/blueprint-compiler.git",
36 | "tag" : "v0.16.0",
37 | "commit" : "04ef0944db56ab01307a29aaa7303df6067cb3c0"
38 | }]
39 | },
40 | {
41 | "name" : "Folio",
42 | "builddir" : true,
43 | "buildsystem" : "meson",
44 | "sources" : [{
45 | "type" : "git",
46 | "url" : "https://github.com/toolstack/Folio",
47 | "tag" : "25.02",
48 | "commit" : "446a3968d8c16c920df02959390c007571c79999"
49 | }]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/data/app.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | Terminal=false
4 | Exec=@app_id@
5 | DBusActivatable=true
6 | StartupWMClass=@app_id@
7 |
8 | Name=Folio
9 | GenericName=Notes Manager
10 | Comment=Take notes
11 |
12 | Keywords=Notebook;Note;Notes;Text;Markdown;Notepad;Write;School;Post-it;Sticky;
13 | Icon=@app_id@
14 | Categories=GTK;Utility;TextEditor;GNOME;
15 | StartupNotify=true
16 | X-Purism-FormFactor=Workstation;Mobile;
17 | X-GNOME-SingleWindow=true
18 | Actions=new-note;new-notebook;markdown-cheatsheet;preferences;
19 |
20 | [Desktop Action new-note]
21 | Name=New Note
22 | Exec=@app_id@ --new-note
23 |
24 | [Desktop Action new-notebook]
25 | Name=New Notebook
26 | Exec=@app_id@ --new-notebook
27 |
28 | [Desktop Action markdown-cheatsheet]
29 | Name=Markdown Cheatsheet
30 | Exec=@app_id@ --markdown-cheatsheet
31 |
32 | [Desktop Action preferences]
33 | Name=Preferences
34 | Exec=@app_id@ --preferences
35 |
--------------------------------------------------------------------------------
/data/app.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 'Sans 10'
6 | Note Font
7 | The font notes will be displayed in
8 |
9 |
10 | 'Monospace 10'
11 | Monospace Font
12 | The font code will be displayed in
13 |
14 |
15 | 720
16 | Use Fixed Width For Notes
17 | Use a fixed width for the text area of a note
18 |
19 |
20 | false
21 | OLED Mode
22 | Makes the dark theme pitch black
23 |
24 |
25 | true
26 | Enable Toolbar
27 | Displays the formatting toolbar at the bottom of the note
28 |
29 |
30 | true
31 | Enable Cheatsheet
32 | Adds a button to the markdown reference sheet on the right of the formatting toolbar
33 |
34 |
35 | false
36 | 3-Pane Layout
37 | Expands the notebook list to include the notebook name
38 |
39 |
40 | '~/.var/app/com.toolstack.Folio/data'
41 | Notes Storage Location
42 | Where the notebooks are stored (requires app restart)
43 |
44 |
45 | '~/.var/app/com.toolstack.Folio/data'
46 | Trash Storage Location
47 | Where the trash folder is located (requires app restart)
48 |
49 |
50 | false
51 | Show line numbers
52 | Displays line numbers at the left of the note
53 |
54 |
55 | false
56 | Show all notes
57 | Displays the "All Notes" notebook at the top of the notebook list
58 |
59 |
60 | false
61 | Enable autosave
62 | Automatically saves the current note every 30 seconds if the contents have changed (requires app restart)
63 |
64 |
65 | false
66 | Don't hide the Trash folder
67 | The trash folder is set as a hidden folder by default, this option makes it visible
68 |
69 |
70 | 0
71 | Note sort order
72 | The sort order of the note list
73 |
74 |
75 | 0
76 | Notebook sort order
77 | The sort order of the notebook list
78 |
79 |
80 | 0
81 | URL detection level
82 | How aggressive to look for URLs in markdown text
83 |
84 |
85 | '1.0'
86 | Line spacing
87 | The line spacing for the note text
88 |
89 |
90 | false
91 | Allow long notebook names
92 | The notebook column will expand to the width of the longest notebook name
93 |
94 |
95 | false
96 | Allow long note names
97 | The note list column will expand to the width of the longest note name
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | 'default'
110 |
111 |
112 |
113 |
114 | 720
115 |
116 |
117 | 512
118 |
119 |
120 | false
121 |
122 |
123 | 100
124 |
125 |
126 | ''
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/data/app.service.in:
--------------------------------------------------------------------------------
1 | [D-BUS Service]
2 | Name=@app_id@
3 | Exec=@bindir@/@app_id@ --gapplication-service
4 |
--------------------------------------------------------------------------------
/data/editor.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | Terminal=false
4 | Exec=@app_id@ --file %u
5 | NoDisplay=true
6 |
7 | Name=Folio
8 |
9 | Icon=@app_id@
10 | StartupNotify=true
11 | MimeType=text/x-markdown;text/markdown;
--------------------------------------------------------------------------------
/data/folio_markdown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Greg Ross
4 | <_description>The style scheme for Folio
5 |
6 |
7 | light
8 | folio-dark
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/data/folio_markdown_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Greg Ross
4 | <_description>The style scheme for Folio
5 |
6 |
7 | dark
8 | folio
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/com.toolstack.Folio-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
44 |
--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
1 | scalable_dir = 'hicolor' / 'scalable' / 'apps'
2 | install_data(
3 | scalable_dir / ('@0@.svg').format(app_id),
4 | install_dir: get_option('datadir') / 'icons' / scalable_dir,
5 | )
6 |
7 | symbolic_dir = 'hicolor' / 'symbolic' / 'apps'
8 | install_data(
9 | symbolic_dir / ('@0@-symbolic.svg').format(app_id),
10 | install_dir: get_option('datadir') / 'icons' / symbolic_dir
11 | )
12 |
--------------------------------------------------------------------------------
/data/markdownpp.lang:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
23 |
24 | text/x-markdown
25 | *.markdown;*.md;*.mkd
26 | <!--
27 | -->
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
47 | ^(#[ ])[^#].*
48 |
49 |
50 |
51 |
52 |
53 |
54 | ^(##[ ])[^#].*
55 |
56 |
57 |
58 |
59 |
60 |
61 | ^(#{3}[ ])[^#].*
62 |
63 |
64 |
65 |
66 |
67 |
68 | ^(#{4}[ ])[^#].*
69 |
70 |
71 |
72 |
73 |
74 |
75 | ^(#{5}[ ])[^#].*
76 |
77 |
78 |
79 |
80 |
81 |
82 | ^(#{6}[ ])[^#].*
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | conf = configuration_data()
2 | conf.set('app_id', app_id)
3 | conf.set('version', version)
4 | conf.set('bindir', get_option('prefix') / get_option('bindir'))
5 |
6 | service_file = configure_file(
7 | input: 'app.service.in',
8 | output: '@0@.service'.format(app_id),
9 | configuration: conf,
10 | )
11 |
12 | install_data(
13 | service_file,
14 | install_dir: get_option('datadir') / 'dbus-1' / 'services'
15 | )
16 |
17 | desktop_conf = configure_file(
18 | input: 'app.desktop.in',
19 | output: '@0@.desktop.in'.format(app_id),
20 | configuration: conf
21 | )
22 |
23 | desktop_file = i18n.merge_file(
24 | input: desktop_conf,
25 | output: '@0@.desktop'.format(app_id),
26 | type: 'desktop',
27 | po_dir: '../po',
28 | install: true,
29 | install_dir: get_option('datadir') / 'applications'
30 | )
31 |
32 | editor_conf = configure_file(
33 | input: 'editor.desktop.in',
34 | output: '@0@-editor.desktop.in'.format(app_id),
35 | configuration: conf
36 | )
37 |
38 | editor_file = i18n.merge_file(
39 | input: editor_conf,
40 | output: '@0@-editor.desktop'.format(app_id),
41 | type: 'desktop',
42 | po_dir: '../po',
43 | install: true,
44 | install_dir: get_option('datadir') / 'applications'
45 | )
46 |
47 | desktop_utils = find_program('desktop-file-validate', required: false)
48 | if desktop_utils.found()
49 | test('Validate desktop file', desktop_utils,
50 | args: [desktop_file]
51 | )
52 | endif
53 |
54 | appstream_conf = configure_file(
55 | input: 'app.metainfo.xml.in',
56 | output: '@0@.metainfo.xml.in'.format(app_id),
57 | configuration: conf
58 | )
59 |
60 | appstream_file = i18n.merge_file(
61 | input: appstream_conf,
62 | output: '@0@.appdata.xml'.format(app_id),
63 | po_dir: '../po',
64 | install: true,
65 | install_dir: get_option('datadir') / 'metainfo'
66 | )
67 |
68 | appstream_util = find_program('appstream-util', required: false)
69 | if appstream_util.found()
70 | test('Validate appstream file', appstream_util,
71 | args: ['validate', '--nonet', appstream_file]
72 | )
73 | endif
74 |
75 | gschema_file = configure_file(
76 | input: 'app.gschema.xml',
77 | output: app_id + '.gschema.xml',
78 | configuration: conf,
79 | )
80 |
81 | install_data(
82 | gschema_file,
83 | install_dir: get_option('datadir') / 'glib-2.0' / 'schemas'
84 | )
85 |
86 | compile_schemas = find_program('glib-compile-schemas', required: false)
87 | if compile_schemas.found()
88 | test('Validate schema file', compile_schemas,
89 | args: ['--strict', '--dry-run', meson.current_source_dir()]
90 | )
91 | endif
92 |
93 | install_data('folio_markdown.xml',
94 | install_dir: get_option('datadir') / 'gtksourceview-5' / 'styles'
95 | )
96 | install_data('folio_markdown_dark.xml',
97 | install_dir: get_option('datadir') / 'gtksourceview-5' / 'styles'
98 | )
99 | install_data('markdownpp.lang',
100 | install_dir: get_option('datadir') / 'gtksourceview-5' / 'language-specs'
101 | )
102 |
103 | subdir('icons')
104 |
--------------------------------------------------------------------------------
/lib/meson.build:
--------------------------------------------------------------------------------
1 |
2 | lib_sources = files(
3 | 'note.vala',
4 | 'note_container.vala',
5 | 'notebook_info.vala',
6 | 'provider.vala',
7 | 'trash.vala',
8 | 'notebook_icon_type.vala',
9 |
10 | 'notebook/notebook.vala',
11 | 'notebook/local_notebook.vala',
12 | 'notebook/trashed_notebook.vala',
13 |
14 | 'util/fuzzy_matching.vala',
15 | 'util/file_utils.vala',
16 | 'util/sugar.vala',
17 | 'util/NaturalCollate.vala',
18 | )
19 |
--------------------------------------------------------------------------------
/lib/note.vala:
--------------------------------------------------------------------------------
1 |
2 | public class Folio.Note : Object {
3 |
4 | public string name {
5 | get { return _name; }
6 | }
7 |
8 | public string extension {
9 | get { return _extension; }
10 | }
11 |
12 | public inline string file_name {
13 | owned get { return @"$_name.$_extension"; }
14 | }
15 |
16 | public inline bool is_markdown {
17 | get { return _extension.down () == "md"; }
18 | }
19 |
20 | public inline bool is_text {
21 | get { return _extension.down () == "txt"; }
22 | }
23 |
24 | public string path {
25 | owned get { return @"$(_notebook.path)/$file_name"; }
26 | }
27 |
28 | public DateTime time_modified {
29 | get { return _time_modified; }
30 | }
31 |
32 | public Notebook notebook {
33 | get { return _notebook; }
34 | }
35 |
36 | public string id {
37 | owned get { return @"$(_notebook.name)/$name"; }
38 | }
39 |
40 | private string _name;
41 | private string _extension;
42 | private Notebook _notebook;
43 | private DateTime _time_modified;
44 |
45 | public Note (string name, string extension, Notebook notebook, DateTime time_modified) {
46 | this._name = name;
47 | this._extension = extension;
48 | this._notebook = notebook;
49 | this._time_modified = time_modified;
50 | }
51 |
52 | public void change (string name, string extension, Notebook notebook, DateTime time_modified) {
53 | this._name = name;
54 | this._extension = extension;
55 | this._notebook = notebook;
56 | this._time_modified = time_modified;
57 | }
58 |
59 | public string? load_text () {
60 | uint8[] text_data = {};
61 |
62 | try {
63 | var file = File.new_for_path (path);
64 | if (!file.query_exists ()) {
65 | file.create (FileCreateFlags.REPLACE_DESTINATION);
66 | } else {
67 | string etag_out;
68 | file.load_contents (null, out text_data, out etag_out);
69 | // Make sure the last character of the file is a return, otherwise some of the regex's will break.
70 | if (text_data[text_data.length - 1] != 10) { text_data += 10; }
71 | }
72 | update_note_time ();
73 | } catch (Error e) {
74 | error (e.message);
75 | }
76 | if (text_data.length > 0) {
77 | return (string)text_data;
78 | }
79 | return "";
80 | }
81 |
82 | public bool validate_save () {
83 | // Lets make sure the file hasn't changed on disk before saving it.
84 | var file_handle = File.new_for_path (path);
85 | FileInfo file_info;
86 | DateTime file_time;
87 |
88 | try {
89 | file_info = file_handle.query_info (FileAttribute.TIME_MODIFIED, FileQueryInfoFlags.NONE);
90 | file_time = file_info.get_modification_date_time ();
91 | } catch (Error e) {
92 | file_time = this._time_modified;
93 | }
94 |
95 | if ( !file_time.to_local ().equal (this._time_modified)) {
96 | return false;
97 | }
98 |
99 | return true;
100 | }
101 |
102 | public DateTime update_note_time () {
103 | var file_handle = File.new_for_path (path);
104 | FileInfo file_info;
105 | DateTime file_time = _time_modified;
106 |
107 | try {
108 | file_info = file_handle.query_info (FileAttribute.TIME_MODIFIED, FileQueryInfoFlags.NONE);
109 | file_time = file_info.get_modification_date_time ();
110 | } catch (Error e) {}
111 |
112 | if (!file_time.equal (_time_modified)) {
113 | _time_modified = file_time;
114 | }
115 |
116 | return _time_modified;
117 | }
118 |
119 | public void save (string text) {
120 | // Lets make sure the file hasn't changed on disk before saving it.
121 | var file_handle = File.new_for_path (path);
122 |
123 | // Save the file.
124 | FileUtils.save_to (file_handle, text);
125 |
126 | // Now get the updated file time and store it in the note.
127 | update_note_time ();
128 | }
129 |
130 | public bool equals (Note other) {
131 | return this.name == other.name && this.notebook == other.notebook;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/lib/note_container.vala:
--------------------------------------------------------------------------------
1 | using Gee;
2 |
3 | public interface Folio.NoteContainer : Object, ListModel {
4 |
5 | public abstract string name { get; }
6 |
7 | public abstract Gee.List? loaded_notes { get; }
8 |
9 | public abstract void load ();
10 | public abstract void unload ();
11 | }
12 |
13 | public class Folio.SimpleNoteContainer : Object, ListModel, NoteContainer {
14 |
15 | public string name { get { return _name; } }
16 |
17 | public Gee.List? loaded_notes { get { return _loaded_notes; } }
18 |
19 | public delegate Gee.List Loader ();
20 |
21 | private string _name;
22 | private Loader _load;
23 | private Gee.List? _loaded_notes;
24 |
25 | public SimpleNoteContainer (string name, Loader loader) {
26 | _name = name;
27 | _load = () => { return loader (); };
28 | }
29 |
30 | public void load () {
31 | if (_loaded_notes != null) return;
32 | _loaded_notes = _load ();
33 | }
34 |
35 | public void unload () {
36 | _loaded_notes = null;
37 | }
38 |
39 | public Type get_item_type () {
40 | return typeof (Note);
41 | }
42 |
43 | public uint get_n_items () {
44 | if (_loaded_notes == null)
45 | error (@"Container \"$name\": Notes haven't loaded yet");
46 | return _loaded_notes.size;
47 | }
48 |
49 | public Object? get_item (uint i) {
50 | if (_loaded_notes == null)
51 | error (@"Container \"$name\": Notes haven't loaded yet");
52 | return (i >= _loaded_notes.size) ? null : _loaded_notes.@get((int) i);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/notebook/local_notebook.vala:
--------------------------------------------------------------------------------
1 | using Gee;
2 | using NaturalCollate;
3 |
4 | public class Folio.LocalNotebook : Object, ListModel, NoteContainer, Notebook {
5 |
6 | public string name { get { return info.name; } }
7 |
8 | public string path {
9 | owned get { return @"$(provider.notes_dir)/$name"; }
10 | }
11 |
12 | public NotebookInfo info {
13 | get { return _info; }
14 | }
15 |
16 | public Gee.List? loaded_notes {
17 | get { return _loaded_notes; }
18 | }
19 |
20 | ArrayList? _loaded_notes = null;
21 |
22 | NotebookInfo _info;
23 | Provider provider;
24 |
25 | private bool disable_hidden_trash;
26 | private int note_sort_order;
27 |
28 | construct {
29 | var settings = new Settings (Config.APP_ID);
30 | disable_hidden_trash = settings.get_boolean ("disable-hidden-trash");
31 | note_sort_order = settings.get_int ("note-sort-order");
32 | }
33 |
34 | public LocalNotebook (Provider provider, NotebookInfo info) {
35 | this.provider = provider;
36 | this._info = info;
37 | }
38 |
39 | public void load () {
40 | if (_loaded_notes != null) return;
41 | _loaded_notes = new ArrayList ();
42 | var dir = File.new_for_path (path);
43 | try {
44 | var enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.TIME_MODIFIED + "," + FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_DISPLAY_NAME, 0);
45 | FileInfo file_info;
46 | while ((file_info = enumerator.next_file ()) != null) {
47 | var content_type = file_info.get_content_type ();
48 | if (content_type == null || !(content_type.has_prefix ("text") || content_type.has_prefix ("application")))
49 | continue;
50 | var name = file_info.get_display_name ();
51 | if (name[0] == '.')
52 | continue;
53 | var dot_i = name.last_index_of_char ('.');
54 | if (dot_i == -1)
55 | continue;
56 | var extension = name.substring (dot_i + 1);
57 | name = name.substring (0, dot_i);
58 | var mod_time = (!) file_info.get_modification_date_time ().to_timezone (new TimeZone.local ());
59 | _loaded_notes.add (new Note (
60 | name,
61 | extension,
62 | this,
63 | mod_time
64 | ));
65 | }
66 | } catch (Error e) {
67 | error (@"Notebook loading failed: $(e.message)\n");
68 | }
69 | sort_notes (note_sort_order);
70 | }
71 |
72 | public void sort_notes (int note_sort_order) {
73 | this.note_sort_order = note_sort_order;
74 |
75 | switch (note_sort_order) {
76 | case 1:
77 | _loaded_notes.sort ((a, b) => a.time_modified.compare(b.time_modified));
78 | break;
79 | case 2:
80 | _loaded_notes.sort ((a, b) => strcmp (a.name, b.name));
81 | break;
82 | case 3:
83 | _loaded_notes.sort ((a, b) => strcmp (b.name, a.name));
84 | break;
85 | case 4:
86 | _loaded_notes.sort ((a, b) => NaturalCollate.compare (a.name, b.name));
87 | break;
88 | case 5:
89 | _loaded_notes.sort ((a, b) => NaturalCollate.compare (b.name, a.name));
90 | break;
91 | default:
92 | _loaded_notes.sort ((a, b) => b.time_modified.compare(a.time_modified));
93 | break;
94 | }
95 | }
96 |
97 | public void unload () {
98 | _loaded_notes = null;
99 | }
100 |
101 | public void change (Provider provider, NotebookInfo info) {
102 | this.provider = provider;
103 | this._info = info;
104 | }
105 |
106 | public Note new_note (string name, string extension) throws ProviderError {
107 | load ();
108 | var file_name = @"$name.$extension";
109 | var path = @"$path/$file_name";
110 | var file = File.new_for_path (path);
111 | if (file.query_exists ()) {
112 | throw new ProviderError.ALREADY_EXISTS (@"Note \"$name\" already exists in $(this.name)");
113 | }
114 | try {
115 | file.create (FileCreateFlags.NONE);
116 | } catch (Error e) {
117 | throw new ProviderError.COULDNT_CREATE_FILE("Couldn't create note at \"$path\"");
118 | }
119 | var note = new Note (name, extension, this, new DateTime.now ());
120 | _loaded_notes.insert (0, note);
121 | items_changed (0, 0, 1);
122 | return note;
123 | }
124 |
125 | public void change_note (Note note, string name, string extension) throws ProviderError {
126 | load ();
127 | if (note.name != name || note.extension != extension) {
128 | var original_path = note.path;
129 | var original_file = File.new_for_path (original_path);
130 | var file_name = @"$name.$extension";
131 | var path = @"$path/$file_name";
132 | var file = File.new_for_path (path);
133 | if (file.query_exists ()) {
134 | throw new ProviderError.ALREADY_EXISTS (@"Note at $path already exists");
135 | }
136 | try {
137 | original_file.set_display_name(file_name);
138 | } catch (Error e) {
139 | throw new ProviderError.COULDNT_CREATE_FILE (@"Couldn't change $original_path to $path: $(e.message)");
140 | }
141 |
142 | note.change (name, extension, this, new DateTime.now ());
143 | int i = _loaded_notes.index_of (note);
144 | items_changed (i, 1, 1);
145 | }
146 | }
147 |
148 | public void delete_note (Note note) throws ProviderError {
149 | load ();
150 | var path = note.path;
151 | var file = File.new_for_path (path);
152 | if (!file.query_exists ()) {
153 | throw new ProviderError.COULDNT_DELETE (@"Couldn't delete note at $path");
154 | }
155 | var trashed_dir_path = "";
156 | if (disable_hidden_trash) {
157 | trashed_dir_path = @"$(provider.trash_dir)/Trash/$(note.notebook.name)";
158 | } else {
159 | trashed_dir_path = @"$(provider.trash_dir)/.trash/$(note.notebook.name)";
160 | }
161 | var trashed_path = @"$trashed_dir_path/$(note.file_name)";
162 | try {
163 | var trashed_dir = File.new_for_path (trashed_dir_path);
164 | if (!trashed_dir.query_exists ()) {
165 | trashed_dir.make_directory_with_parents ();
166 | }
167 | var trashed_file = File.new_for_path (trashed_path);
168 | file.move (trashed_file, FileCopyFlags.OVERWRITE);
169 | provider.trash.unload ();
170 | } catch (Error e) {
171 | throw new ProviderError.COULDNT_DELETE (@"Couldn't move note from $path, to $trashed_path");
172 | }
173 | int i = _loaded_notes.index_of (note);
174 | _loaded_notes.remove_at (i);
175 | items_changed (i, 1, 0);
176 | }
177 |
178 | public Type get_item_type () {
179 | return typeof (Note);
180 | }
181 |
182 | public uint get_n_items () {
183 | load ();
184 | if (_loaded_notes == null)
185 | error (@"Notebook \"$name\": Notes haven't loaded yet");
186 | return _loaded_notes.size;
187 | }
188 |
189 | public uint get_index_of(Note? note){
190 | load ();
191 | if (note != null && _loaded_notes != null){
192 | return loaded_notes.index_of (note);
193 | }
194 | return -1;
195 | }
196 |
197 | public Object? get_item (uint i) {
198 | load ();
199 | if (_loaded_notes == null)
200 | error (@"Notebook \"$name\": Notes haven't loaded yet");
201 | return (i >= _loaded_notes.size) ? null : _loaded_notes.@get((int) i);
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/lib/notebook/notebook.vala:
--------------------------------------------------------------------------------
1 | using Gee;
2 |
3 | public interface Folio.Notebook : Object, ListModel, NoteContainer {
4 |
5 | public Gdk.RGBA color { get { return info.color; } }
6 | public NotebookIconType icon_type { get { return info.icon_type; } }
7 |
8 | public abstract NotebookInfo info { get; }
9 |
10 | public abstract string path { owned get; }
11 |
12 | public abstract Note new_note (string name, string extension = "md") throws ProviderError;
13 | public abstract void change_note (Note note, string name, string extension = note.extension) throws ProviderError;
14 | public abstract void delete_note (Note note) throws ProviderError;
15 | public abstract uint get_index_of(Note? note);
16 | public abstract void sort_notes (int note_sort_order);
17 |
18 | public bool equals (Notebook other) {
19 | return this.name == other.name;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/notebook/trashed_notebook.vala:
--------------------------------------------------------------------------------
1 | using Gee;
2 |
3 | public class Folio.TrashedNotebook : Object, ListModel, NoteContainer, Notebook {
4 |
5 | public string name { get { return info.name; } }
6 |
7 | public string path {
8 | owned get { return @"$(trash.path)/$name"; }
9 | }
10 |
11 | public NotebookInfo info {
12 | get { return _info; }
13 | }
14 |
15 | public Gee.List? loaded_notes {
16 | get { return null; }
17 | }
18 |
19 | NotebookInfo _info;
20 | Trash trash;
21 |
22 | public TrashedNotebook (Trash trash, NotebookInfo info) {
23 | this.trash = trash;
24 | this._info = info;
25 | }
26 |
27 | public void load () {}
28 | public void unload () {}
29 |
30 | public void sort_notes (int note_sort_order) {}
31 |
32 | public Note new_note (string name, string extension) throws ProviderError {
33 | error ("Can't create notes in trash");
34 | }
35 |
36 | public void change_note (Note note, string name, string extension) throws ProviderError {
37 | error ("Can't edit notes in trash");
38 | }
39 |
40 | public void delete_note (Note note) throws ProviderError {
41 | trash.delete_note (note);
42 | }
43 |
44 | public Type get_item_type () {
45 | return typeof (Note);
46 | }
47 |
48 | public uint get_n_items () {
49 | return 0;
50 | }
51 |
52 | public Object? get_item (uint i) {
53 | return null;
54 | }
55 |
56 | public uint get_index_of(Note? note){
57 | return -1;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/notebook_icon_type.vala:
--------------------------------------------------------------------------------
1 |
2 | public enum Folio.NotebookIconType {
3 | FIRST,
4 | INITIALS,
5 | INITIALS_CAMEL_CASE,
6 | INITIALS_SNAKE_CASE,
7 | PREDEFINED_ICON,
8 | CUSTOM_ICON;
9 |
10 | public const NotebookIconType DEFAULT = FIRST;
11 |
12 | public string to_string () {
13 | switch (this) {
14 | case FIRST:
15 | return "first";
16 | case INITIALS:
17 | return "initials";
18 | case INITIALS_CAMEL_CASE:
19 | return "camel_case";
20 | case INITIALS_SNAKE_CASE:
21 | return "snake_case";
22 | case PREDEFINED_ICON:
23 | return "predefined_icon";
24 | case CUSTOM_ICON:
25 | return "custom_icon";
26 | default:
27 | assert_not_reached();
28 | }
29 | }
30 |
31 | public static NotebookIconType from_string (string text) {
32 | switch (text) {
33 | case "first":
34 | return FIRST;
35 | case "initials":
36 | return INITIALS;
37 | case "camel_case":
38 | return INITIALS_CAMEL_CASE;
39 | case "snake_case":
40 | return INITIALS_SNAKE_CASE;
41 | case "predefined_icon":
42 | return PREDEFINED_ICON;
43 | case "custom_icon":
44 | return CUSTOM_ICON;
45 | default:
46 | assert_not_reached();
47 | }
48 | }
49 |
50 | public static NotebookIconType from_int (int integer) {
51 | switch (integer) {
52 | case 0:
53 | return FIRST;
54 | case 1:
55 | return INITIALS;
56 | case 2:
57 | return INITIALS_CAMEL_CASE;
58 | case 3:
59 | return INITIALS_SNAKE_CASE;
60 | case 4:
61 | return PREDEFINED_ICON;
62 | case 5:
63 | return CUSTOM_ICON;
64 | default:
65 | assert_not_reached();
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/notebook_info.vala:
--------------------------------------------------------------------------------
1 |
2 | public class Folio.NotebookInfo {
3 |
4 | public string name;
5 | public Gdk.RGBA color;
6 | public NotebookIconType icon_type;
7 | public string? icon_name;
8 | public DateTime time_modified;
9 | public string? custom_icon_label;
10 |
11 | public NotebookInfo (
12 | string name,
13 | Gdk.RGBA color = Gdk.RGBA (),
14 | NotebookIconType icon_type = NotebookIconType.DEFAULT,
15 | string? icon_name = null,
16 | DateTime? time_modified = null,
17 | string? custom_icon_label = null
18 | ) {
19 | this.name = name;
20 | this.color = color;
21 | this.icon_type = icon_type;
22 | this.icon_name = icon_name;
23 | this.time_modified = time_modified == null ? new DateTime.now () : time_modified;
24 | this.custom_icon_label = custom_icon_label;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/trash.vala:
--------------------------------------------------------------------------------
1 | using Gee;
2 |
3 | public class Folio.Trash : Object, ListModel, NoteContainer {
4 |
5 | public string name {
6 | get { return _name; }
7 | }
8 |
9 | public Gee.List? loaded_notes {
10 | get { return _loaded_notes; }
11 | }
12 |
13 | public string path {
14 | owned get {
15 | if (disable_hidden_trash) {
16 | return @"$(provider.trash_dir)/Trash";
17 | } else {
18 | return @"$(provider.trash_dir)/.trash";
19 | }
20 | }
21 | }
22 |
23 | private ArrayList? _loaded_notes = null;
24 | private Provider provider;
25 | private string _name;
26 |
27 | private bool disable_hidden_trash;
28 |
29 | construct {
30 | var settings = new Settings (Config.APP_ID);
31 | disable_hidden_trash = settings.get_boolean ("disable-hidden-trash");
32 | }
33 |
34 | public Trash (Provider provider, string name) {
35 | this.provider = provider;
36 | this._name = name;
37 | }
38 |
39 | public void load () {
40 | if (_loaded_notes != null) return;
41 | _loaded_notes = new ArrayList ();
42 | var dir = File.new_for_path (path);
43 | if (dir.query_exists ()) {
44 | if (dir.query_file_type (0) != FileType.DIRECTORY) {
45 | error (@"Trash directory $(dir.get_path ()) isn't a directory");
46 | }
47 | } else try {
48 | dir.make_directory_with_parents ();
49 | } catch (Error err) {
50 | error (@"Trash directory $(dir.get_path ()) couldn't be created");
51 | }
52 | try {
53 | var enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_DISPLAY_NAME, 0);
54 | FileInfo file_info;
55 | while ((file_info = enumerator.next_file ()) != null) {
56 | var name = file_info.get_display_name ();
57 | if (name[0] == '.') continue;
58 | var notebook = new TrashedNotebook (this, new NotebookInfo (name));
59 | load_notebook (notebook);
60 | }
61 | } catch (Error e) {
62 | error (@"Notebook loading failed: $(e.message)\n");
63 | }
64 | }
65 |
66 | public void unload () {
67 | _loaded_notes = null;
68 | }
69 |
70 | private void load_notebook (TrashedNotebook notebook) {
71 | var dir = File.new_for_path (notebook.path);
72 | try {
73 | var enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.TIME_MODIFIED + "," + FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_DISPLAY_NAME, 0);
74 | FileInfo file_info;
75 | while ((file_info = enumerator.next_file ()) != null) {
76 | var content_type = file_info.get_content_type ();
77 | if (content_type == null || (!content_type.has_prefix ("text") && content_type != "application/x-zerosize"))
78 | continue;
79 | var name = file_info.get_display_name ();
80 | if (name[0] == '.')
81 | continue;
82 | var dot_i = name.last_index_of (".");
83 | if (dot_i == -1)
84 | continue;
85 | var extension = name.substring (dot_i + 1);
86 | name = name.substring (0, dot_i);
87 | var mod_time = (!) file_info.get_modification_date_time ();
88 | _loaded_notes.add (new Note (
89 | name,
90 | extension,
91 | notebook,
92 | mod_time
93 | ));
94 | }
95 | _loaded_notes.sort ((a, b) => b.time_modified.compare(a.time_modified));
96 | } catch (Error e) {}
97 | }
98 |
99 | public void delete_note (Note note) throws ProviderError {
100 | load ();
101 | var path = note.path;
102 | var file = File.new_for_path (path);
103 | if (!file.query_exists ()) {
104 | throw new ProviderError.COULDNT_DELETE (@"Couldn't delete note at $path");
105 | }
106 | try {
107 | file.@delete ();
108 | } catch (Error e) {
109 | throw new ProviderError.COULDNT_DELETE (@"Couldn't delete note at $path");
110 | }
111 | int i = _loaded_notes.index_of (note);
112 | _loaded_notes.remove_at (i);
113 | items_changed (i, 1, 0);
114 | }
115 |
116 | public void delete_all () throws ProviderError {
117 | load ();
118 | foreach (var note in _loaded_notes) {
119 | var path = note.path;
120 | var file = File.new_for_path (path);
121 | if (!file.query_exists ()) {
122 | throw new ProviderError.COULDNT_DELETE (@"Couldn't delete note at $path");
123 | }
124 | try {
125 | file.@delete ();
126 | } catch (Error e) {
127 | throw new ProviderError.COULDNT_DELETE (@"Couldn't delete note at $path");
128 | }
129 | }
130 | var l = _loaded_notes.size;
131 | _loaded_notes = new ArrayList ();
132 | items_changed (0, l, 0);
133 | }
134 |
135 | public void restore_note (Note note) throws ProviderError {
136 | load ();
137 | var path = note.path;
138 | var file = File.new_for_path (path);
139 | if (!file.query_exists ()) {
140 | throw new ProviderError.COULDNT_MOVE (@"Couldn't restore note at $path");
141 | }
142 | var restore_dir_path = @"$(provider.notes_dir)/$(note.notebook.name)";
143 | var restore_path = @"$restore_dir_path/$(note.file_name)";
144 | var restore_file = File.new_for_path (restore_path);
145 | if (restore_file.query_exists ()) {
146 | throw new ProviderError.ALREADY_EXISTS (@"Couldn't move from $path, to $restore_path, file already axists");
147 | }
148 | try {
149 | var restore_dir = File.new_for_path (restore_dir_path);
150 | if (!restore_dir.query_exists ()) {
151 | restore_dir.make_directory_with_parents ();
152 | }
153 | file.move (restore_file, 0);
154 | } catch (Error e) {
155 | message (e.message);
156 | throw new ProviderError.COULDNT_MOVE (@"Couldn't restore note at $path");
157 | }
158 | int i = _loaded_notes.index_of (note);
159 | _loaded_notes.remove_at (i);
160 | items_changed (i, 1, 0);
161 | }
162 |
163 | public Type get_item_type () {
164 | return typeof (Note);
165 | }
166 |
167 | public uint get_n_items () {
168 | check_notes_loaded ();
169 | return _loaded_notes.size;
170 | }
171 |
172 | public Object? get_item (uint i) {
173 | check_notes_loaded ();
174 | return (i >= _loaded_notes.size) ? null : _loaded_notes.@get((int) i);
175 | }
176 |
177 | private inline void check_notes_loaded () {
178 | if (_loaded_notes == null) {
179 | error ("Notes haven't loaded yet");
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/lib/util/NaturalCollate.vala:
--------------------------------------------------------------------------------
1 | /**
2 | * NaturalCollate
3 | * Simple helper class for natural sorting in Vala.
4 | *
5 | * (c) Tobia Tesan , 2014
6 | *
7 | * This program is free software; you can redistribute it and/or
8 | * modify it under the terms of the Lesser GNU General Public License
9 | * as published by the Free Software Foundation; either version 2.1
10 | * of the License, or (at your option) any later version.
11 | *
12 | * You should have received a copy of the GNU Lesser General Public
13 | * License along with this program; see the file COPYING. If not,
14 | * see .
15 | */
16 |
17 | namespace NaturalCollate {
18 |
19 | private const unichar SUPERDIGIT = ':';
20 | private const unichar NUM_SENTINEL = 0x2; // glib uses these, so do we
21 | private const string COLLATION_SENTINEL = "\x01\x01\x01";
22 |
23 | private static int read_number(owned string s, ref int byte_index) {
24 | /*
25 | * Given a string in the form [numerals]*[everythingelse]*
26 | * returns the int value of the first block and increments index
27 | * by its length as a side effect.
28 | * Notice that "numerals" is not just 0-9 but everything else
29 | * Unicode considers a numeral (see: string::isdigit())
30 | */
31 | int number = 0;
32 |
33 | while (s.length != 0 && s.get_char(0).isdigit()) {
34 | number = number*10;
35 | number += s.get_char(0).digit_value();
36 | int second_char = s.index_of_nth_char(1);
37 | s = s.substring(second_char);
38 | byte_index += second_char;
39 | }
40 | return number;
41 | }
42 |
43 | public static int compare(string str1, string str2) {
44 | return strcmp(collate_key(str1), collate_key(str2));
45 | }
46 |
47 | public static string collate_key(owned string str) {
48 | /*
49 | * Computes a collate key.
50 | * Has roughly the same effect as g_utf8_collate_key_for_file, except that it doesn't
51 | * handle the dot as a special char.
52 | */
53 | assert (str.validate());
54 | string result = "";
55 | bool eos = (str.length == 0);
56 |
57 | while (!eos) {
58 | assert(str.validate());
59 | int position = 0;
60 | while (!(str.get_char(position).to_string() in "0123456789")) {
61 | // We only care about plain old 0123456789, aping what g_utf8_collate_key_for_filename does
62 | position++;
63 | }
64 |
65 | // (0... position( is a bunch of non-numerical chars, so we compute and append the collate key...
66 | result = result + (str.substring(0, position).collate_key());
67 |
68 | // ...then throw them away
69 | str = str.substring(position);
70 |
71 | eos = (str.length == 0);
72 | position = 0;
73 |
74 | if (!eos) {
75 | // We have some numbers to handle in front of us
76 | int number = read_number(str, ref position);
77 | str = str.substring(position);
78 | int number_of_superdigits = number.to_string().length;
79 | string to_append = "";
80 | for (int i = 1; i < number_of_superdigits; i++) {
81 | // We append n - 1 superdigits where n is the number of digits
82 | to_append = to_append + SUPERDIGIT.to_string();
83 | }
84 | to_append = to_append + (number.to_string()); // We append the actual number
85 | result = result +
86 | COLLATION_SENTINEL +
87 | NUM_SENTINEL.to_string() +
88 | to_append;
89 | }
90 | eos = (str.length == 0);
91 | }
92 |
93 | result = result + NUM_SENTINEL.to_string();
94 | // No specific reason except that glib does it
95 |
96 | return result;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/util/file_utils.vala:
--------------------------------------------------------------------------------
1 |
2 | namespace FileUtils {
3 | public void save_to (File file, string text) {
4 | try {
5 | if (file.query_exists ()) {
6 | // If the file already exists check to see if anything has changed, if not, bail.
7 | string etag_out;
8 | uint8[] text_data = {};
9 | file.load_contents (null, out text_data, out etag_out);
10 | if (text == (string) text_data) {
11 | return;
12 | }
13 | } else {
14 | // Let's make sure the directory exists we're trying to save to.
15 | // If it doesn't, create it.
16 | var path = file.get_parent ().get_parse_name ();
17 | var dir = File.new_for_path (path);
18 | if (!dir.query_exists ()) {
19 | dir.make_directory_with_parents ();
20 | }
21 | }
22 |
23 | // Write the text out to the file.
24 | file.replace_contents (text.data, null, false, FileCreateFlags.NONE, null);
25 | } catch (Error e) {
26 | error (e.message);
27 | }
28 | }
29 |
30 | // Recursively delete a directory and all of it's contents.
31 | public void recursive_delete (File path) {
32 | // Enumerate the children of the path we're deleting.
33 | GLib.FileEnumerator enumerator;
34 | try {
35 | enumerator = path.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_DISPLAY_NAME, FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
36 | } catch (Error e) {
37 | error (e.message);
38 | }
39 |
40 | FileInfo file_info;
41 |
42 | try {
43 | file_info = enumerator.next_file ();
44 | } catch (Error e) {
45 | file_info = null;
46 | }
47 |
48 | // Loop through the children.
49 | while (file_info != null) {
50 | // Get a handle to the child.
51 | var child = enumerator.get_child (file_info);
52 |
53 | // If the child is a directory, descend into it.
54 | // Otherwise just delete it.
55 | if (file_info.get_file_type() == FileType.DIRECTORY) {
56 | recursive_delete (child);
57 | } else {
58 | try {
59 | child.@delete ();
60 | } catch (Error e) {}
61 | }
62 |
63 | try {
64 | file_info = enumerator.next_file ();
65 | } catch (Error e) {
66 | file_info = null;
67 | }
68 | }
69 |
70 | // Once we're done, delete the root.
71 | try {
72 | path.@delete ();
73 | } catch (Error e) {}
74 | }
75 |
76 | // Expand the ~ operator in file names/paths.
77 | public string expand_home_directory (string path) {
78 | if (path[0] == '~') {
79 | var user_home = GLib.Environment.get_home_dir ();
80 |
81 | if (path[1] != '/' || path[1] != '/') {
82 | return user_home + "/" + path.substring (1, -1);
83 | } else {
84 | return user_home + path.substring (1, -1);
85 | }
86 | }
87 |
88 | return path;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/util/fuzzy_matching.vala:
--------------------------------------------------------------------------------
1 |
2 |
3 | namespace Util {
4 |
5 | public int search_distance (string item, string query) {
6 | var words = item.split_set (" _-");
7 | var query_words = query.split_set (" _-");
8 | var base_distance = 0;
9 | var min_word = int.MAX;
10 | foreach (var i in words) {
11 | if (i.length == 0) continue;
12 | var min = int.MAX;
13 | foreach (var q in query_words) {
14 | if (q.length == 0) continue;
15 | min = int.min (min, damerau_levenshtein_distance (i, q, 5, 3, 2, 1));
16 | }
17 | base_distance += min;
18 | min_word = int.min (min_word, min);
19 | }
20 | return (base_distance + min_word) / words.length / 2;
21 | }
22 |
23 | /**
24 | * This implementation is an adaptation based on Kevin L. Stern's work
25 | * https://github.com/KevinStern/software-and-algorithms
26 | *
27 | * The running time of the Damerau-Levenshtein algorithm is O(n*m) where n is
28 | * the length of the source string and m is the length of the target string.
29 | * This implementation consumes O(n*m) space.
30 | */
31 | public int damerau_levenshtein_distance (
32 | string source,
33 | string target,
34 | int insert_cost,
35 | int delete_cost,
36 | int replace_cost,
37 | int swap_cost
38 | ) {
39 | int delete_distance = 0;
40 | int insert_distance = 0;
41 | int match_distance = 0;
42 | int jSwap = 0;
43 | int maxSourceLetterMatchIndex = 0;
44 | int? candidateSwapIndex = 0;
45 | int swap_distance = 0;
46 |
47 | if (source.length == 0)
48 | return target.length * insert_cost;
49 |
50 | if (target.length == 0)
51 | return source.length * delete_cost;
52 |
53 | var table = new int[source.length, target.length];
54 | var sourceIndexByCharacter = new Gee.HashMap ();
55 |
56 | if (source.get_char (0) != target.get_char (0))
57 | table[0, 0] = int.min (replace_cost, delete_cost + insert_cost);
58 |
59 | sourceIndexByCharacter.@set (source.get_char (0), 0);
60 |
61 | for (int i = 1; i < source.length; i++) {
62 | delete_distance = table[i - 1, 0] + delete_cost;
63 | insert_distance = (i + 1) * delete_cost + insert_cost;
64 | match_distance = i * delete_cost + (source.get_char (i) == target.get_char (0) ? 0 : replace_cost);
65 | table[i, 0] = int.min (int.min (delete_distance, insert_distance), match_distance);
66 | }
67 |
68 | for (int j = 1; j < target.length; j++) {
69 | delete_distance = (j + 1) * insert_cost + delete_cost;
70 | insert_distance = table[0, j - 1] + insert_cost;
71 | match_distance = j * insert_cost + (source.get_char (0) == target.get_char (j) ? 0 : replace_cost);
72 | table[0, j] = int.min (int.min (delete_distance, insert_distance), match_distance);
73 | }
74 |
75 | for (int i = 1; i < source.length; i++) {
76 | maxSourceLetterMatchIndex = source.get_char (i) == target.get_char (0) ? 0 : -1;
77 | for (int j = 1; j < target.length; j++) {
78 | candidateSwapIndex = sourceIndexByCharacter.@get (target.get_char (j));
79 | jSwap = maxSourceLetterMatchIndex;
80 | delete_distance = table[i - 1, j] + delete_cost;
81 | insert_distance = table[i, j - 1] + insert_cost;
82 | match_distance = table[i - 1, j - 1];
83 |
84 | if (source.get_char (i) != target.get_char (j))
85 | match_distance += replace_cost;
86 | else
87 | maxSourceLetterMatchIndex = j;
88 |
89 | if (candidateSwapIndex != null && jSwap != -1) {
90 | int iSwap = candidateSwapIndex;
91 | int preswap_cost;
92 | if (iSwap == 0 && jSwap == 0)
93 | preswap_cost = 0;
94 | else
95 | preswap_cost = table[int.max (0, iSwap - 1), int.max (0, jSwap - 1)];
96 | swap_distance = preswap_cost + (i - iSwap - 1) * delete_cost + (j - jSwap - 1) * insert_cost + swap_cost;
97 | }
98 | else swap_distance = int.MAX;
99 | table[i, j] = int.min (int.min (int.min (delete_distance, insert_distance), match_distance), swap_distance);
100 | }
101 | sourceIndexByCharacter.@set (source.get_char (i), i);
102 | }
103 | return table[source.length - 1, target.length - 1];
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/util/sugar.vala:
--------------------------------------------------------------------------------
1 |
2 | public delegate void Runnable ();
3 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'com.toolstack.Folio',
3 | ['c', 'vala'],
4 | version: '25.02',
5 | meson_version: '>= 0.59.4',
6 | default_options: [
7 | 'warning_level=2',
8 | ],
9 | )
10 |
11 | i18n = import('i18n')
12 | gnome = import('gnome')
13 |
14 | app_id = meson.project_name()
15 | version = meson.project_version()
16 |
17 | add_project_arguments(
18 | [
19 | '--enable-experimental',
20 | ],
21 | language: 'vala'
22 | )
23 |
24 | subdir('lib')
25 | subdir('po')
26 | subdir('data')
27 | subdir('src')
28 | subdir('search-provider')
29 |
30 | gnome.post_install(
31 | glib_compile_schemas: true,
32 | gtk_update_icon_cache: true,
33 | update_desktop_database: true
34 | )
35 |
--------------------------------------------------------------------------------
/meta/folio-desktop-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toolstack/Folio/175fcdede83641b8b4683f94a3ba8f04ae36f7c1/meta/folio-desktop-dark.png
--------------------------------------------------------------------------------
/meta/folio-desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toolstack/Folio/175fcdede83641b8b4683f94a3ba8f04ae36f7c1/meta/folio-desktop.png
--------------------------------------------------------------------------------
/meta/folio-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toolstack/Folio/175fcdede83641b8b4683f94a3ba8f04ae36f7c1/meta/folio-mobile.png
--------------------------------------------------------------------------------
/meta/folio-preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toolstack/Folio/175fcdede83641b8b4683f94a3ba8f04ae36f7c1/meta/folio-preferences.png
--------------------------------------------------------------------------------
/meta/folio-three-pane-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toolstack/Folio/175fcdede83641b8b4683f94a3ba8f04ae36f7c1/meta/folio-three-pane-mode.png
--------------------------------------------------------------------------------
/po/LINGUAS:
--------------------------------------------------------------------------------
1 | ar
2 | ca
3 | cs
4 | de
5 | es
6 | fa
7 | fi
8 | fr
9 | gl
10 | hi
11 | it
12 | ko
13 | nb
14 | nl_BE
15 | nl_NL
16 | oc
17 | pl
18 | pt_BR
19 | ru
20 | sr
21 | tok
22 | tr
23 | uk
24 | zh_CN
25 |
--------------------------------------------------------------------------------
/po/POTFILES:
--------------------------------------------------------------------------------
1 | data/app.desktop.in
2 | data/app.gschema.xml
3 | data/app.metainfo.xml.in
4 | data/app.service.in
5 | src/ui/edit_view/cheatsheet/markdown_cheatsheet.blp
6 | src/ui/edit_view/edit_view.blp
7 | src/ui/edit_view/toolbar/toolbar.blp
8 | src/ui/file_editor/file_editor_window.blp
9 | src/ui/popup/notebook_selection_popup/notebook_list_item.blp
10 | src/ui/popup/notebook_selection_popup/notebook_selection_popup.blp
11 | src/ui/popup/shortcut_window.blp
12 | src/ui/preferences/preferences.blp
13 | src/ui/strings.vala
14 | src/ui/widgets/markdown/heading_popover.blp
15 | src/ui/widgets/notebook_icon/notebook_preview.blp
16 | src/ui/widgets/theme_selector/theme_selector.blp
17 | src/ui/window/app_menu/app_menu.blp
18 | src/ui/window/font_scale/font_scale.blp
19 | src/ui/window/notebooks_bar/icon.blp
20 | src/ui/window/notebooks_bar/notebook_create_popup.blp
21 | src/ui/window/notebooks_bar/notebook_menu.blp
22 | src/ui/window/notebooks_bar/notebooks_bar.blp
23 | src/ui/window/sidebar/note_card.blp
24 | src/ui/window/sidebar/note_create_popup.blp
25 | src/ui/window/sidebar/note_menu.blp
26 | src/ui/window/window.blp
27 |
--------------------------------------------------------------------------------
/po/generate-POT-file.sh:
--------------------------------------------------------------------------------
1 | # Generates a new POT file for Folio
2 | ./print-source-files.sh > POTFILES
3 | cd ../build
4 | meson compile com.toolstack.Folio-pot
5 |
--------------------------------------------------------------------------------
/po/meson.build:
--------------------------------------------------------------------------------
1 | i18n.gettext(app_id, preset: 'glib')
2 |
--------------------------------------------------------------------------------
/po/print-source-files.sh:
--------------------------------------------------------------------------------
1 | # Prints out all *.c and *.blp files in the project
2 | # Useful for updating the POTFILES file (run from this directory)
3 | pushd ../ > /dev/null
4 | (find data -name app.*; find src -name *.blp; find src -name strings.vala) | sort
5 | popd > /dev/null
--------------------------------------------------------------------------------
/search-provider/SearchProvider.service.in:
--------------------------------------------------------------------------------
1 | [D-BUS Service]
2 | Name=@app_id@.SearchProvider
3 | Exec=@exec_name@
4 |
--------------------------------------------------------------------------------
/search-provider/meson.build:
--------------------------------------------------------------------------------
1 | search_provider_sources = [
2 | 'search-provider.vala',
3 | lib_sources,
4 | ]
5 |
6 | search_provider_deps = [
7 | dependency('glib-2.0'),
8 | dependency('gobject-2.0'),
9 | dependency('gmodule-2.0'),
10 | dependency('gtk4'),
11 | dependency('gio-2.0'),
12 | dependency('gee-0.8'),
13 | ]
14 |
15 | sp_sources = gnome.gdbus_codegen(
16 | 'shell-search-provider-generated',
17 | sources: 'org.gnome.Shell.SearchProvider2.xml',
18 | interface_prefix : 'org.gnome.',
19 | namespace : 'Folio',
20 | )
21 |
22 | conf = configuration_data()
23 | conf.set_quoted('APP_ID', app_id)
24 | conf.set_quoted('VERSION', version)
25 | configure_file(output: 'config.h', configuration: conf)
26 |
27 | exec_name = 'folio-search-provider'
28 |
29 | executable(
30 | exec_name,
31 | search_provider_sources,
32 | dependencies: search_provider_deps,
33 | c_args: [
34 | '-DGETTEXT_PACKAGE="' + app_id + '"'
35 | ],
36 | vala_args: [
37 | meson.project_source_root() + '/src/config.vapi',
38 | '--target-glib=2.70',
39 | ],
40 | install: true,
41 | )
42 |
43 | dbusconf = configuration_data()
44 | dbusconf.set('app_id', app_id)
45 | dbusconf.set('version', version)
46 | dbusconf.set('exec_name', exec_name)
47 |
48 | gnome_shell_file = configure_file(
49 | input: 'search-provider.ini',
50 | output: app_id + '.SearchProvider-search-provider.ini',
51 | configuration: dbusconf,
52 | )
53 |
54 | install_data(
55 | gnome_shell_file,
56 | install_dir: get_option('datadir') / 'gnome-shell' / 'search-providers',
57 | )
58 |
59 | configuration_file = configure_file(
60 | input: 'SearchProvider.service.in',
61 | output: app_id + '.SearchProvider.service',
62 | configuration: dbusconf,
63 | )
64 |
65 | install_data(
66 | configuration_file,
67 | install_dir: get_option('datadir') / 'dbus-1' / 'services',
68 | )
69 |
--------------------------------------------------------------------------------
/search-provider/org.gnome.Shell.SearchProvider2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/search-provider/search-provider.ini:
--------------------------------------------------------------------------------
1 | [Shell Search Provider]
2 | DesktopId=@app_id@.desktop
3 | BusName=@app_id@.SearchProvider
4 | ObjectPath=/com/toolstack/Folio/SearchProvider
5 | Version=2
6 |
--------------------------------------------------------------------------------
/search-provider/search-provider.vala:
--------------------------------------------------------------------------------
1 |
2 | [DBus (name = "org.gnome.Shell.SearchProvider2")]
3 | public class SearchProvider : Object {
4 |
5 | Settings settings = new Settings (Config.APP_ID);
6 | Folio.Provider notebook_provider = new Folio.Provider ();
7 |
8 | public SearchProvider () {
9 | settings.changed["notes-dir"].connect (on_settings_notes_dir_change);
10 |
11 | on_settings_notes_dir_change ();
12 | }
13 |
14 | private void on_settings_notes_dir_change () {
15 | try {
16 | update_storage_dir ();
17 | } catch (Error e) {}
18 | }
19 |
20 | private HashTable notes;
21 |
22 | struct ResultWithDistance {
23 | string result;
24 | int distance;
25 |
26 | ResultWithDistance (string result, int distance) {
27 | this.result = result;
28 | this.distance = distance;
29 | }
30 | }
31 |
32 | public void update_storage_dir () throws Error {
33 | var path = settings.get_string ("notes-dir");
34 | var notes_dir = path.has_prefix ("~/") ? Environment.get_home_dir () + path[1:] : path;
35 | try {
36 | notebook_provider.set_directory (notes_dir);
37 | notebook_provider.load ();
38 | update_notes ();
39 | } catch (Error e) {}
40 | }
41 |
42 | public void update_notes () throws Error {
43 | var notebooks = notebook_provider.notebooks;
44 | notes = new HashTable (str_hash, str_equal);
45 | foreach (var notebook in notebooks) {
46 | notebook.load ();
47 | foreach (var note in notebook.loaded_notes) {
48 | notes.insert(note.id, note);
49 | }
50 | notebook.unload ();
51 | }
52 | }
53 |
54 | public string[] get_initial_result_set (string[] terms) throws Error {
55 | update_notes ();
56 | return get_subsearch_result_set ({}, terms);
57 | }
58 |
59 | public string[] get_subsearch_result_set (string[] _, string[] terms) throws Error {
60 | var results = new Gee.ArrayList ();
61 | new Gee.ArrayList.wrap (notes.get_keys_as_array ())
62 | .map (x => {
63 | var name = x.split("/")[1].down ().normalize ();
64 | var query = string.joinv (" ", terms).down ().normalize ();
65 | var xd = Util.search_distance (name, query);
66 | return ResultWithDistance (x, xd);
67 | })
68 | .filter (x => x.distance < 5)
69 | .order_by ((a, b) => a.distance - b.distance)
70 | .foreach (x => results.add (x.result));
71 | return results.to_array ();
72 | }
73 |
74 | public HashTable[] get_result_metas (string[] ids) throws Error {
75 | var metas = new HashTable[ids.length];
76 | for (var i = 0; i < ids.length; i++) {
77 | var id = ids[i];
78 | metas[i] = new HashTable (str_hash, str_equal);
79 | metas[i].insert ("id", id);
80 | metas[i].insert ("name", notes[id].name);
81 | }
82 | return metas;
83 | }
84 |
85 | public void launch_search (string[] terms, uint32 timestamp) throws Error {
86 | Process.spawn_command_line_async (
87 | "com.toolstack.Folio --launch-search " + Shell.quote (string.joinv (" ", terms))
88 | );
89 | }
90 |
91 | public void activate_result (string result_id, string[] terms, uint32 timestamp) throws Error {
92 | var note = notes[result_id];
93 | Process.spawn_command_line_async (
94 | "com.toolstack.Folio --open-note " + Shell.quote (note.id)
95 | );
96 | }
97 | }
98 |
99 | public class SearchProviderApp : Application {
100 | public SearchProviderApp () {
101 | Object (
102 | application_id: "com.toolstack.Folio.SearchProvider",
103 | flags: ApplicationFlags.IS_SERVICE
104 | );
105 | }
106 |
107 | private uint registration_id;
108 |
109 | public override bool dbus_register (DBusConnection connection, string object_path) {
110 | SearchProvider search_provider = new SearchProvider ();
111 |
112 | try {
113 | registration_id = connection.register_object (object_path, search_provider);
114 | }
115 | catch (IOError e) {
116 | error (@"Could not register service: $(e.message)");
117 | }
118 |
119 | return true;
120 | }
121 |
122 | public override void dbus_unregister (DBusConnection connection, string object_path) {
123 | connection.unregister_object (registration_id);
124 | }
125 | }
126 |
127 | int main (string[] args) {
128 | Gtk.init();
129 | Intl.setlocale (LocaleCategory.ALL, "");
130 | return new SearchProviderApp ().run (args);
131 | }
132 |
--------------------------------------------------------------------------------
/snap/snapcraft.yaml:
--------------------------------------------------------------------------------
1 | name: folio
2 | base: core24
3 | adopt-info: folio
4 | grade: stable
5 | confinement: strict
6 |
7 | parts:
8 | folio:
9 | plugin: meson
10 | source: https://github.com/toolstack/Folio.git
11 | source-tag: '25.02'
12 | source-depth: 1
13 | build-snaps:
14 | - blueprint-compiler
15 | meson-parameters:
16 | - --prefix=/usr
17 | organize:
18 | snap/folio/current: .
19 | prime:
20 | - -snap/folio
21 | parse-info: [usr/share/metainfo/com.toolstack.Folio.appdata.xml]
22 |
23 | slots:
24 | folio:
25 | interface: dbus
26 | bus: session
27 | name: com.toolstack.Folio
28 |
29 | apps:
30 | folio:
31 | command: usr/bin/com.toolstack.Folio
32 | desktop: usr/share/applications/com.toolstack.Folio.desktop
33 | common-id: com.toolstack.Folio
34 | extensions: [gnome]
35 | plugs: &folio
36 | - home
37 | - removable-media
38 | editor:
39 | command: usr/bin/com.toolstack.Folio
40 | desktop: usr/share/applications/com.toolstack.Folio-editor.desktop
41 | extensions: [gnome]
42 | plugs: *folio
43 |
44 | platforms:
45 | amd64:
46 | arm64:
47 | armhf:
48 |
--------------------------------------------------------------------------------
/src/color.vala:
--------------------------------------------------------------------------------
1 |
2 | namespace Color {
3 | public struct HSL {
4 | public float h;
5 | public float s;
6 | public float l;
7 | }
8 |
9 | public struct RGB {
10 | public float r;
11 | public float g;
12 | public float b;
13 |
14 | public inline RGB (float r = 0, float g = 0, float b = 0) {
15 | this.r = r;
16 | this.g = g;
17 | this.b = b;
18 | }
19 |
20 | public inline RGB times (RGB rgb) {
21 | return RGB (r * rgb.r, g * rgb.g, b * rgb.b);
22 | }
23 |
24 | public inline RGB multiply (float x) {
25 | return RGB (r * x, g * x, b * x);
26 | }
27 |
28 | public inline RGB plus (RGB rgb) {
29 | return RGB (r + rgb.r, g + rgb.g, b + rgb.b);
30 | }
31 | }
32 |
33 | public inline HSL rgb_to_hsl (RGB rgb, out HSL hsl = null) {
34 | hsl = HSL();
35 | float s, v;
36 | Gtk.rgb_to_hsv(rgb.r, rgb.g, rgb.b, out hsl.h, out s, out v);
37 | hsl.l = v - v * s / 2;
38 | float m = float.min (hsl.l, 1 - hsl.l);
39 | hsl.s = (m != 0) ? (v-hsl.l)/m : 0;
40 | return hsl;
41 | }
42 |
43 | public inline RGB hsl_to_rgb (HSL hsl, out RGB rgb = null) {
44 | rgb = RGB();
45 | float v = hsl.s * float.min (hsl.l, 1 - hsl.l) + hsl.l;
46 | float s = (v != 0) ? 2-2*hsl.l/v : 0;
47 | Gtk.hsv_to_rgb(hsl.h, s, v, out rgb.r, out rgb.g, out rgb.b);
48 | return rgb;
49 | }
50 |
51 | public inline RGB RGBA_to_rgb (Gdk.RGBA rgba, out RGB rgb = null) {
52 | rgb = RGB();
53 | rgb.r = rgba.red;
54 | rgb.g = rgba.green;
55 | rgb.b = rgba.blue;
56 | return rgb;
57 | }
58 |
59 | public inline Gdk.RGBA rgb_to_RGBA (RGB rgb, out Gdk.RGBA rgba = null) {
60 | rgba = Gdk.RGBA();
61 | rgba.red = rgb.r;
62 | rgba.green = rgb.g;
63 | rgba.blue = rgb.b;
64 | rgba.alpha = 1;
65 | return rgba;
66 | }
67 |
68 | public inline float get_luminance (float r, float g, float b) {
69 | return Math.sqrtf (0.299f * r * r + 0.587f * g * g + 0.114f * b * b);
70 | }
71 |
72 | public inline float get_luminance_photometric (float r, float g, float b) {
73 | return 0.2126f * r + 0.7152f * g + 0.0722f;
74 | }
75 |
76 | public inline float get_luminance_digital (float r, float g, float b) {
77 | return 0.299f * r + 0.587f * g + 0.114f;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/config.vapi:
--------------------------------------------------------------------------------
1 | [CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
2 | namespace Config {
3 | public const string APP_ID;
4 | public const string VERSION;
5 | public const string GNOMELOCALEDIR;
6 | }
7 |
--------------------------------------------------------------------------------
/src/css/style-black-hc.css:
--------------------------------------------------------------------------------
1 |
2 | @define-color borders alpha(shade(@theme_color, 1.6), 0.5);
3 |
--------------------------------------------------------------------------------
/src/css/style-black.css:
--------------------------------------------------------------------------------
1 | @define-color window_bg_color #000000;
2 |
3 | @define-color view_bg_color @window_bg_color;
4 | @define-color notebooks_bar_bg_color @window_bg_color;
5 |
6 | @define-color popover_bg_color #1f1f1f;
7 |
8 | @define-color window_fg_color #f2f2f2;
9 |
10 | @define-color notebooks_bar_fg_color @window_fg_color;
11 |
12 | @define-color selected_bg_color alpha(mix(@theme_color, #180033, 0.5), 0.4);
13 |
14 | @define-color accent_color shade(@theme_color, 0.9);
15 | @define-color accent_bg_color shade(@theme_color, 0.6);
16 |
17 | @define-color borders alpha(shade(@theme_color, 1.6), 0.25);
18 |
19 | window:not(.tiled-left):not(.tiled-right):not(.tiled-bottom), dialog {
20 | outline: solid 1px @borders;
21 | }
22 |
--------------------------------------------------------------------------------
/src/css/style-dark.css:
--------------------------------------------------------------------------------
1 | @define-color accent_color shade(mix(@theme_color, #efffa0, 0.1), 1.5);
2 | @define-color accent_bg_color shade(@theme_color, 0.8);
3 |
4 | @define-color notebooks_bar_bg_color shade(@window_bg_color, 1.2);
5 | @define-color notebooks_bar_fg_color shade(@window_fg_color, 1.2);
6 |
7 | @define-color selected_bg_color alpha(mix(@theme_color, #50505f, 0.4), 0.34);
8 | @define-color hover_bg_color alpha(@accent_color, .05);
--------------------------------------------------------------------------------
/src/folio.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | css/style.css
5 | css/style-dark.css
6 | css/style-black.css
7 | css/style-black-hc.css
8 |
9 | ui/preferences/preferences.ui
10 |
11 | ui/edit_view/edit_view.ui
12 | ui/edit_view/toolbar/toolbar.ui
13 |
14 | ui/file_editor/file_editor_window.ui
15 |
16 | ui/window/window.ui
17 | ui/window/app_menu/app_menu.ui
18 | ui/window/font_scale/font_scale.ui
19 | ui/window/notebooks_bar/notebooks_bar.ui
20 | ui/window/notebooks_bar/icon.ui
21 | ui/window/notebooks_bar/notebook_menu.ui
22 | ui/window/notebooks_bar/notebook_create_popup.ui
23 | ui/window/sidebar/note_card.ui
24 | ui/window/sidebar/note_menu.ui
25 | ui/window/sidebar/note_create_popup.ui
26 |
27 | ui/edit_view/cheatsheet/markdown_cheatsheet.ui
28 | ui/edit_view/cheatsheet/markdown_cheatsheet.md
29 |
30 | ui/popup/shortcut_window.ui
31 | ui/popup/notebook_selection_popup/notebook_selection_popup.ui
32 | ui/popup/notebook_selection_popup/notebook_list_item.ui
33 |
34 | ui/widgets/notebook_icon/notebook_preview.ui
35 | ui/widgets/theme_selector/theme_selector.ui
36 | ui/widgets/markdown/heading_popover.ui
37 |
38 | graphics/scalable/actions/apply-symbolic.svg
39 | graphics/scalable/actions/dark-mode-symbolic.svg
40 | graphics/scalable/actions/empty-trash-symbolic.svg
41 | graphics/scalable/actions/format-text-highlight-symbolic.svg
42 | graphics/scalable/actions/insert-horizontal-rule-symbolic.svg
43 | graphics/scalable/actions/insert-code-symbolic.svg
44 | graphics/scalable/actions/reset-symbolic.svg
45 | graphics/scalable/actions/stretch-horizontal-symbolic.svg
46 | graphics/scalable/actions/toolbar-symbolic.svg
47 |
48 | graphics/scalable/actions/icon-car-symbolic.svg
49 | graphics/scalable/actions/icon-code-symbolic.svg
50 | graphics/scalable/actions/icon-food-symbolic.svg
51 | graphics/scalable/actions/icon-gaming-symbolic.svg
52 | graphics/scalable/actions/icon-heart-symbolic.svg
53 | graphics/scalable/actions/icon-home-symbolic.svg
54 | graphics/scalable/actions/icon-like-symbolic.svg
55 | graphics/scalable/actions/icon-music-symbolic.svg
56 | graphics/scalable/actions/icon-nature-symbolic.svg
57 | graphics/scalable/actions/icon-patch-symbolic.svg
58 | graphics/scalable/actions/icon-pin-symbolic.svg
59 | graphics/scalable/actions/icon-plant-symbolic.svg
60 | graphics/scalable/actions/icon-plus-symbolic.svg
61 | graphics/scalable/actions/icon-school-symbolic.svg
62 | graphics/scalable/actions/icon-science-symbolic.svg
63 | graphics/scalable/actions/icon-settings-symbolic.svg
64 | graphics/scalable/actions/icon-skull-symbolic.svg
65 | graphics/scalable/actions/icon-sport-symbolic.svg
66 | graphics/scalable/actions/icon-star-symbolic.svg
67 | graphics/scalable/actions/icon-toki-symbolic.svg
68 | graphics/scalable/actions/icon-toki-pona-symbolic.svg
69 | graphics/scalable/actions/icon-travel-symbolic.svg
70 | graphics/scalable/actions/icon-work-symbolic.svg
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/apply-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/dark-mode-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/document-save-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/empty-trash-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
22 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/font-x-generic-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/format-text-highlight-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-car-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-code-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-food-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-gaming-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-heart-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-home-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-like-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-music-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-nature-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-patch-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-pin-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-plant-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-plus-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-school-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-science-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-settings-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-skull-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-sport-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-star-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-toki-pona-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
47 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-toki-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
43 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-travel-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/icon-work-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/insert-code-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
45 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/insert-horizontal-rule-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
45 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/object-flip-horizontal-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/reset-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/stretch-horizontal-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/toolbar-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/view-list-ordered-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/graphics/scalable/actions/view-sort-descending-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/main.vala:
--------------------------------------------------------------------------------
1 | /* main.vala
2 | *
3 | * Copyright 2022 Zagura
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | int main (string[] args) {
20 | Environment.set_prgname (Config.APP_ID);
21 | Intl.bindtextdomain (Config.APP_ID, Config.GNOMELOCALEDIR);
22 | Intl.bind_textdomain_codeset (Config.APP_ID, "UTF-8");
23 | Intl.textdomain (Config.APP_ID);
24 | Environment.set_application_name (Folio.Strings.APP_NAME);
25 | Gtk.init();
26 | GtkSource.init ();
27 | var app = new Folio.Application ();
28 | return app.run (args);
29 | }
30 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | folio_sources = [
2 | 'main.vala',
3 | 'application.vala',
4 |
5 | 'ui/strings.vala',
6 |
7 | 'ui/window/window.vala',
8 | 'ui/window/window_model.vala',
9 | 'ui/preferences/preferences.vala',
10 |
11 | 'ui/edit_view/edit_view.vala',
12 | 'ui/edit_view/toolbar/toolbar.vala',
13 | 'ui/file_editor/file_editor_window.vala',
14 |
15 | 'ui/fuzzy_string_sorter.vala',
16 |
17 | 'ui/window/app_menu/app_menu.vala',
18 | 'ui/window/font_scale/font_scale.vala',
19 | 'ui/window/notebooks_bar/notebooks_bar.vala',
20 | 'ui/window/notebooks_bar/icon.vala',
21 | 'ui/window/notebooks_bar/notebook_menu.vala',
22 | 'ui/window/notebooks_bar/notebook_create_popup.vala',
23 | 'ui/window/notebooks_bar/notebook_sidebar_item.vala',
24 |
25 | 'ui/window/sidebar/note_card.vala',
26 | 'ui/window/sidebar/note_menu.vala',
27 | 'ui/window/sidebar/note_create_popup.vala',
28 |
29 | 'ui/edit_view/cheatsheet/markdown_cheatsheet.vala',
30 |
31 | 'ui/popup/notebook_selection_popup/notebook_list_item.vala',
32 | 'ui/popup/notebook_selection_popup/notebook_selection_popup.vala',
33 |
34 | 'ui/widgets/markdown/markdown_buffer.vala',
35 | 'ui/widgets/markdown/markdown_view.vala',
36 | 'ui/widgets/markdown/heading_popover.vala',
37 |
38 | 'ui/widgets/theme_selector/theme_selector.vala',
39 | 'ui/widgets/notebook_icon/notebook_preview.vala',
40 |
41 | lib_sources,
42 |
43 | 'color.vala',
44 | ]
45 |
46 | subdir('ui')
47 | generated_resources_dir = meson.current_build_dir()
48 |
49 | folio_sources += gnome.compile_resources('folio-resources',
50 | 'folio.gresource.xml',
51 | dependencies: blueprints,
52 | source_dir: ['src', generated_resources_dir],
53 | c_name: 'folio'
54 | )
55 |
56 | folio_sources += custom_target('blueprint-hack',
57 | input: blueprints,
58 | output: 'blueprint.vala',
59 | command: [find_program('touch'), '@OUTPUT@']
60 | )
61 |
62 |
63 | conf = configuration_data()
64 | conf.set_quoted('APP_ID', app_id)
65 | conf.set_quoted('VERSION', version)
66 | conf.set_quoted('GNOMELOCALEDIR', get_option('prefix') / get_option('localedir'))
67 | configure_file(output: 'config.h', configuration: conf)
68 |
69 | cc = meson.get_compiler('c')
70 | m_dep = cc.find_library('m', required: false)
71 |
72 | folio_deps = [
73 | dependency('glib-2.0'),
74 | dependency('gobject-2.0'),
75 | dependency('gmodule-2.0'),
76 | dependency('gtk4'),
77 | dependency('gio-2.0'),
78 | dependency('gee-0.8'),
79 | dependency('libadwaita-1', version: '>=1.4.0', required: true),
80 | dependency('gtksourceview-5'),
81 | m_dep,
82 | ]
83 |
84 | executable(
85 | meson.project_name(),
86 | folio_sources,
87 | c_args: [
88 | '-DGETTEXT_PACKAGE="' + app_id + '"',
89 | '-DG_LOG_DOMAIN="Folio"'
90 | ],
91 | vala_args: [
92 | meson.project_source_root() + '/src/config.vapi',
93 | '--target-glib=2.72.1',
94 | '--gresourcesdir=' + generated_resources_dir,
95 | ],
96 | dependencies: folio_deps,
97 | implicit_include_directories: true,
98 | install: true,
99 | )
100 |
--------------------------------------------------------------------------------
/src/ui/edit_view/cheatsheet/markdown_cheatsheet.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioMarkdownCheatsheet : Adw.Dialog {
5 | styles [ "cheatsheet" ]
6 | content-width: 500;
7 | content-height: 440;
8 |
9 | child: Adw.ToolbarView {
10 |
11 | [top]
12 | Adw.HeaderBar {}
13 |
14 | content: ScrolledWindow {
15 | $GtkMarkdownView text_view {
16 | tab-width: 4;
17 | auto-indent: true;
18 | wrap-mode: word-char;
19 | bottom-margin: 18;
20 | left-margin: 18;
21 | right-margin: 18;
22 | top-margin: 18;
23 | hexpand: true;
24 | vexpand: true;
25 | editable: false;
26 | cursor-visible: false;
27 | show-gutter: false;
28 |
29 | styles ["note-font"]
30 | }
31 | };
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/ui/edit_view/cheatsheet/markdown_cheatsheet.md:
--------------------------------------------------------------------------------
1 | ## Headings
2 |
3 | ##### Heading 1
4 | `# Heading`
5 | H1
6 |
7 | ##### Heading 2
8 | `## Heading`
9 | H2
10 |
11 | ##### Heading 3
12 | `### Heading`
13 | H3
14 |
15 | ##### Heading 4
16 | `#### Heading`
17 | H4
18 |
19 | ##### Heading 5
20 | `##### Heading`
21 | H5
22 |
23 | ##### Heading 6
24 | `###### Heading`
25 | H6
26 |
27 | ## Emphasis
28 |
29 | `**text**`
30 | `__text__`
31 | Displays text in **bold**
32 |
33 | `*text*`
34 | `_text_`
35 | Displays text in _italics_
36 |
37 | `~~text~~`
38 | Adds ~~strikethrough~~ effect to the text
39 |
40 | `==text==`
41 | Adds ==highlight== effect to the text
42 |
43 | ## Lists
44 |
45 | ##### Unordered Lists
46 | `- list item`
47 | `- list item`
48 |
49 | ##### Ordered Lists
50 | `1. list item`
51 | `2. list item`
52 |
53 | ## Links
54 |
55 | ##### Link
56 | `[link label](https://linkurl.io)`
57 | Relative file paths can be used to link to other notes:
58 | - `[link label](file://./My big list of links)`
59 | - `[link label](./My big list of links)`
60 | - `[link label](./My big list of links.md)`
61 | - `[link label](file://../Junk links/My big list of links)`
62 | - `[link label](../Junk links/My big list of links)`
63 | - `[link label](../Junk links/My big list of links.md)`
64 |
65 | ## Blockquotes
66 |
67 | `> quote`
68 |
69 | ## Horizontal Rule
70 |
71 | `- - -`
72 | `* * *`
73 | `---------`
74 | `*********`
75 |
76 | ## Code
77 |
78 | ##### Code span
79 | `code`
80 |
81 | ##### Escaped code span
82 | ``tis a ` backtick inside``
83 |
84 | ##### Code block
85 | ```language-name
86 | code
87 | ```
88 |
89 |
--------------------------------------------------------------------------------
/src/ui/edit_view/cheatsheet/markdown_cheatsheet.vala:
--------------------------------------------------------------------------------
1 | /* window.vala
2 | *
3 | * Copyright 2022 Zagura
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | [GtkTemplate (ui = "/com/toolstack/Folio/markdown_cheatsheet.ui")]
20 | public class Folio.MarkdownCheatsheet : Adw.Dialog {
21 |
22 | [GtkChild]
23 | unowned GtkMarkdown.View text_view;
24 |
25 | private Application app;
26 |
27 | public MarkdownCheatsheet (Application app) {
28 | Object (
29 | title: Strings.MARKDOWN_CHEATSHEET
30 | );
31 |
32 | this.app = app;
33 |
34 | try {
35 | var buffer = new GtkMarkdown.Buffer ();
36 | buffer.text = (string) resources_lookup_data (
37 | "/com/toolstack/Folio/markdown_cheatsheet.md",
38 | ResourceLookupFlags.NONE
39 | ).get_data ();
40 | text_view.buffer = buffer;
41 | } catch (Error e) {}
42 |
43 | app.style_manager.notify["dark"].connect (on_dark_mode_enabled);
44 | text_view.dark = app.style_manager.dark;
45 | }
46 |
47 | private void on_dark_mode_enabled () {
48 | text_view.dark = app.style_manager.dark;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/ui/edit_view/edit_view.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioEditView : Box {
4 |
5 | orientation: vertical;
6 |
7 | styles ["text-area"]
8 |
9 | ScrolledWindow scrolled_window {
10 |
11 | hexpand: true;
12 | vexpand: true;
13 |
14 | $GtkMarkdownView markdown_view {
15 |
16 | halign: center;
17 | hexpand: true;
18 | vexpand: true;
19 | tab-width: 4;
20 | auto-indent: true;
21 | wrap-mode: word-char;
22 | top-margin: 64;
23 | bottom-margin: 64;
24 | show-gutter: true;
25 |
26 | styles ["markdown-view"]
27 | }
28 | }
29 |
30 | $FolioToolbar toolbar {
31 | hexpand: true;
32 | vexpand: false;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/ui/edit_view/toolbar/toolbar.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | StringList heading_types {
4 | strings [
5 | _("Plain Text"),
6 | _("Heading 1"),
7 | _("Heading 2"),
8 | _("Heading 3"),
9 | _("Heading 4"),
10 | _("Heading 5"),
11 | _("Heading 6"),
12 | ]
13 | }
14 |
15 | template $FolioToolbar : Box {
16 |
17 | Box squeezer {
18 |
19 | hexpand: true;
20 | vexpand: false;
21 |
22 | styles ["bottom-toolbar"]
23 |
24 | Box regular_toolbar {
25 |
26 | orientation: horizontal;
27 |
28 | styles ["toolbar"]
29 |
30 | DropDown format_heading_type {
31 | model: heading_types;
32 | }
33 |
34 | Separator {
35 | styles ["spacer"]
36 | }
37 |
38 | Button {
39 | icon-name: "format-text-bold-symbolic";
40 | tooltip-text: _("Bold");
41 | action-name: "win.format-bold";
42 | }
43 |
44 | Button {
45 | icon-name: "format-text-italic-symbolic";
46 | tooltip-text: _("Italic");
47 | action-name: "win.format-italic";
48 | }
49 |
50 | Button {
51 | icon-name: "format-text-strikethrough-symbolic";
52 | tooltip-text: _("Strikethrough");
53 | action-name: "win.format-strikethrough";
54 | }
55 |
56 | Button {
57 | icon-name: "format-text-highlight-symbolic";
58 | tooltip-text: _("Highlight");
59 | action-name: "win.format-highlight";
60 | }
61 |
62 | Separator {
63 | styles ["spacer"]
64 | }
65 |
66 | Button {
67 | icon-name: "insert-link-symbolic";
68 | tooltip-text: _("Insert Link");
69 | action-name: "win.insert-link";
70 | }
71 |
72 | Button {
73 | icon-name: "insert-code-symbolic";
74 | tooltip-text: _("Insert Code");
75 | action-name: "win.insert-code-span";
76 | }
77 |
78 | Button {
79 | icon-name: "insert-horizontal-rule-symbolic";
80 | tooltip-text: _("Insert Horizontal Rule");
81 | action-name: "win.insert-horizontal-rule";
82 | }
83 |
84 | Box {
85 | hexpand: true;
86 | }
87 |
88 | Button {
89 | visible: bind template.cheatsheet-enabled;
90 | icon-name: "dialog-information-symbolic";
91 | tooltip-text: _("Markdown Cheatsheet");
92 | action-name: "app.markdown-cheatsheet";
93 | }
94 | }
95 |
96 | Box small_toolbar {
97 |
98 | orientation: horizontal;
99 | visible: false;
100 |
101 | styles ["toolbar"]
102 |
103 | Button {
104 | hexpand: true;
105 | icon-name: "format-text-bold-symbolic";
106 | tooltip-text: _("Bold");
107 | action-name: "win.format-bold";
108 | }
109 |
110 | Button {
111 | hexpand: true;
112 | icon-name: "format-text-italic-symbolic";
113 | tooltip-text: _("Italic");
114 | action-name: "win.format-italic";
115 | }
116 |
117 | MenuButton {
118 | hexpand: true;
119 | icon-name: "format-text-rich-symbolic";
120 | tooltip-text: _("Format");
121 | direction: up;
122 | popover: Popover {
123 |
124 | styles ["custom-popover-menu"]
125 |
126 | child: Box {
127 | orientation: vertical;
128 | spacing: 6;
129 |
130 | DropDown format_heading_type_mobile {
131 | model: heading_types;
132 | }
133 |
134 | Separator {}
135 |
136 | Box {
137 | orientation: horizontal;
138 |
139 | Button {
140 | icon-name: "format-text-bold-symbolic";
141 | tooltip-text: _("Bold");
142 | action-name: "win.format-bold";
143 | styles ["flat", "modelbutton"]
144 | }
145 |
146 | Button {
147 | icon-name: "format-text-italic-symbolic";
148 | tooltip-text: _("Italic");
149 | action-name: "win.format-italic";
150 | styles ["flat", "modelbutton"]
151 | }
152 |
153 | Button {
154 | icon-name: "format-text-strikethrough-symbolic";
155 | tooltip-text: _("Strikethrough");
156 | action-name: "win.format-strikethrough";
157 | styles ["flat", "modelbutton"]
158 | }
159 |
160 | Button {
161 | icon-name: "format-text-highlight-symbolic";
162 | tooltip-text: _("Highlight");
163 | action-name: "win.format-highlight";
164 | styles ["flat", "modelbutton"]
165 | }
166 | }
167 | };
168 | };
169 | }
170 |
171 | MenuButton {
172 | hexpand: true;
173 | icon-name: "insert-object-symbolic";
174 | tooltip-text: _("Insert");
175 | direction: up;
176 | menu-model: insert_menu;
177 | }
178 | }
179 | }
180 |
181 |
182 | }
183 |
184 | menu insert_menu {
185 | section {
186 | item (_("Insert Link"), "win.insert-link", "insert-link-symbolic")
187 | item (_("Insert Code"), "win.insert-code-span", "insert-code-symbolic")
188 | item (_("Insert Horizontal Rule"), "win.insert-horizontal-rule", "horizontal-rule-symbolic")
189 | }
190 | }
191 |
192 | menu format_menu {
193 | section {
194 | item (_("Bold"), "win.format-bold", "format-text-bold-symbolic")
195 | item (_("Italic"), "win.format-italic", "format-text-italic-symbolic")
196 | item (_("Strikethrough"), "win.format-strikethrough", "format-text-strikethrough-symbolic")
197 | item (_("Highlight"), "win.format-highlight", "format-text-highlight-symbolic")
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/ui/edit_view/toolbar/toolbar.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/toolbar.ui")]
3 | public class Folio.Toolbar : Gtk.Box {
4 |
5 | public bool compacted {
6 | get { return small_toolbar.visible; }
7 | }
8 |
9 | public int heading_i { get; set; }
10 |
11 | public bool cheatsheet_enabled { get; set; }
12 |
13 | public signal void heading_i_changed (int i);
14 |
15 | [GtkChild] unowned Gtk.Box regular_toolbar;
16 | [GtkChild] unowned Gtk.Box small_toolbar;
17 | [GtkChild] unowned Gtk.Box squeezer;
18 | [GtkChild] unowned Gtk.DropDown format_heading_type;
19 | [GtkChild] unowned Gtk.DropDown format_heading_type_mobile;
20 |
21 | construct {
22 | notify["heading-i"].connect (on_heading_changed);
23 | format_heading_type.set_selected (heading_i);
24 | format_heading_type_mobile.set_selected (heading_i);
25 | format_heading_type.notify["selected-item"].connect (on_heading_type_selected_item_changed);
26 | format_heading_type_mobile.notify["selected-item"].connect (on_heading_type_mobile_selected_item_changed);
27 | var settings = new Settings (Config.APP_ID);
28 | settings.bind ("cheatsheet-enabled", this, "cheatsheet-enabled", SettingsBindFlags.DEFAULT);
29 | }
30 |
31 | private void on_heading_changed () {
32 | format_heading_type.set_selected (heading_i);
33 | format_heading_type_mobile.set_selected (heading_i);
34 | }
35 |
36 | private void on_heading_type_selected_item_changed () {
37 | heading_i_changed((int)format_heading_type.get_selected ());
38 | }
39 |
40 | private void on_heading_type_mobile_selected_item_changed () {
41 | heading_i_changed((int)format_heading_type_mobile.get_selected ());
42 | }
43 |
44 | public void resize_toolbar () {
45 | var width = squeezer.get_width ();
46 |
47 | if ( width < 500 && width > 0) {
48 | regular_toolbar.visible = false;
49 | small_toolbar.visible = true;
50 | } else {
51 | regular_toolbar.visible = true;
52 | small_toolbar.visible = false;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/ui/file_editor/file_editor_window.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioFileEditorWindow : Adw.ApplicationWindow {
5 | default-width: 720;
6 | default-height: 512;
7 |
8 | Adw.ToastOverlay toast_overlay {
9 |
10 | Overlay {
11 |
12 | hexpand: true;
13 | vexpand: true;
14 | width-request: 390;
15 |
16 | styles ["text-area"]
17 |
18 | $FolioEditView edit_view {
19 | hexpand: true;
20 | vexpand: true;
21 | width-request: 390;
22 | }
23 |
24 | [overlay]
25 | Adw.HeaderBar headerbar {
26 |
27 | hexpand: true;
28 | vexpand: false;
29 | valign: start;
30 |
31 | [title]
32 | Box {
33 | orientation: vertical;
34 | valign: center;
35 | CenterBox {
36 | orientation: horizontal;
37 | halign: center;
38 |
39 | styles ["title"]
40 |
41 | [start]
42 | Label save_indicator {
43 | label: "•";
44 | visible: false;
45 | hexpand: false;
46 | halign: end;
47 | margin-end: 6;
48 | }
49 | [center]
50 | Label file_title {
51 | halign: fill;
52 | }
53 | }
54 | Label file_subtitle {
55 | styles ["subtitle"]
56 | }
57 | }
58 |
59 | [end]
60 | $FolioAppMenu {}
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/ui/file_editor/file_editor_window.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/file_editor_window.ui")]
3 | public class Folio.FileEditorWindow : Adw.ApplicationWindow {
4 |
5 | [GtkChild] unowned Gtk.Label file_title;
6 | [GtkChild] unowned Gtk.Label file_subtitle;
7 | [GtkChild] unowned Gtk.Label save_indicator;
8 |
9 | [GtkChild] unowned Adw.HeaderBar headerbar;
10 |
11 | [GtkChild] unowned EditView edit_view;
12 | [GtkChild] unowned Adw.ToastOverlay toast_overlay;
13 |
14 | private ActionEntry[] ACTIONS = {
15 | { "format-bold", on_format_bold },
16 | { "format-italic", on_format_italic },
17 | { "format-strikethrough", on_format_strikethrough },
18 | { "format-highlight", on_format_highlight },
19 |
20 | { "insert-link", on_insert_link },
21 | { "insert-code-span", on_insert_code_span },
22 | { "insert-horizontal-rule", on_insert_horizontal_rule },
23 |
24 | { "save-note", save },
25 | { "toggle-fullscreen", toggle_fullscreen },
26 | };
27 |
28 | private GtkMarkdown.Buffer current_buffer;
29 | private File current_file;
30 | private Application app;
31 |
32 | construct {
33 | add_action_entries (ACTIONS, this);
34 |
35 | Gtk.IconTheme.get_for_display (display).add_resource_path ("/com/toolstack/Folio/graphics/");
36 | }
37 |
38 | public FileEditorWindow (Application app, File file) {
39 | Object (
40 | application: app,
41 | title: @"$(file.get_basename ()) ($(file.get_path ())) - $(Strings.APP_NAME)",
42 | icon_name: Config.APP_ID
43 | );
44 |
45 | this.app = app;
46 |
47 | current_file = file;
48 | edit_view.on_dark_changed(app.style_manager.dark);
49 | app.style_manager.notify["dark"].connect (on_style_manager_change);
50 |
51 | file_title.label = file.get_basename ();
52 | file_subtitle.label = file.get_path ();
53 |
54 | string etag_out;
55 | uint8[] text_data = {};
56 | try {
57 | file.load_contents (null, out text_data, out etag_out);
58 | } catch (Error e) {
59 | // Probably need to do something else here...
60 | return;
61 | }
62 | // Make sure the last character of the file is a return, otherwise some of the regex's will break.
63 | if (text_data[text_data.length - 1] != 10) { text_data += 10; }
64 | current_buffer = new GtkMarkdown.Buffer ((string) text_data);
65 | edit_view.buffer = current_buffer;
66 | edit_view.is_editable = true;
67 |
68 | close_request.connect (on_close_request);
69 |
70 | edit_view.scrolled_window.vadjustment.notify["value"].connect (on_scrolled_window_vadjustment_changed);
71 |
72 | recolor (Color.RGB ());
73 |
74 | current_buffer.begin_user_action.connect (on_begin_user_action_change);
75 |
76 | save_indicator.visible = false;
77 | }
78 |
79 | private void on_begin_user_action_change () {
80 | save_indicator.visible = true;
81 | }
82 |
83 | private void on_scrolled_window_vadjustment_changed () {
84 | var v = edit_view.scrolled_window.vadjustment.value;
85 | if (v == 0) headerbar.remove_css_class ("overlaid");
86 | else headerbar.add_css_class ("overlaid");
87 | }
88 |
89 | private void on_style_manager_change () {
90 | edit_view.on_dark_changed(app.style_manager.dark);
91 | }
92 |
93 | private bool on_close_request () {
94 | save_file ();
95 | return false;
96 | }
97 |
98 | public void save_file () {
99 | FileUtils.save_to (current_file, current_buffer.get_all_text ());
100 | }
101 |
102 | public void save () {
103 | save_file ();
104 | save_indicator.visible = false;
105 | }
106 |
107 | public void toast (string text) {
108 | var toast = new Adw.Toast (text);
109 | toast_overlay.add_toast (toast);
110 | }
111 |
112 | private void recolor (Color.RGB rgb) {
113 | var rgba = Gdk.RGBA ();
114 | var light_rgba = Gdk.RGBA ();
115 | var hsl = Color.rgb_to_hsl (rgb);
116 | {
117 | hsl.l = 0.5f;
118 | Color.hsl_to_rgb (hsl, out rgb);
119 | Color.rgb_to_RGBA (rgb, out rgba);
120 | hsl.l = 0.7f;
121 | Color.hsl_to_rgb (hsl, out rgb);
122 | Color.rgb_to_RGBA (rgb, out light_rgba);
123 | }
124 | var css = new Gtk.CssProvider ();
125 | css.load_from_string (@"@define-color theme_color $rgba;@define-color notebook_light_color $light_rgba;");
126 | get_style_context ().add_provider (css, -1);
127 | edit_view.theme_color = rgba;
128 | }
129 |
130 | private void toggle_fullscreen () {
131 | fullscreened = !fullscreened;
132 | }
133 |
134 | private void on_format_bold () { edit_view.format_selection_bold (); }
135 | private void on_format_italic () { edit_view.format_selection_italic (); }
136 | private void on_format_strikethrough () { edit_view.format_selection_strikethrough (); }
137 | private void on_format_highlight () { edit_view.format_selection_highlight (); }
138 | private void on_insert_link () { edit_view.insert_link (); }
139 | private void on_insert_code_span () { edit_view.insert_code_span (); }
140 | private void on_insert_horizontal_rule () { edit_view.insert_horizontal_rule (); }
141 | }
142 |
--------------------------------------------------------------------------------
/src/ui/fuzzy_string_sorter.vala:
--------------------------------------------------------------------------------
1 |
2 | public class Folio.FuzzyStringSorter : Gtk.Sorter {
3 |
4 | public FuzzyStringSorter (Gtk.Expression expression) {
5 | this._expression = expression;
6 | }
7 |
8 | public string? target {
9 | get { return _target; }
10 | set {
11 | _target = value.down ().normalize ();
12 | changed (Gtk.SorterChange.DIFFERENT);
13 | }
14 | }
15 |
16 | private string _target;
17 | private Gtk.Expression _expression;
18 |
19 | public override Gtk.SorterOrder get_order () {
20 | return (_target == null || _target.length == 0)
21 | ? Gtk.SorterOrder.NONE
22 | : Gtk.SorterOrder.PARTIAL;
23 | }
24 |
25 | public override Gtk.Ordering compare (Object? item1, Object? item2) {
26 | if (_target == null) return Gtk.Ordering.EQUAL;
27 | if (item1 == null) return Gtk.Ordering.EQUAL;
28 | if (item2 == null) return Gtk.Ordering.EQUAL;
29 | var d1 = get_distance (item1);
30 | var d2 = get_distance (item2);
31 | return (d1 == d2)
32 | ? Gtk.Ordering.EQUAL : (d1 > d2)
33 | ? Gtk.Ordering.LARGER
34 | : Gtk.Ordering.SMALLER;
35 | }
36 |
37 | private int get_distance (Object item) {
38 | //var val = Value (_expression.get_value_type ());
39 | //_expression.evaluate (item, val);
40 | var str = ((Note) item).name;
41 | if (str == null) return 0;
42 | return Util.search_distance (str.down ().normalize (), _target);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/ui/meson.build:
--------------------------------------------------------------------------------
1 |
2 | blueprint_files = files(
3 | 'edit_view/edit_view.blp',
4 | 'edit_view/toolbar/toolbar.blp',
5 | 'edit_view/cheatsheet/markdown_cheatsheet.blp',
6 | 'file_editor/file_editor_window.blp',
7 | 'popup/shortcut_window.blp',
8 | 'popup/notebook_selection_popup/notebook_list_item.blp',
9 | 'popup/notebook_selection_popup/notebook_selection_popup.blp',
10 | 'preferences/preferences.blp',
11 | 'widgets/markdown/heading_popover.blp',
12 | 'widgets/notebook_icon/notebook_preview.blp',
13 | 'widgets/theme_selector/theme_selector.blp',
14 | 'window/window.blp',
15 | 'window/app_menu/app_menu.blp',
16 | 'window/font_scale/font_scale.blp',
17 | 'window/notebooks_bar/notebook_create_popup.blp',
18 | 'window/notebooks_bar/icon.blp',
19 | 'window/notebooks_bar/notebook_menu.blp',
20 | 'window/notebooks_bar/notebooks_bar.blp',
21 | 'window/sidebar/note_card.blp',
22 | 'window/sidebar/note_create_popup.blp',
23 | 'window/sidebar/note_menu.blp',
24 | )
25 |
26 | blueprint_ui = []
27 | foreach blp : blueprint_files
28 | blueprint_ui += '@0@'.format(blp).replace('.blp', '.ui').split('/')[-1]
29 | endforeach
30 |
31 | blueprints = custom_target('blueprints',
32 | input: blueprint_files,
33 | output: blueprint_ui,
34 | command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTDIR@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
35 | )
--------------------------------------------------------------------------------
/src/ui/popup/notebook_selection_popup/notebook_list_item.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNotebookListItem : Box {
4 |
5 | hexpand: true;
6 | orientation: horizontal;
7 |
8 | $FolioNotebookPreview icon {}
9 |
10 | Label label {
11 | ellipsize: end;
12 | halign: start;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/ui/popup/notebook_selection_popup/notebook_list_item.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/popup/notebook_list_item.ui")]
3 | public class Folio.NotebookListItem : Gtk.Box {
4 |
5 | [GtkChild]
6 | unowned NotebookPreview icon;
7 |
8 | [GtkChild]
9 | unowned Gtk.Label label;
10 |
11 | public Notebook notebook {
12 | get { return _notebook; }
13 | set {
14 | this._notebook = value;
15 | label.label = value.name;
16 | icon.notebook_info = value.info;
17 |
18 | var settings = new Settings (Config.APP_ID);
19 | if (settings.get_boolean ("long-notebook-names") == true) {
20 | label.set_ellipsize (Pango.EllipsizeMode.NONE);
21 | } else {
22 | label.set_ellipsize (Pango.EllipsizeMode.END);
23 | }
24 | }
25 | }
26 |
27 | private Notebook _notebook;
28 | }
29 |
--------------------------------------------------------------------------------
/src/ui/popup/notebook_selection_popup/notebook_selection_popup.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioNotebookSelectionPopup : Adw.Dialog {
5 |
6 | content-width: 360;
7 | content-height: 420;
8 |
9 | child: Adw.ToolbarView {
10 | [top]
11 | Adw.HeaderBar headerbar {
12 | hexpand: true;
13 | show-start-title-buttons: false;
14 | show-end-title-buttons: false;
15 |
16 | [start]
17 | Button button_cancel {
18 | label: _("Cancel");
19 | }
20 |
21 | [end]
22 | Button button_confirm {
23 | label: _("Confirm");
24 | styles ["suggested-action"]
25 | }
26 | }
27 |
28 | content: ScrolledWindow scrolled_window {
29 |
30 | hexpand: true;
31 | vexpand: true;
32 |
33 | ListView notebooks_list {
34 | hexpand: true;
35 | styles ["boxed-list", "notebook-list"]
36 | }
37 | };
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/src/ui/popup/notebook_selection_popup/notebook_selection_popup.vala:
--------------------------------------------------------------------------------
1 |
2 | public delegate void Folio.OnNotebookSelected (Notebook notebook);
3 |
4 | [GtkTemplate (ui = "/com/toolstack/Folio/popup/notebook_selection_popup.ui")]
5 | public class Folio.NotebookSelectionPopup : Adw.Dialog {
6 |
7 | [GtkChild]
8 | unowned Gtk.ListView notebooks_list;
9 |
10 | [GtkChild]
11 | unowned Gtk.Button button_cancel;
12 |
13 | [GtkChild]
14 | unowned Gtk.Button button_confirm;
15 |
16 | [GtkChild]
17 | unowned Adw.HeaderBar headerbar;
18 |
19 | [GtkChild]
20 | unowned Gtk.ScrolledWindow scrolled_window;
21 |
22 | private OnNotebookSelected callback;
23 | private Gtk.SingleSelection model;
24 |
25 | private void on_button_cancel_clicked () {
26 | this.close ();
27 | }
28 |
29 | private void on_factory_setup_change (Object obj) {
30 | var li = obj as Gtk.ListItem;
31 | if (li != null) {
32 | li.child = new NotebookListItem ();
33 | }
34 | }
35 |
36 | private void on_factory_bind_change (Object obj) {
37 | var li = obj as Gtk.ListItem;
38 | if (li != null) {
39 | var nli = li.child as NotebookListItem;
40 | if (nli != null) {
41 | nli.notebook = li.item as Notebook;
42 | }
43 | }
44 | }
45 |
46 | private void on_button_confirm_clicked () {
47 | close ();
48 | this.callback (model.selected_item as Notebook);
49 | }
50 |
51 | private void on_scrolled_window_vadjustment_changed () {
52 | var v = scrolled_window.vadjustment.value;
53 | if (v == 0) headerbar.remove_css_class ("overlaid");
54 | else headerbar.add_css_class ("overlaid");
55 | }
56 |
57 | public NotebookSelectionPopup (Provider provider, string title, string action_name, owned OnNotebookSelected callback) {
58 | Object ();
59 | this.title = title;
60 | this.button_confirm.label = action_name;
61 | this.callback = (owned) callback;
62 | button_cancel.clicked.connect (on_button_cancel_clicked);
63 | model = new Gtk.SingleSelection (provider);
64 | var factory = new Gtk.SignalListItemFactory ();
65 | factory.setup.connect (on_factory_setup_change);
66 | factory.bind.connect (on_factory_bind_change);
67 | notebooks_list.model = model;
68 | notebooks_list.factory = factory;
69 | button_confirm.clicked.connect (on_button_confirm_clicked);
70 |
71 | scrolled_window.vadjustment.notify["value"].connect (on_scrolled_window_vadjustment_changed);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/ui/popup/shortcut_window.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | ShortcutsWindow help_overlay {
4 |
5 | modal: true;
6 |
7 | ShortcutsSection {
8 |
9 | section-name: "shortcuts";
10 | max-height: 10;
11 |
12 | ShortcutsGroup {
13 |
14 | title: _("Note");
15 |
16 | ShortcutsShortcut {
17 | title: _("New Note");
18 | action-name: "app.new-note";
19 | }
20 |
21 | ShortcutsShortcut {
22 | title: _("Edit Note");
23 | action-name: "app.edit-note";
24 | }
25 |
26 | ShortcutsShortcut {
27 | title: _("Save Note");
28 | action-name: "win.save-note";
29 | }
30 | }
31 |
32 | ShortcutsGroup {
33 |
34 | title: _("Notebook");
35 |
36 | ShortcutsShortcut {
37 | title: _("New Notebook");
38 | action-name: "app.new-notebook";
39 | }
40 |
41 | ShortcutsShortcut {
42 | title: _("Edit Notebook");
43 | action-name: "app.edit-notebook";
44 | }
45 | }
46 |
47 | ShortcutsGroup {
48 |
49 | title: _("General");
50 |
51 | ShortcutsShortcut {
52 | title: _("Show Keyboard Shortcuts");
53 | action-name: "win.show-help-overlay";
54 | }
55 |
56 | ShortcutsShortcut {
57 | title: _("Quit");
58 | action-name: "app.quit";
59 | }
60 |
61 | ShortcutsShortcut {
62 | title: _("Preferences");
63 | action-name: "app.preferences";
64 | }
65 | }
66 |
67 | ShortcutsGroup {
68 |
69 | title: _("Formatting");
70 |
71 | ShortcutsShortcut {
72 | title: _("Bold");
73 | action-name: "win.format-bold";
74 | }
75 |
76 | ShortcutsShortcut {
77 | title: _("Italic");
78 | action-name: "win.format-italic";
79 | }
80 |
81 | ShortcutsShortcut {
82 | title: _("Strikethrough");
83 | action-name: "win.format-strikethrough";
84 | }
85 |
86 | ShortcutsShortcut {
87 | title: _("Highlight");
88 | action-name: "win.format-highlight";
89 | }
90 |
91 | ShortcutsShortcut {
92 | title: _("Insert Link");
93 | action-name: "win.insert-link";
94 | }
95 | }
96 |
97 | ShortcutsGroup {
98 |
99 | title: _("Navigation");
100 |
101 | ShortcutsShortcut {
102 | title: _("Toggle Sidebar");
103 | action-name: "win.toggle-sidebar";
104 | }
105 |
106 | ShortcutsShortcut {
107 | title: _("Search Notes");
108 | action-name: "win.search-notes";
109 | }
110 | }
111 |
112 | ShortcutsGroup {
113 |
114 | title: _("Display");
115 |
116 | ShortcutsShortcut {
117 | title: _("Full Screen");
118 | action-name: "win.toggle-fullscreen";
119 | }
120 |
121 | ShortcutsShortcut {
122 | title: _("Zoom In");
123 | action-name: "win.zoom-in";
124 | }
125 |
126 | ShortcutsShortcut {
127 | title: _("Zoom Out");
128 | action-name: "win.zoom-out";
129 | }
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/ui/strings.vala:
--------------------------------------------------------------------------------
1 |
2 | public class Folio.Strings {
3 | public static string APP_NAME { get { return _("Folio"); } }
4 | public static string TRASH { get { return _("Trash"); } }
5 | public static string X_NOTES { get { return _("%d Notes"); } }
6 | public static string EMPTY_TRASH_CONFIRMATION { get { return _("Are you sure you want to delete everything in the trash?"); } }
7 | public abstract const string EMPTY_TRASH = _("Empty Trash");
8 | public abstract const string NEW_NOTE = _("New Note");
9 | public abstract const string CREATE_NOTEBOOK_BEFORE_CREATING_NOTE = _("Create/choose a notebook before creating a note");
10 | public abstract const string SELECT_NOTE_TO_EDIT = _("Please, select a note to edit");
11 | public abstract const string SELECT_NOTE_TO_DELETE = _("Please, select a note to delete");
12 | public abstract const string EXPORT_NOTE = _("Export Note");
13 | public abstract const string EXPORT = _("Export");
14 | public abstract const string NEW_NOTEBOOK = _("New Notebook");
15 | public abstract const string RENAME_NOTE = _("Rename Note");
16 | public abstract const string MOVE_TO_NOTEBOOK = _("Move to Notebook");
17 | public abstract const string MOVE = _("Move");
18 | public abstract const string NOTE_X_ALREADY_EXISTS_IN_X = _("Note “%s” already exists in notebook “%s”");
19 | public abstract const string DELETE_NOTE_CONFIRMATION = _("Are you sure you want to delete the note “%s”?");
20 | public abstract const string DELETE_NOTE = _("Delete Note");
21 | public abstract const string DELETE_NOTEBOOK_CONFIRMATION = _("Are you sure you want to delete the notebook “%s”?");
22 | public abstract const string DELETE_NOTEBOOK = _("Delete Notebook");
23 | public abstract const string EDIT_NOTEBOOK = _("Edit Notebook");
24 | public abstract const string NOTE_NAME_SHOULDNT_CONTAIN_RESERVED_CHAR = _("Note names cannot contain a “/”");
25 | public abstract const string NOTE_NAME_SHOULDNT_BE_BLANK = _("Note names cannot be blank");
26 | public abstract const string NOTE_X_ALREADY_EXISTS = _("Note “%s” already exists");
27 | public abstract const string COULDNT_CREATE_NOTE = _("Could not create note");
28 | public abstract const string COULDNT_CHANGE_NOTE = _("Could not change note");
29 | public abstract const string COULDNT_DELETE_NOTE = _("Could not delete note");
30 | public abstract const string COULDNT_RESTORE_NOTE = _("Could not restore note");
31 | public abstract const string SAVED_X_TO_X = _("Saved “%s” to “%s”");
32 | public abstract const string UNKNOWN_ERROR = _("Unknown error");
33 | public abstract const string NOTEBOOK_NAME_SHOULDNT_CONTAIN_RESERVED_CHAR = _("Notebook names cannot contain a “/”");
34 | public abstract const string NOTEBOOK_NAME_SHOULDNT_BE_BLANK = _("Notebook names cannot be blank");
35 | public abstract const string NOTEBOOK_X_ALREADY_EXISTS = _("Notebook “%s” already exists");
36 | public abstract const string COULDNT_CREATE_NOTEBOOK = _("Could not create notebook");
37 | public abstract const string COULDNT_CHANGE_NOTEBOOK = _("Could not change notebook");
38 | public abstract const string COULDNT_DELETE_NOTEBOOK = _("Could not delete notebook");
39 | public abstract const string MARKDOWN_CHEATSHEET = _("Markdown Cheatsheet");
40 | public abstract const string SEARCH = _("Search");
41 | public abstract const string RENAME = _("Rename");
42 | public abstract const string COULDNT_FIND_APP_TO_HANDLE_URIS = _("Could not find an app to handle file URIs");
43 | public abstract const string APPLY = _("Apply");
44 | public abstract const string CANCEL = _("Cancel");
45 | public abstract const string PICK_NOTES_DIR = _("Pick where the notebooks will be stored");
46 | public abstract const string PICK_TRASH_DIR = _("Pick where the trash can will be stored");
47 | public abstract const string ALL_NOTES = _("All Notes");
48 | public abstract const string NOTEBOOK = _("Notebook");
49 | public abstract const string LAST_MODIFIED = _("Last Modified");
50 | public abstract const string EXTENSION = _("Extension");
51 | public abstract const string UNRECOGNIZED_URI = _("Unable to recognize URI");
52 | public abstract const string PICK_NOTE_FONT = _("Pick primary font for notes");
53 | public abstract const string PICK_CODE_FONT = _("Pick monospace font for code");
54 | public abstract const string FILE_CHANGED_ON_DISK = _("File Changed On Disk");
55 | public abstract const string FILE_CHANGED_DIALOG_TRIPLE = _("The file has changed on disk since it was last saved/loaded by Folio.\n\nYou may do one of the following:\n\n • Reload the file (discarding any changes you have made in Folio)\n • Overwrite the file (discarding any changes made outside of Folio)\n • Cancel the operation and manually resolve the issue\n\nNote: Canceling the save if you have already moved to a new note/notebook will discard your changes.");
56 | public abstract const string FILE_CHANGED_RELOAD = _("Reload");
57 | public abstract const string FILE_CHANGED_OVERWRITE = _("Overwrite");
58 | public abstract const string FILE_CHANGED_CANCEL = _("Cancel");
59 | public abstract const string FILE_CHANGED_DIALOG_DOUBLE = _("The file has changed on disk by another application.\n\nYou may do one of the following:\n\n • Reload the file (discarding any changes you have made in Folio)\n • Overwrite the file (discarding any changes made outside of Folio)");
60 | public abstract const string NEW_NOTE_NAME = _("Note");
61 | public abstract const string NEW_NOTE_NAME_X = _("Note %i");
62 | public abstract const string NOTE_SORT_ORDER_TIME_ASC = _("Modified Time - Ascending");
63 | public abstract const string NOTE_SORT_ORDER_TIME_DSC = _("Modified Time - Descending");
64 | public abstract const string NOTE_SORT_ORDER_ALPHA_ASC = _("Alphabetical - Ascending");
65 | public abstract const string NOTE_SORT_ORDER_ALPHA_DSC = _("Alphabetical - Descending");
66 | public abstract const string NOTE_SORT_ORDER_NATURAL_ASC = _("Natural - Ascending");
67 | public abstract const string NOTE_SORT_ORDER_NATURAL_DSC = _("Natural - Descending");
68 | public abstract const string UNABLE_TO_LAUNCH_EXTERNAL_APPLICATION = _("Unable to launch external application for file %s.");
69 | public abstract const string URL_DETECTION_AGGRESSIVE = _("Aggressive");
70 | public abstract const string URL_DETECTION_STRICT = _("Strict");
71 | public abstract const string URL_DETECTION_DISABLED = _("Disabled");
72 | public abstract const string NOTE_TRASHED = _("“%s” moved to trash");
73 | public abstract const string NOTE_RESTORED = _("“%s” restored");
74 | }
75 |
--------------------------------------------------------------------------------
/src/ui/widgets/markdown/heading_popover.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $GtkMarkdownHeadingPopover : Popover {
4 |
5 | styles ["custom-popover-menu"]
6 |
7 | Box {
8 |
9 | orientation: vertical;
10 |
11 | Button button_heading_1 {
12 | label: _("Heading 1");
13 | halign: start;
14 | styles ["flat", "modelbutton"]
15 | }
16 |
17 | Button button_heading_2 {
18 | label: _("Heading 2");
19 | halign: start;
20 | styles ["flat", "modelbutton"]
21 | }
22 |
23 | Button button_heading_3 {
24 | label: _("Heading 3");
25 | halign: start;
26 | styles ["flat", "modelbutton"]
27 | }
28 |
29 | Button button_heading_4 {
30 | label: _("Heading 4");
31 | halign: start;
32 | styles ["flat", "modelbutton"]
33 | }
34 |
35 | Button button_heading_5 {
36 | label: _("Heading 5");
37 | halign: start;
38 | styles ["flat", "modelbutton"]
39 | }
40 |
41 | Button button_heading_6 {
42 | label: _("Heading 6");
43 | halign: start;
44 | styles ["flat", "modelbutton"]
45 | }
46 |
47 | Button button_plain_text {
48 | label: _("Plain Text");
49 | halign: start;
50 | styles ["flat", "modelbutton"]
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/ui/widgets/markdown/heading_popover.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/markdown/heading_popover.ui")]
3 | public class GtkMarkdown.HeadingPopover : Gtk.Popover {
4 |
5 | [GtkChild]
6 | unowned Gtk.Button button_heading_1;
7 |
8 | [GtkChild]
9 | unowned Gtk.Button button_heading_2;
10 |
11 | [GtkChild]
12 | unowned Gtk.Button button_heading_3;
13 |
14 | [GtkChild]
15 | unowned Gtk.Button button_heading_4;
16 |
17 | [GtkChild]
18 | unowned Gtk.Button button_heading_5;
19 |
20 | [GtkChild]
21 | unowned Gtk.Button button_heading_6;
22 |
23 | [GtkChild]
24 | unowned Gtk.Button button_plain_text;
25 |
26 | private View view;
27 | private uint line;
28 |
29 | public HeadingPopover (View view, uint line) {
30 | this.view = view;
31 | this.line = line;
32 |
33 | button_heading_1.clicked.connect (on_button_heading_1_clicked);
34 | button_heading_2.clicked.connect (on_button_heading_2_clicked);
35 | button_heading_3.clicked.connect (on_button_heading_3_clicked);
36 | button_heading_4.clicked.connect (on_button_heading_4_clicked);
37 | button_heading_5.clicked.connect (on_button_heading_5_clicked);
38 | button_heading_6.clicked.connect (on_button_heading_6_clicked);
39 | button_plain_text.clicked.connect (on_button_heading_0_clicked);
40 | }
41 |
42 | private void set_heading_level (int level) {
43 | popdown ();
44 | view.set_title_level (line, level);
45 | }
46 |
47 | private void on_button_heading_1_clicked () {
48 | set_heading_level (1);
49 | }
50 |
51 | private void on_button_heading_2_clicked () {
52 | set_heading_level (2);
53 | }
54 |
55 | private void on_button_heading_3_clicked () {
56 | set_heading_level (3);
57 | }
58 |
59 | private void on_button_heading_4_clicked () {
60 | set_heading_level (4);
61 | }
62 |
63 | private void on_button_heading_5_clicked () {
64 | set_heading_level (5);
65 | }
66 |
67 | private void on_button_heading_6_clicked () {
68 | set_heading_level (6);
69 | }
70 |
71 | private void on_button_heading_0_clicked () {
72 | set_heading_level (0);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ui/widgets/markdown/markdown_buffer.vala:
--------------------------------------------------------------------------------
1 |
2 |
3 | public class GtkMarkdown.Buffer : GtkSource.Buffer {
4 |
5 | public Buffer (string? text = null) {
6 | Object ();
7 | this.text = text;
8 | }
9 |
10 | public string get_all_text () {
11 | Gtk.TextIter start, end;
12 | get_start_iter (out start);
13 | get_end_iter (out end);
14 | return get_text(start, end, true);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/ui/widgets/notebook_icon/notebook_preview.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNotebookPreview : Box {
4 |
5 | halign: center;
6 | hexpand: false;
7 |
8 | styles ["notebook"]
9 |
10 | Label label {
11 | hexpand: true;
12 | vexpand: false;
13 | halign: center;
14 | valign: center;
15 | }
16 |
17 | Image icon {
18 | hexpand: true;
19 | vexpand: false;
20 | halign: center;
21 | valign: center;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/ui/widgets/notebook_icon/notebook_preview.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/notebook_preview.ui")]
3 | public class Folio.NotebookPreview : Gtk.Box {
4 |
5 | [GtkChild] private unowned Gtk.Label label;
6 | [GtkChild] private unowned Gtk.Image icon;
7 |
8 | public NotebookInfo? notebook_info {
9 | get { return _notebook_info; }
10 | set {
11 | _notebook_info = value;
12 | update_color ();
13 | update_text ();
14 | }
15 | }
16 |
17 | public string notebook_name {
18 | set {
19 | if (_notebook_info == null)
20 | _notebook_info = new NotebookInfo (value);
21 | else
22 | _notebook_info.name = value;
23 | update_text ();
24 | }
25 | }
26 |
27 | public Gdk.RGBA color {
28 | set {
29 | _notebook_info.color = value;
30 | update_color ();
31 | }
32 | }
33 |
34 | public NotebookIconType icon_type {
35 | set {
36 | _notebook_info.icon_type = value;
37 | update_text ();
38 | }
39 | }
40 |
41 | public string icon_name {
42 | set {
43 | _notebook_info.icon_name = value;
44 | update_text ();
45 | }
46 | }
47 |
48 |
49 | private NotebookInfo? _notebook_info;
50 |
51 | private void update_color () {
52 | var info = _notebook_info;
53 | if (info == null)
54 | return;
55 | var fg_rgba = Gdk.RGBA ();
56 | {
57 | var rgb = Color.RGBA_to_rgb (info.color);
58 | var hsl = Color.rgb_to_hsl (rgb);
59 | var l = Color.get_luminance(rgb.r, rgb.g, rgb.b);
60 | var is_notebook_light = l > 0.7f;
61 | hsl.l = is_notebook_light ? 0.05f : 0.6f;
62 | hsl.s = 1.0f;
63 | var m = is_notebook_light ? 1.0f : 4.0f;
64 | Color.hsl_to_rgb (hsl, out rgb);
65 | Color.rgb_to_RGBA (rgb.multiply (m), out fg_rgba);
66 | }
67 | var css = new Gtk.CssProvider ();
68 | css.load_from_string (@"@define-color notebook_color $(info.color);@define-color notebook_fg_color $fg_rgba;");
69 | parent.get_style_context ().add_provider (css, -1);
70 | get_style_context ().add_provider (css, -1);
71 | }
72 |
73 | private void update_text () {
74 | var info = _notebook_info;
75 | if (info == null)
76 | return;
77 | tooltip_text = info.name;
78 | if (info.icon_type == NotebookIconType.PREDEFINED_ICON) {
79 | label.visible = false;
80 | icon.visible = true;
81 | icon.icon_name = info.icon_name ?? "icon-skull-symbolic";
82 | return;
83 | }
84 | icon.visible = false;
85 | label.visible = true;
86 | switch (info.icon_type) {
87 | case NotebookIconType.INITIALS:
88 | label.label = initials_split (info.name, " ");
89 | break;
90 | case NotebookIconType.INITIALS_SNAKE_CASE:
91 | label.label = initials_split (info.name, "_");
92 | break;
93 | case NotebookIconType.INITIALS_CAMEL_CASE:
94 | try {
95 | var regex = new Regex ("[A-Z]");
96 | MatchInfo matches;
97 | var matchable = info.name.substring (int.min(info.name.length, 1));
98 | if (regex.match_full (matchable, matchable.length, 0, 0, out matches)) {
99 | var _1 = matches.fetch (0);
100 | var result = (info.name.length == 0) ? "" : info.name.get_char (0).to_string ();
101 | if (_1 != null) {
102 | result += _1;
103 | }
104 | label.label = result;
105 | }
106 | else label.label = first_chars (info.name);
107 | } catch (RegexError e) {
108 | error (e.message);
109 | }
110 | break;
111 | case NotebookIconType.FIRST:
112 | label.label = first_chars (info.name);
113 | break;
114 | case NotebookIconType.CUSTOM_ICON:
115 | label.label = info.custom_icon_label;
116 | break;
117 | default:
118 | assert_not_reached();
119 | }
120 | }
121 |
122 | private string initials_split (string original, string delimiter) {
123 | var words = original.split (delimiter);
124 | string initials = "";
125 | var i = 0;
126 | foreach (string word in words) {
127 | if (word.length == 0) continue;
128 | initials += word.get_char (0).to_string ();
129 | i++;
130 | if (i == 2) break;
131 | }
132 | switch (i) {
133 | case 0:
134 | case 1:
135 | return first_chars (original);
136 | default:
137 | return initials;
138 | }
139 | }
140 |
141 | private string first_chars (string original) {
142 | if (original.length == 0)
143 | return "";
144 | var a = original.get_char (0).to_string ();
145 | if (a.length == original.length) return a;
146 | else return a + original.get_char (a.length).to_string ();
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/ui/widgets/theme_selector/theme_selector.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioThemeSelector : Box {
4 |
5 | orientation: horizontal;
6 | hexpand: true;
7 | spacing: 12;
8 |
9 | styles ["theme-selector"]
10 |
11 | CheckButton _auto {
12 | hexpand: true;
13 | halign: center;
14 | focus-on-click: false;
15 | tooltip-text: _("Follow system style");
16 | styles ["auto"]
17 | }
18 |
19 | CheckButton light {
20 | hexpand: true;
21 | halign: center;
22 | focus-on-click: false;
23 | tooltip-text: _("Light style");
24 | group: _auto;
25 | styles ["light"]
26 | }
27 |
28 | CheckButton dark {
29 | hexpand: true;
30 | halign: center;
31 | focus-on-click: false;
32 | tooltip-text: _("Dark style");
33 | group: light;
34 | styles ["dark"]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/ui/widgets/theme_selector/theme_selector.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/theme_selector.ui")]
3 | public class Folio.ThemeSelector : Gtk.Box {
4 |
5 | [GtkChild] unowned Gtk.CheckButton _auto;
6 | [GtkChild] unowned Gtk.CheckButton light;
7 | [GtkChild] unowned Gtk.CheckButton dark;
8 |
9 | private Settings settings;
10 | private Adw.StyleManager style_manager;
11 |
12 | construct {
13 | settings = new Settings (@"$(Config.APP_ID).Theme");
14 | style_manager = Adw.StyleManager.get_default ();
15 |
16 | switch (settings.get_enum ("variant")) {
17 | case Adw.ColorScheme.DEFAULT: _auto.active = true; break;
18 | case Adw.ColorScheme.FORCE_LIGHT: light.active = true; break;
19 | case Adw.ColorScheme.FORCE_DARK: dark.active = true; break;
20 | }
21 |
22 | _auto.toggled.connect (on_auto_changed);
23 | light.toggled.connect (on_light_changed);
24 | dark.toggled.connect (on_dark_changed);
25 | }
26 |
27 | private void on_auto_changed () {
28 | if (_auto.active) {
29 | settings.set_enum ("variant", Adw.ColorScheme.DEFAULT);
30 | style_manager.color_scheme = Adw.ColorScheme.DEFAULT;
31 | }
32 | }
33 |
34 | private void on_light_changed () {
35 | if (light.active) {
36 | settings.set_enum ("variant", Adw.ColorScheme.FORCE_LIGHT);
37 | style_manager.color_scheme = Adw.ColorScheme.FORCE_LIGHT;
38 | }
39 | }
40 |
41 | private void on_dark_changed () {
42 | if (dark.active) {
43 | settings.set_enum ("variant", Adw.ColorScheme.FORCE_DARK);
44 | style_manager.color_scheme = Adw.ColorScheme.FORCE_DARK;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/ui/window/app_menu/app_menu.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioAppMenu : Adw.Bin {
5 | MenuButton {
6 | hexpand: false;
7 | vexpand: false;
8 | valign: center;
9 | icon-name: "open-menu-symbolic";
10 | tooltip-text: _("Main Menu");
11 | primary: true;
12 | popover: PopoverMenu popover {
13 | menu-model: primary_menu;
14 | };
15 | }
16 | }
17 |
18 | menu primary_menu {
19 | section {
20 | item {
21 | custom: "theme";
22 | }
23 | }
24 | section {
25 | item {
26 | label: _("_New Notebook");
27 | action: "app.new-notebook";
28 | }
29 | item {
30 | label: _("_Edit Notebook");
31 | action: "app.edit-notebook";
32 | }
33 | }
34 | section {
35 | item {
36 | label: _("_Preferences");
37 | action: "app.preferences";
38 | }
39 | item {
40 | label: _("_Keyboard Shortcuts");
41 | action: "win.show-help-overlay";
42 | }
43 | item {
44 | label: _("_About Folio");
45 | action: "app.about";
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/ui/window/app_menu/app_menu.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/app_menu.ui")]
3 | public class Folio.AppMenu : Adw.Bin {
4 |
5 | [GtkChild] unowned Gtk.PopoverMenu popover;
6 |
7 | construct {
8 | popover.add_child (new ThemeSelector (), "theme");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/ui/window/font_scale/font_scale.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioFontScale : Box {
4 |
5 | orientation: horizontal;
6 | spacing: 12;
7 | margin-start: 12;
8 | margin-end: 12;
9 |
10 | Button dec {
11 | icon-name: "zoom-out-symbolic";
12 | styles ["flat", "circular"]
13 | }
14 |
15 | Label zoom_level {
16 | hexpand: true;
17 | }
18 |
19 | Button inc {
20 | icon-name: "zoom-in-symbolic";
21 | styles ["flat", "circular"]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/ui/window/font_scale/font_scale.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/font_scale.ui")]
3 | public class Folio.FontScale : Gtk.Box {
4 |
5 | [GtkChild] unowned Gtk.Button dec;
6 | [GtkChild] unowned Gtk.Button inc;
7 | [GtkChild] unowned Gtk.Label zoom_level;
8 |
9 | private EditView edit_view;
10 |
11 | construct {
12 | }
13 |
14 | public FontScale (EditView edit_view) {
15 | this.edit_view = edit_view;
16 | edit_view.notify["scale"].connect(update_scale);
17 | dec.clicked.connect(edit_view.zoom_out);
18 | inc.clicked.connect(edit_view.zoom_in);
19 | update_scale ();
20 | }
21 |
22 | private void update_scale () {
23 | zoom_level.label = edit_view.scale.to_string () + "%";
24 | dec.sensitive = edit_view.scale > EditView.MIN_SCALE;
25 | inc.sensitive = edit_view.scale < EditView.MAX_SCALE;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/icon.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNotebookIcon : Box {
4 |
5 | hexpand: true;
6 |
7 | accessibility {
8 | labelled-by: icon;
9 | }
10 |
11 | Overlay {
12 | hexpand: true;
13 |
14 | [overlay]
15 | Box {
16 | halign: start;
17 | styles ["marker"]
18 | }
19 |
20 | [overlay]
21 | $FolioNotebookPreview icon {}
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/icon.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/notebooks_bar/icon.ui")]
3 | public class Folio.NotebookIcon : Gtk.Box {
4 |
5 | construct {
6 | accessible_role = Gtk.AccessibleRole.LABEL;
7 | }
8 |
9 | [GtkChild]
10 | private unowned NotebookPreview icon;
11 |
12 | public Notebook notebook {
13 | get { return _notebook; }
14 | set {
15 | _notebook = value;
16 | icon.notebook_info = notebook.info;
17 | update_property (Gtk.AccessibleProperty.LABEL, notebook.name, -1);
18 | }
19 | }
20 |
21 | private Notebook _notebook;
22 |
23 | public NotebookIcon (Window window) {
24 | this.window = window;
25 | var long_press = new Gtk.GestureLongPress ();
26 | long_press.pressed.connect (show_popup);
27 | add_controller (long_press);
28 | var right_click = new Gtk.GestureClick ();
29 | right_click.button = Gdk.BUTTON_SECONDARY;
30 | right_click.pressed.connect (show_popup_right);
31 | add_controller (right_click);
32 | var drop_target = new Gtk.DropTarget (typeof (Note), Gdk.DragAction.MOVE);
33 | drop_target.preload = true;
34 | drop_target.drop.connect (on_drop_target_drop_change);
35 | add_controller (drop_target);
36 | }
37 |
38 | private Window window;
39 | private Gtk.Popover? current_popover = null;
40 |
41 | private bool on_drop_target_drop_change (Value v, double x, double y) {
42 | var note = v.get_object () as Note;
43 | if (note.notebook == _notebook)
44 | return false;
45 | window.window_model.move_note (note, _notebook);
46 | return true;
47 | }
48 |
49 | private void show_popup_right (int n, double x, double y) {
50 | show_popup (x, y);
51 | }
52 |
53 | private void show_popup (double x, double y) {
54 | if (current_popover != null) {
55 | current_popover.popdown();
56 | }
57 | var popover = new NotebookMenuPopover (window, _notebook);
58 | popover.closed.connect (on_popover_close);
59 | popover.autohide = true;
60 | popover.has_arrow = true;
61 | popover.position = Gtk.PositionType.RIGHT;
62 | popover.set_parent (this);
63 | popover.popup ();
64 | current_popover = popover;
65 | }
66 |
67 | private void on_popover_close () {
68 | current_popover.unparent ();
69 | current_popover = null;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebook_create_popup.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | StringList icon_types {
5 | strings [
6 | _("First Characters"),
7 | _("Initials"),
8 | _("Initials: camelCase"),
9 | _("Initials: snake_case"),
10 | _("Icon"),
11 | _("Custom"),
12 | ]
13 | }
14 |
15 | Gtk.ColorDialog color_dialog {
16 | title: _("Notebook Color");
17 | modal: true;
18 | }
19 |
20 | template $FolioNotebookCreatePopup : Adw.Dialog {
21 |
22 | content-width: 320;
23 |
24 | WindowHandle {
25 | Box {
26 |
27 | spacing: 16;
28 | orientation: vertical;
29 | hexpand: true;
30 | margin-bottom: 16;
31 |
32 | Overlay {
33 |
34 | [overlay]
35 | Adw.HeaderBar {
36 |
37 | hexpand: true;
38 | vexpand: false;
39 | valign: start;
40 |
41 | [title]
42 | Box {}
43 | }
44 |
45 | $FolioNotebookPreview preview {
46 | margin-top: 45;
47 | styles ["notebook-preview"]
48 | }
49 | }
50 |
51 | Box {
52 |
53 | orientation: horizontal;
54 | margin-start: 18;
55 | margin-end: 18;
56 |
57 | Entry entry {
58 | placeholder-text: _("Notebook Name");
59 | hexpand: true;
60 | }
61 | }
62 |
63 | Label notebook_name_warning {
64 | margin-start: 18;
65 | margin-end: 18;
66 | justify: center;
67 | wrap: true;
68 | visible: false;
69 |
70 | label: _("Duplicate notebook names are not allowed!");
71 | }
72 |
73 | Box {
74 |
75 | orientation: horizontal;
76 | margin-start: 18;
77 | margin-end: 18;
78 |
79 | styles ["linked"]
80 |
81 | DropDown icon_type_dropdown {
82 | model: icon_types;
83 | }
84 |
85 | Entry custom_icon_label {
86 | visible: false;
87 | hexpand: false;
88 | max-width-chars: 2;
89 |
90 | }
91 |
92 | MenuButton button_icon {
93 |
94 | visible: false;
95 | hexpand: false;
96 |
97 | styles ["icon-selector"]
98 |
99 | popover: Popover {
100 | child: ScrolledWindow {
101 |
102 | vexpand: false;
103 | height-request: 145;
104 | hscrollbar-policy: never;
105 | vscrollbar-policy: external;
106 |
107 | GridView icon_grid {
108 | hexpand: true;
109 | vexpand: false;
110 | min-columns: 4;
111 | max-columns: 4;
112 | }
113 | };
114 | };
115 | }
116 |
117 | ColorDialogButton button_color {
118 | rgba: "#2ec27eff";
119 | hexpand: false;
120 | dialog: color_dialog;
121 | }
122 | }
123 |
124 | Button button_create {
125 |
126 | label: _("Create Notebook");
127 | margin-start: 16;
128 | margin-end: 16;
129 |
130 | styles ["suggested-action"]
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebook_menu.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNotebookMenuPopover : Popover {
4 |
5 | styles ["custom-popover-menu"]
6 |
7 | Box {
8 |
9 | orientation: vertical;
10 |
11 | Button button_edit {
12 |
13 | styles ["flat", "modelbutton"]
14 |
15 | Label {
16 | label: _("Edit");
17 | halign: start;
18 | }
19 | }
20 |
21 | Button button_trash {
22 |
23 | styles ["flat", "modelbutton"]
24 |
25 | Label {
26 | label: _("Move all to Trash");
27 | halign: start;
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebook_menu.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/notebooks_bar/notebook_menu.ui")]
3 | public class Folio.NotebookMenuPopover : Gtk.Popover {
4 |
5 | [GtkChild]
6 | unowned Gtk.Button button_edit;
7 |
8 | [GtkChild]
9 | unowned Gtk.Button button_trash;
10 |
11 | private Window window;
12 | private Notebook notebook;
13 |
14 | public NotebookMenuPopover (Window window, Notebook notebook) {
15 | this.window = window;
16 | this.notebook = notebook;
17 |
18 | button_edit.clicked.connect (on_button_edit_clicked);
19 | button_trash.clicked.connect (on_button_trash_clicked);
20 | }
21 |
22 | private void on_button_edit_clicked () {
23 | popdown ();
24 | window.request_edit_notebook (notebook);
25 | }
26 |
27 | private void on_button_trash_clicked () {
28 | popdown ();
29 | window.request_delete_notebook (notebook);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebook_sidebar_item.vala:
--------------------------------------------------------------------------------
1 |
2 | public class Folio.NotebookSidebarItem : NotebookListItem {
3 |
4 | private Window window;
5 | private Gtk.Popover? current_popover = null;
6 |
7 | public NotebookSidebarItem (Window window) {
8 | this.window = window;
9 | var long_press = new Gtk.GestureLongPress ();
10 | long_press.pressed.connect (show_popup);
11 | add_controller (long_press);
12 | var right_click = new Gtk.GestureClick ();
13 | right_click.button = Gdk.BUTTON_SECONDARY;
14 | right_click.pressed.connect (show_popup_right);
15 | add_controller (right_click);
16 | var drop_target = new Gtk.DropTarget (typeof (Note), Gdk.DragAction.MOVE);
17 | drop_target.preload = true;
18 | drop_target.drop.connect (on_drop_target_drop_change);
19 | add_controller (drop_target);
20 | }
21 |
22 | private void show_popup_right (int n, double x, double y) {
23 | show_popup (x, y);
24 | }
25 |
26 | private bool on_drop_target_drop_change (Value v, double x, double y) {
27 | var note = v.get_object () as Note;
28 | if (note.notebook == notebook)
29 | return false;
30 | window.window_model.move_note (note, notebook);
31 | return true;
32 | }
33 |
34 | private void show_popup (double x, double y) {
35 | if (current_popover != null) {
36 | current_popover.popdown();
37 | }
38 | var popover = new NotebookMenuPopover (window, notebook);
39 | popover.closed.connect (on_popover_close);
40 | popover.autohide = true;
41 | popover.has_arrow = true;
42 | popover.position = Gtk.PositionType.RIGHT;
43 | popover.set_parent (this);
44 | popover.popup ();
45 | current_popover = popover;
46 | }
47 |
48 | private void on_popover_close () {
49 | current_popover.unparent ();
50 | current_popover = null;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebooks_bar.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioNotebooksBar : Box {
5 |
6 | hexpand: false;
7 | vexpand: true;
8 | orientation: vertical;
9 |
10 | styles ["notebooks-bar"]
11 |
12 | Adw.HeaderBar header_bar {
13 |
14 | show-start-title-buttons: bind template.paned;
15 | show-end-title-buttons: false;
16 |
17 | [title]
18 | Box {
19 | $FolioAppMenu {
20 | visible: bind template.paned inverted;
21 | }
22 |
23 | Adw.WindowTitle window_title {
24 | visible: bind template.paned;
25 | }
26 | }
27 |
28 | [end]
29 | $FolioAppMenu {
30 | visible: bind template.paned;
31 | }
32 | }
33 |
34 | ScrolledWindow scrolled_window {
35 |
36 | hexpand: false;
37 | vexpand: true;
38 | hscrollbar-policy: never;
39 | vscrollbar-policy: automatic;
40 |
41 | Box {
42 | orientation: vertical;
43 |
44 | Revealer all_button_revealer {
45 | reveal-child: false;
46 | transition-type: slide_down;
47 |
48 | Box {
49 |
50 | ToggleButton all_button {
51 |
52 | visible: bind template.paned inverted;
53 | hexpand: true;
54 | vexpand: false;
55 |
56 | styles ["flat", "all-button"]
57 |
58 | accessibility {
59 | label: _("All Notes");
60 | }
61 |
62 | Overlay {
63 | hexpand: true;
64 |
65 | [overlay]
66 | Box {
67 | halign: start;
68 | styles ["marker"]
69 | }
70 |
71 | [overlay]
72 | Image {
73 | icon-name: "view-list-symbolic";
74 | halign: center;
75 | }
76 | }
77 | }
78 |
79 | ToggleButton {
80 |
81 | visible: bind template.paned;
82 | hexpand: true;
83 | vexpand: false;
84 | active: bind all_button.active bidirectional;
85 | sensitive: bind all_button.sensitive bidirectional;
86 |
87 | styles ["flat", "all-button"]
88 |
89 | Box {
90 |
91 | hexpand: true;
92 | orientation: horizontal;
93 |
94 | Image {
95 | icon-name: "view-list-symbolic";
96 | }
97 |
98 | Label {
99 | ellipsize: end;
100 | halign: start;
101 | label: _("All Notes");
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | ListView list {
109 | hexpand: false;
110 | vexpand: true;
111 |
112 | accessibility {
113 | label: _("Notebooks");
114 | }
115 | }
116 | }
117 | }
118 |
119 | ToggleButton trash_button {
120 |
121 | visible: bind template.paned inverted;
122 | icon-name: "user-trash-symbolic";
123 | hexpand: false;
124 | vexpand: false;
125 |
126 | styles ["flat", "trash-button"]
127 |
128 | accessibility {
129 | label: _("Trash");
130 | }
131 |
132 | Overlay {
133 | hexpand: true;
134 |
135 | [overlay]
136 | Box {
137 | halign: start;
138 | styles ["marker"]
139 | }
140 |
141 | [overlay]
142 | Image {
143 | icon-name: "user-trash-symbolic";
144 | halign: center;
145 | }
146 | }
147 | }
148 |
149 | ToggleButton {
150 |
151 | visible: bind template.paned;
152 | hexpand: false;
153 | vexpand: false;
154 | active: bind trash_button.active bidirectional;
155 | sensitive: bind trash_button.sensitive bidirectional;
156 |
157 | styles ["flat", "trash-button"]
158 |
159 | Box {
160 |
161 | hexpand: true;
162 | orientation: horizontal;
163 |
164 | Image {
165 | icon-name: "user-trash-symbolic";
166 | }
167 |
168 | Label {
169 | ellipsize: end;
170 | halign: start;
171 | label: _("Trash");
172 | }
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/ui/window/notebooks_bar/notebooks_bar.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/notebooks_bar.ui")]
3 | public class Folio.NotebooksBar : Gtk.Box {
4 |
5 | public bool paned { get; set; default = false; }
6 |
7 | public bool all_button_enabled {
8 | set {
9 | var settings = new Settings (Config.APP_ID);
10 | if (settings.get_boolean ("show-all-notes")) {
11 | all_button_revealer.reveal_child = true;
12 | } else {
13 | all_button_revealer.reveal_child = value;
14 | }
15 | }
16 | }
17 |
18 | [GtkChild] unowned Gtk.ListView list;
19 | [GtkChild] unowned Gtk.ToggleButton all_button;
20 | [GtkChild] unowned Gtk.Revealer all_button_revealer;
21 | [GtkChild] unowned Gtk.ToggleButton trash_button;
22 | [GtkChild] unowned Adw.HeaderBar header_bar;
23 | [GtkChild] unowned Adw.WindowTitle window_title;
24 | [GtkChild] unowned Gtk.ScrolledWindow scrolled_window;
25 |
26 | private Window window;
27 | private WindowModel window_model;
28 |
29 | private Gtk.ListItemFactory item_factory;
30 |
31 | private Gtk.ListItemFactory item_factory_paned;
32 |
33 | construct {
34 | all_button_revealer.notify["child-revealed"].connect (update_scroll);
35 | scrolled_window.vadjustment.notify["value"].connect (update_scroll);
36 | update_scroll ();
37 |
38 | var settings = new Settings (Config.APP_ID);
39 | settings.bind ("enable-3-pane", this, "paned", SettingsBindFlags.DEFAULT);
40 | all_button_revealer.set_reveal_child (settings.get_boolean ("show-all-notes"));
41 |
42 | window_title.title = Strings.APP_NAME;
43 |
44 | notify["paned"].connect (on_paned_change);
45 | }
46 |
47 | private void on_paned_change () {
48 | if (paned) add_css_class ("paned");
49 | else remove_css_class ("paned");
50 | list.factory = paned ? item_factory_paned : item_factory;
51 | }
52 |
53 | private void update_scroll () {
54 | var v = scrolled_window.vadjustment.value;
55 |
56 | if (v == scrolled_window.vadjustment.lower) header_bar.remove_css_class ("overlaid");
57 | else header_bar.add_css_class ("overlaid");
58 |
59 | if (v >= scrolled_window.vadjustment.upper - scrolled_window.get_height ()) trash_button.remove_css_class ("overlaid");
60 | else trash_button.add_css_class ("overlaid");
61 | }
62 |
63 | public void init (Window window) {
64 | this.window = window;
65 | this.window_model = window.window_model;
66 | {
67 | var factory = new Gtk.SignalListItemFactory ();
68 | factory.setup.connect (on_item_factory_setup_change);
69 | factory.bind.connect (on_item_factory_bind_change);
70 | item_factory = factory;
71 | }
72 | {
73 | var factory = new Gtk.SignalListItemFactory ();
74 | factory.setup.connect (on_paned_factory_setup_change);
75 | factory.bind.connect (on_paned_factory_bind_change);
76 | item_factory_paned = factory;
77 | }
78 | list.factory = paned ? item_factory_paned : item_factory;
79 |
80 | trash_button.toggled.connect (on_trash_button_toggled);
81 | all_button.toggled.connect (on_all_button_toggled);
82 |
83 | window_model.notify["notebooks-model"].connect (on_notebooks_updated);
84 | on_notebooks_updated ();
85 | }
86 |
87 |
88 | private void on_item_factory_setup_change (Object object) {
89 | var list_item = object as Gtk.ListItem;
90 | var widget = new NotebookIcon (window);
91 | list_item.child = widget;
92 | }
93 |
94 | private void on_item_factory_bind_change (Object object) {
95 | var list_item = object as Gtk.ListItem;
96 | var widget = list_item.child as NotebookIcon;
97 | var item = list_item.item as Notebook;
98 | widget.notebook = item;
99 | }
100 |
101 | private void on_paned_factory_setup_change (Object object) {
102 | var widget = new NotebookSidebarItem (window);
103 | var li = object as Gtk.ListItem;
104 | if (li != null) {
105 | li.child = widget;
106 | }
107 | }
108 |
109 | private void on_paned_factory_bind_change (Object object) {
110 | var list_item = object as Gtk.ListItem;
111 | var widget = list_item.child as NotebookSidebarItem;
112 | var item = list_item.item as Notebook;
113 | widget.notebook = item;
114 | }
115 |
116 | private void on_trash_button_toggled () {
117 | trash_button.sensitive = !trash_button.active;
118 | if (trash_button.active) {
119 | window_model.select_notebook (null);
120 | window_model.set_trash (window_model.notebook_provider.trash);
121 | }
122 | }
123 |
124 | private void on_all_button_toggled () {
125 | all_button.sensitive = !all_button.active;
126 | if (all_button.active) {
127 | window_model.select_notebook (null);
128 | window_model.set_all ();
129 | }
130 | }
131 |
132 | private void on_notebooks_updated () {
133 | window_model.notebooks_model.selection_changed.connect (on_notebooks_model_selection_changed);
134 | list.model = window_model.notebooks_model;
135 |
136 | if (window_model.notebook_provider.notebooks.size != 0) {
137 | window_model.notebooks_model.selection_changed (0, 1);
138 | }
139 | }
140 |
141 | private void on_notebooks_model_selection_changed () {
142 | var i = window_model.notebooks_model.selected;
143 | var notebooks = window_model.notebook_provider.notebooks;
144 | if (i <= notebooks.size && i != -1) {
145 | trash_button.active = false;
146 | all_button.active = false;
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_card.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNoteCard : Box {
4 |
5 | accessibility {
6 | labelled-by: label;
7 | described-by: descrption_box;
8 | }
9 |
10 | hexpand: true;
11 | orientation: vertical;
12 |
13 | styles ["note-card"]
14 |
15 | Box {
16 | hexpand: true;
17 | orientation: horizontal;
18 |
19 | Label label {
20 | ellipsize: end;
21 | visible: bind entry.visible inverted;
22 | styles ["heading"]
23 | }
24 |
25 | Box {
26 | orientation: horizontal;
27 | hexpand: true;
28 |
29 | styles ["linked"]
30 |
31 | Entry entry {
32 | truncate-multiline: true;
33 | visible: false;
34 | hexpand: true;
35 | styles ["heading"]
36 | }
37 | Button button_apply {
38 | icon-name: "apply-symbolic";
39 | visible: bind entry.visible;
40 | styles ["apply", "suggested-action"]
41 | }
42 | }
43 |
44 | Button button_edit {
45 | halign: end;
46 | icon-name: "document-edit-symbolic";
47 | visible: bind entry.visible inverted;
48 | styles ["edit", "flat"]
49 | }
50 | }
51 |
52 |
53 | Box descrption_box {
54 | hexpand: true;
55 | orientation: horizontal;
56 |
57 | Label subtitle {
58 | ellipsize: end;
59 | hexpand: true;
60 | halign: start;
61 | styles ["caption", "dim-label"]
62 | }
63 |
64 | Label extension {
65 | styles ["caption", "extension"]
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_card.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/sidebar/note_card.ui")]
3 | public class Folio.NoteCard : Gtk.Box {
4 |
5 | public Window window { set { this._window = value; } }
6 |
7 | public Note note {
8 | set {
9 | this._note = value;
10 | if (value != null) {
11 | label.label = value.name;
12 | var time_string = value.time_modified.format ("%-d %b, %-H:%M").strip ();
13 | subtitle.label = _window.window_model.state == WindowModel.State.NOTEBOOK
14 | ? time_string
15 | : @"$(time_string) - $(value.notebook.name)";
16 | extension.label = value.extension;
17 | extension.visible = !value.is_markdown;
18 | tooltip_text = value.name;
19 | var v = Value (typeof (Note));
20 | v.set_object (value);
21 | drag_controller.content = new Gdk.ContentProvider.for_value (v);
22 | extension.update_property (Gtk.AccessibleProperty.LABEL, @"$(Strings.EXTENSION) $(value.extension)", -1);
23 | var accessible_time_string = @"$(Strings.LAST_MODIFIED) " + value.time_modified.format ("%-d %B, %-H:%-M").strip ();
24 | var accessible_description = _window.window_model.state == WindowModel.State.NOTEBOOK
25 | ? accessible_time_string
26 | : @"$(accessible_time_string), $(Strings.NOTEBOOK) $(value.notebook.name)";
27 | subtitle.update_property (Gtk.AccessibleProperty.LABEL, accessible_description, -1);
28 |
29 | var settings = new Settings (Config.APP_ID);
30 | if (settings.get_boolean ("long-note-names") == true) {
31 | label.set_ellipsize (Pango.EllipsizeMode.NONE);
32 | } else {
33 | label.set_ellipsize (Pango.EllipsizeMode.END);
34 | }
35 | }
36 | }
37 | }
38 |
39 | [GtkChild] unowned Gtk.Label label;
40 | [GtkChild] unowned Gtk.Entry entry;
41 | [GtkChild] unowned Gtk.Button button_edit;
42 | [GtkChild] unowned Gtk.Button button_apply;
43 | [GtkChild] unowned Gtk.Label subtitle;
44 | [GtkChild] unowned Gtk.Label extension;
45 |
46 | private Gtk.DragSource drag_controller;
47 |
48 | private Window _window;
49 | private Note _note;
50 | private Gtk.Popover? current_popover = null;
51 | private bool clickthrough = false;
52 |
53 | private void show_popup_right ( int n, double x, double y) {
54 | show_popup (x, y);
55 | }
56 |
57 | construct {
58 | var long_press = new Gtk.GestureLongPress ();
59 | long_press.pressed.connect (show_popup);
60 | add_controller (long_press);
61 | var right_click = new Gtk.GestureClick ();
62 | right_click.button = Gdk.BUTTON_SECONDARY;
63 | right_click.pressed.connect (show_popup_right);
64 | add_controller (right_click);
65 | drag_controller = new Gtk.DragSource ();
66 | drag_controller.actions = Gdk.DragAction.MOVE;
67 | drag_controller.drag_begin.connect (on_drag_being);
68 | drag_controller.drag_end.connect (on_drag_end);
69 | add_controller (drag_controller);
70 |
71 | var double_click = new Gtk.GestureClick ();
72 | double_click.button = Gdk.BUTTON_PRIMARY;
73 | double_click.pressed.connect (check_double_click);
74 | double_click.released.connect (note_navigate);
75 | add_controller (double_click);
76 |
77 | button_edit.clicked.connect (request_rename);
78 | var controller = new Gtk.EventControllerKey ();
79 | controller.key_pressed.connect (on_controller_key_pressed);
80 | add_controller (controller);
81 | }
82 |
83 | private bool on_controller_key_pressed (uint keyval) {
84 | if (keyval == Gdk.Key.Escape) {
85 | maybe_exit_rename ();
86 | return true;
87 | }
88 | return false;
89 | }
90 |
91 | private void on_drag_being () {
92 | add_css_class ("dragged");
93 | queue_draw ();
94 | var paintable = new Gtk.WidgetPaintable (this);
95 | drag_controller.set_icon (paintable, 0, 0);
96 | }
97 |
98 | private void on_drag_end () {
99 | remove_css_class ("dragged");
100 | }
101 |
102 | public void check_double_click (int n_press, double x, double y) {
103 | clickthrough = true;
104 | if (n_press == 2) {
105 | // We can't just call the rename request here as after this code runs the default
106 | // handler will run and steal the focus away again. So instead, just add a slight
107 | // delay to get the default handler to run before calling the rename request.
108 | GLib.Timeout.add_once (100, on_check_double_click_change);
109 | }
110 | }
111 |
112 | private void on_check_double_click_change () {
113 | clickthrough = false;
114 | request_rename ();
115 | }
116 |
117 | public void note_navigate (int n_press, double x, double y) {
118 | print (n_press.to_string ());
119 | if (n_press == 1 && clickthrough) {
120 | GLib.Timeout.add_once (175, on_note_navigate_delay);
121 | }
122 | }
123 |
124 | private void on_note_navigate_delay () {
125 | if (clickthrough) {
126 | _window.navigate_to_edit_view ();
127 | }
128 | }
129 |
130 | public void request_rename () {
131 | entry.buffer.set_text (_note.file_name.data);
132 | entry.visible = true;
133 | entry.grab_focus_without_selecting ();
134 | _window.notify["focus-widget"].connect (maybe_exit_rename);
135 | button_apply.clicked.connect (rename);
136 | entry.activate.connect (rename);
137 | }
138 |
139 | private void maybe_exit_rename () {
140 | var focused = _window.focus_widget;
141 | if (focused == null
142 | || focused == label
143 | || focused == entry
144 | || focused == button_edit
145 | || focused == button_apply
146 | || focused == this
147 | || focused == parent
148 | ) return;
149 | exit_rename ();
150 | }
151 |
152 | private void exit_rename () {
153 | entry.visible = false;
154 | _window.notify["focus-widget"].disconnect (maybe_exit_rename);
155 | }
156 |
157 | private void rename () {
158 | exit_rename ();
159 | if (_window.try_rename_note (_note, entry.buffer.text))
160 | note = _note;
161 | }
162 |
163 | private void show_popup (double x, double y) {
164 | clickthrough = false;
165 | if (current_popover != null) {
166 | current_popover.popdown();
167 | }
168 | var popover = new NoteMenuPopover (
169 | _window,
170 | _note,
171 | _window.window_model.state == WindowModel.State.TRASH,
172 | request_rename
173 | );
174 | popover.closed.connect (on_popover_closed);
175 | popover.autohide = true;
176 | popover.has_arrow = true;
177 | popover.position = Gtk.PositionType.BOTTOM;
178 | popover.set_parent (label);
179 | popover.popup ();
180 | current_popover = popover;
181 | }
182 |
183 | private void on_popover_closed () {
184 | current_popover.unparent ();
185 | current_popover = null;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_create_popup.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 | using Adw 1;
3 |
4 | template $FolioNoteCreatePopup : Adw.Dialog {
5 | presentation-mode: floating;
6 | content-width: 320;
7 |
8 | child: WindowHandle {
9 | Box {
10 |
11 | spacing: 16;
12 | orientation: vertical;
13 | hexpand: true;
14 | margin-bottom: 16;
15 |
16 | [overlay]
17 | Adw.HeaderBar {
18 |
19 | hexpand: true;
20 | vexpand: false;
21 | valign: start;
22 | }
23 |
24 | Entry entry {
25 | placeholder-text: _("Note Name");
26 | hexpand: true;
27 | margin-start: 18;
28 | margin-end: 18;
29 | }
30 |
31 | Button button_create {
32 |
33 | label: _("Create Note");
34 | margin-start: 16;
35 | margin-end: 16;
36 |
37 | styles ["suggested-action"]
38 | }
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_create_popup.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/sidebar/create_popup.ui")]
3 | public class Folio.NoteCreatePopup : Adw.Dialog {
4 |
5 | [GtkChild]
6 | unowned Gtk.Entry entry;
7 |
8 | [GtkChild]
9 | unowned Gtk.Button button_create;
10 |
11 | private Window window;
12 | private Note? note;
13 |
14 | public NoteCreatePopup (Window window, Note? note = null) {
15 | Object ();
16 |
17 | this.window = window;
18 | this.note = note;
19 |
20 | if (note != null) {
21 | button_create.label = Strings.RENAME;
22 | entry.text = note.name;
23 | entry.activate.connect (change);
24 | button_create.clicked.connect (change);
25 | } else {
26 | entry.activate.connect (create);
27 | button_create.clicked.connect (create);
28 | }
29 | }
30 |
31 | private void create () {
32 | var name = entry.text;
33 | close ();
34 | window.try_create_note (name);
35 | }
36 |
37 | private void change () {
38 | var file_name = entry.text;
39 | close ();
40 | window.try_rename_note (note, file_name);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_menu.blp:
--------------------------------------------------------------------------------
1 | using Gtk 4.0;
2 |
3 | template $FolioNoteMenuPopover : Popover {
4 |
5 | styles ["custom-popover-menu"]
6 |
7 | Box {
8 |
9 | orientation: vertical;
10 |
11 | Button button_recover {
12 |
13 | styles ["flat", "modelbutton"]
14 |
15 | Label {
16 | label: _("Restore from Trash");
17 | halign: start;
18 | }
19 | }
20 |
21 | Button button_delete {
22 |
23 | styles ["flat", "modelbutton"]
24 |
25 | Label {
26 | label: _("Delete from Trash");
27 | halign: start;
28 | }
29 | }
30 |
31 | Button button_edit {
32 |
33 | styles ["flat", "modelbutton"]
34 |
35 | Label {
36 | label: _("Rename");
37 | halign: start;
38 | }
39 | }
40 |
41 | Button button_move {
42 |
43 | styles ["flat", "modelbutton"]
44 |
45 | Label {
46 | label: _("Move to Notebook…");
47 | halign: start;
48 | }
49 | }
50 |
51 | Button button_trash {
52 |
53 | styles ["flat", "modelbutton"]
54 |
55 | Label {
56 | label: _("Move to Trash");
57 | halign: start;
58 | }
59 | }
60 |
61 | Button button_open_containing_dir {
62 |
63 | styles ["flat", "modelbutton"]
64 |
65 | Label {
66 | label: _("Open Containing Folder");
67 | halign: start;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ui/window/sidebar/note_menu.vala:
--------------------------------------------------------------------------------
1 |
2 | [GtkTemplate (ui = "/com/toolstack/Folio/sidebar/note_menu.ui")]
3 | public class Folio.NoteMenuPopover : Gtk.Popover {
4 |
5 | [GtkChild]
6 | unowned Gtk.Button button_edit;
7 |
8 | [GtkChild]
9 | unowned Gtk.Button button_move;
10 |
11 | [GtkChild]
12 | unowned Gtk.Button button_recover;
13 |
14 | [GtkChild]
15 | unowned Gtk.Button button_trash;
16 |
17 | [GtkChild]
18 | unowned Gtk.Button button_delete;
19 |
20 | [GtkChild]
21 | unowned Gtk.Button button_open_containing_dir;
22 |
23 | private Window window;
24 | private Note note;
25 |
26 | public NoteMenuPopover (
27 | Window window,
28 | Note note,
29 | bool is_in_trash,
30 | Runnable rename
31 | ) {
32 | this.window = window;
33 | this.note = note;
34 |
35 | if (is_in_trash) {
36 | button_edit.visible = false;
37 | button_trash.visible = false;
38 | button_move.visible = false;
39 | button_recover.clicked.connect (on_button_recover_clicked);
40 | button_delete.clicked.connect (on_button_delete_clicked);
41 | } else {
42 | button_recover.visible = false;
43 | button_delete.visible = false;
44 | button_move.clicked.connect (on_button_move_clicked);
45 | // We have to leave this lambda as Vala doesn't support copying the delegate rename function.
46 | button_edit.clicked.connect (() => {
47 | popdown ();
48 | rename ();
49 | });
50 | button_trash.clicked.connect (on_button_trash_clicked);
51 | }
52 |
53 | button_open_containing_dir.clicked.connect (on_button_open_containing_dir_clicked);
54 | }
55 |
56 | private void on_button_recover_clicked () {
57 | popdown ();
58 | window.try_restore_note (note);
59 | }
60 |
61 | private void on_button_delete_clicked () {
62 | popdown ();
63 | window.request_delete_note (note, true);
64 | }
65 |
66 | private void on_button_move_clicked () {
67 | popdown ();
68 | window.request_move_note (note);
69 | }
70 |
71 | private void on_button_trash_clicked () {
72 | popdown ();
73 | window.try_delete_note (note);
74 | }
75 |
76 | private void on_button_open_containing_dir_clicked () {
77 | popdown ();
78 | var uri = File.new_for_path (note.notebook.path).get_uri ();
79 | try {
80 | AppInfo.launch_default_for_uri (uri, null);
81 | } catch (Error e) {
82 | window.toast (Strings.COULDNT_FIND_APP_TO_HANDLE_URIS);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/subprojects/blueprint-compiler.wrap:
--------------------------------------------------------------------------------
1 | [wrap-git]
2 | directory = blueprint-compiler
3 | url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
4 | revision = v0.10.0
5 | depth = 1
6 |
7 | [provide]
8 | program_names = blueprint-compiler
9 |
--------------------------------------------------------------------------------