Merge refactor branch (#173)
|
@ -2,6 +2,5 @@ _ignore
|
||||||
build
|
build
|
||||||
build.sh
|
build.sh
|
||||||
build-po.sh
|
build-po.sh
|
||||||
install.sh
|
|
||||||
uninstall.sh
|
uninstall.sh
|
||||||
*~
|
*~
|
||||||
|
|
32
README.md
|
@ -1,14 +1,17 @@
|
||||||
![Tootle](https://user-images.githubusercontent.com/37731582/39933812-45d8149a-5544-11e8-9bf4-6d78b1fdb29c.png)
|
![Tootle](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/icons/color.svg)
|
||||||
Simple [Mastodon](https://github.com/tootsuite/mastodon) client designed for elementary OS.
|
Simple [Mastodon](https://github.com/tootsuite/mastodon) client for Linux
|
||||||
|
|
||||||
![Tootle Screenshot](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png)
|
![Screenshot](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png)
|
||||||
|
|
||||||
## Building and Installation
|
## Installation
|
||||||
|
This project is undergoing a major rewrite and will be published in the near future.
|
||||||
|
|
||||||
|
To help the project, please build it manually and help test it.
|
||||||
|
|
||||||
[![Get it on AppCenter](https://appcenter.elementary.io/badge.svg)](https://appcenter.elementary.io/com.github.bleakgrey.tootle)
|
|
||||||
<a href='https://flathub.org/apps/details/com.github.bleakgrey.tootle'><img height='51' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
|
<a href='https://flathub.org/apps/details/com.github.bleakgrey.tootle'><img height='51' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
|
||||||
|
|
||||||
First of all you'll need some dependencies to build and run the app:
|
## Building
|
||||||
|
To build the app, make sure you have these dependencies:
|
||||||
* meson
|
* meson
|
||||||
* valac
|
* valac
|
||||||
* libgtk-3-dev
|
* libgtk-3-dev
|
||||||
|
@ -16,26 +19,19 @@ First of all you'll need some dependencies to build and run the app:
|
||||||
* libgranite-dev
|
* libgranite-dev
|
||||||
* libjson-glib-dev
|
* libjson-glib-dev
|
||||||
|
|
||||||
Then run these commands to build and install it:
|
Then run 'install.sh' in the project directory to install the app.
|
||||||
|
|
||||||
meson build --prefix=/usr
|
|
||||||
cd build
|
|
||||||
sudo ninja install
|
|
||||||
com.github.bleakgrey.tootle
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you feel like contributing, you're always welcome to help the project in many ways:
|
You're always welcome to help the project in many ways:
|
||||||
* Reporting any issues
|
* Donating with [LiberaPay](https://liberapay.com/bleakgrey/) to keep the developer happy and motivated
|
||||||
* Suggesting ideas and functionality
|
* Reporting issues and bugs
|
||||||
* Submitting pull requests
|
* Submitting pull requests
|
||||||
* Donating with [LiberaPay](https://liberapay.com/bleakgrey/) to help project development and keeping the developer happy
|
|
||||||
|
|
||||||
<a href="https://liberapay.com/bleakgrey/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
<a href="https://liberapay.com/bleakgrey/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
* Tootle Logo by [@CallMeFib3r](https://github.com/CallMeFib3r)
|
* Icon design by [Tobias Bernard](https://github.com/bertob)
|
||||||
* Medel typeface by Ozan Karakoc
|
|
||||||
* French translation by [@Larnicone](https://github.com/Larnicone)
|
* French translation by [@Larnicone](https://github.com/Larnicone)
|
||||||
* Polish translation by [@m4sk1n](https://github.com/m4sk1n)
|
* Polish translation by [@m4sk1n](https://github.com/m4sk1n)
|
||||||
* German translation by [@koyuawsmbrtn](https://github.com/koyuawsmbrtn)
|
* German translation by [@koyuawsmbrtn](https://github.com/koyuawsmbrtn)
|
||||||
|
|
37
data/app.css
|
@ -1,35 +1,12 @@
|
||||||
.titlebar.compact {
|
.avatar {
|
||||||
padding: 0 6px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode .toggle{
|
.attachment {
|
||||||
border-radius:0px;
|
border-radius: 4px;
|
||||||
border-top:none;
|
background: rgba (150, 150, 150, 0.2);
|
||||||
border-bottom:none;
|
|
||||||
padding:10px;
|
|
||||||
margin:0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_avatar{
|
.highlight {
|
||||||
padding:0;
|
background: @theme_base_color;
|
||||||
border:0;
|
|
||||||
box-shadow:none;
|
|
||||||
background:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toot-text, .toot-text text{
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header{
|
|
||||||
background-size: cover;
|
|
||||||
background-position: 50%;
|
|
||||||
opacity: 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relationship {
|
|
||||||
background: rgba (0,0,0,.5);
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<project_license>GPL-3.0+</project_license>
|
<project_license>GPL-3.0+</project_license>
|
||||||
<name>Tootle</name>
|
<name>Tootle</name>
|
||||||
<summary>Lightning fast client for Mastodon</summary>
|
<summary>Lightning fast client for Mastodon</summary>
|
||||||
|
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
Tootle is a client for the world’s largest free, open-source, decentralized microblogging network with real-time notifications and support for multiple accounts.
|
Tootle is a client for the world’s largest free, open-source, decentralized microblogging network with real-time notifications and support for multiple accounts.
|
||||||
|
@ -18,16 +18,16 @@
|
||||||
Anyone can run a Mastodon server. Each server hosts individual user accounts, the content they produce, and the content to which they are subscribed. Every user can follow each other and share their posts regardless of their server.
|
Anyone can run a Mastodon server. Each server hosts individual user accounts, the content they produce, and the content to which they are subscribed. Every user can follow each other and share their posts regardless of their server.
|
||||||
</p>
|
</p>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<provides>
|
<provides>
|
||||||
<binary>com.github.bleakgrey.tootle</binary>
|
<binary>com.github.bleakgrey.tootle</binary>
|
||||||
</provides>
|
</provides>
|
||||||
|
|
||||||
<developer_name>bleak_grey</developer_name>
|
<developer_name>bleak_grey</developer_name>
|
||||||
<url type="homepage">https://github.com/bleakgrey</url>
|
<url type="homepage">https://github.com/bleakgrey</url>
|
||||||
<url type="bugtracker">https://github.com/bleakgrey/tootle/issues</url>
|
<url type="bugtracker">https://github.com/bleakgrey/tootle/issues</url>
|
||||||
<url type="donation">https://liberapay.com/bleakgrey/donate</url>
|
<url type="donation">https://liberapay.com/bleakgrey/donate</url>
|
||||||
|
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1">
|
||||||
<content_attribute id="violence-cartoon">none</content_attribute>
|
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||||
<content_attribute id="violence-fantasy">none</content_attribute>
|
<content_attribute id="violence-fantasy">none</content_attribute>
|
||||||
|
@ -57,20 +57,11 @@
|
||||||
<content_attribute id="money-purchasing">none</content_attribute>
|
<content_attribute id="money-purchasing">none</content_attribute>
|
||||||
<content_attribute id="money-gambling">none</content_attribute>
|
<content_attribute id="money-gambling">none</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
|
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png</image>
|
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
|
||||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot2.png</image>
|
|
||||||
</screenshot>
|
|
||||||
<screenshot>
|
|
||||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot3.png</image>
|
|
||||||
</screenshot>
|
|
||||||
<screenshot>
|
|
||||||
<image>https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot4.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
@ -111,7 +102,7 @@
|
||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
|
|
||||||
<custom>
|
<custom>
|
||||||
<value key="x-appcenter-color-primary">#F5F8FF</value>
|
<value key="x-appcenter-color-primary">#F5F8FF</value>
|
||||||
<value key="x-appcenter-color-primary-text">#413F58</value>
|
<value key="x-appcenter-color-primary-text">#413F58</value>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<gresources>
|
|
||||||
<gresource prefix="/com/github/bleakgrey/tootle/">
|
|
||||||
<file alias="app.css">app.css</file>
|
|
||||||
<file alias="light.css">light.css</file>
|
|
||||||
<file alias="dark.css">dark.css</file>
|
|
||||||
<file alias="logo128">logo128.png</file>
|
|
||||||
<file alias="empty_state">empty_state.png</file>
|
|
||||||
</gresource>
|
|
||||||
</gresources>
|
|
|
@ -16,16 +16,6 @@
|
||||||
<summary>Always monitor new notifications</summary>
|
<summary>Always monitor new notifications</summary>
|
||||||
<description></description>
|
<description></description>
|
||||||
</key>
|
</key>
|
||||||
<key name="cache" type="b">
|
|
||||||
<default>false</default>
|
|
||||||
<summary>Cache images to reduce network load</summary>
|
|
||||||
<description></description>
|
|
||||||
</key>
|
|
||||||
<key name="cache-size" type="i">
|
|
||||||
<default>64</default>
|
|
||||||
<summary>Cache size</summary>
|
|
||||||
<description>Sets the maximum size of cached content</description>
|
|
||||||
</key>
|
|
||||||
<key name="live-updates" type="b">
|
<key name="live-updates" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Real-time timelines</summary>
|
<summary>Real-time timelines</summary>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
@define-color colorAccent #c92e34;
|
|
||||||
@define-color colorPrimary #35393c;
|
|
||||||
|
|
||||||
.header-counters{
|
|
||||||
background: rgba(0,0,0,.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment{
|
|
||||||
background: rgba (255,255,255,.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card{
|
|
||||||
background: rgba (255,255,255,.15);
|
|
||||||
}
|
|
Before Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/com/github/bleakgrey/tootle/">
|
||||||
|
<file alias="app.css">app.css</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/views/new_account.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/views/base.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/views/profile_header.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/widgets/status.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/widgets/accounts_button.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/widgets/accounts_button_item.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/dialogs/compose.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/dialogs/main.ui</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
|
@ -1,188 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4379"
|
|
||||||
sodipodi:docname="128x128.svg"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="128px"
|
|
||||||
height="128px"
|
|
||||||
viewBox="0 0 128 128"
|
|
||||||
enable-background="new 0 0 128 128"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata59"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs57" /><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
id="namedview55"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="1.84375"
|
|
||||||
inkscape:cx="54.508475"
|
|
||||||
inkscape:cy="64"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg4379" /><radialGradient
|
|
||||||
id="path3041_1_"
|
|
||||||
cx="-292.7208"
|
|
||||||
cy="1871.6008"
|
|
||||||
r="35.3381"
|
|
||||||
gradientTransform="matrix(1.5564 0 0 -0.1698 519.5884 435.7747)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#000000"
|
|
||||||
id="stop4" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#000000;stop-opacity:0"
|
|
||||||
id="stop6" /></radialGradient><path
|
|
||||||
id="path3041"
|
|
||||||
opacity="0.2"
|
|
||||||
fill="url(#path3041_1_)"
|
|
||||||
enable-background="new "
|
|
||||||
d="M119,118 c-0.006,3.314-24.635,5.999-55.01,5.999C33.622,123.998,9.006,121.313,9,118c-0.006-3.314,24.614-6.001,54.99-6.001 c30.376-0.001,55.005,2.685,55.01,5.999C119,117.999,119,117.999,119,118z" /><g
|
|
||||||
id="g2036"
|
|
||||||
transform="matrix(2.6999989,0,0,0.55555607,-0.8000019,94.888882)"><g
|
|
||||||
id="g3712"
|
|
||||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
|
||||||
opacity="0.4"><radialGradient
|
|
||||||
id="rect2801_1_"
|
|
||||||
cx="-136.6434"
|
|
||||||
cy="5007.957"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(5.695 0 0 -1 816.1725 5051.457)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop12" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop14" /></radialGradient><rect
|
|
||||||
id="rect2801"
|
|
||||||
x="38"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect2801_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><radialGradient
|
|
||||||
id="rect3696_1_"
|
|
||||||
cx="-165.4541"
|
|
||||||
cy="6006.0449"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(-5.695 0 0 1 -932.2481 -5962.5449)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop18" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop20" /></radialGradient><rect
|
|
||||||
id="rect3696"
|
|
||||||
x="5"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3696_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><linearGradient
|
|
||||||
id="rect3700_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-115.496"
|
|
||||||
y1="4803.4004"
|
|
||||||
x2="-115.496"
|
|
||||||
y2="4810.4287"
|
|
||||||
gradientTransform="matrix(2.8421 0 0 -0.7143 352.2516 3478.0276)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop24" /><stop
|
|
||||||
offset="0.5"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop26" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop28" /></linearGradient><rect
|
|
||||||
id="rect3700"
|
|
||||||
x="10"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3700_1_)"
|
|
||||||
width="28"
|
|
||||||
height="7" /></g></g><radialGradient
|
|
||||||
id="SVGID_1_"
|
|
||||||
cx="-42.2709"
|
|
||||||
cy="69.1949"
|
|
||||||
r="322.7202"
|
|
||||||
fx="-55.7745"
|
|
||||||
fy="69.1949"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop32" /><stop
|
|
||||||
offset="0.4576"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop34" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop36" /></radialGradient><path
|
|
||||||
fill="url(#SVGID_1_)"
|
|
||||||
d="M115.5,21.55v90.9c0,3.34-2.71,6.05-6.05,6.05h-90.9c-3.34,0-6.05-2.71-6.05-6.05v-90.9 c0-3.34,2.71-6.05,6.05-6.05h90.9C112.79,15.5,115.5,18.21,115.5,21.55z"
|
|
||||||
id="path38" /><linearGradient
|
|
||||||
id="rect6741-7_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-329.085"
|
|
||||||
y1="3929.1445"
|
|
||||||
x2="-329.085"
|
|
||||||
y2="3891.1445"
|
|
||||||
gradientTransform="matrix(2.7297 0 0 -2.7297 962.313 10740.0137)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop41" /><stop
|
|
||||||
offset="0.0632"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop43" /><stop
|
|
||||||
offset="0.9506"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop45" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop47" /></linearGradient><path
|
|
||||||
id="rect6741-7"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741-7_1_)"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M18.5,15.875h91c2.761,0,5,2.239,5,5v91c0,2.761-2.239,5-5,5h-91c-2.761,0-5-2.239-5-5v-91C13.5,18.114,15.739,15.875,18.5,15.875z" /><path
|
|
||||||
id="rect5505-21-6"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M18.5,15.5h91c3.314,0,6,2.686,6,6v91c0,3.314-2.686,6-6,6h-91c-3.314,0-6-2.686-6-6v-91C12.5,18.186,15.186,15.5,18.5,15.5z" /><path
|
|
||||||
d="M115.5,50.65v61.8c0,3.34-2.71,6.05-6.05,6.05H51.31L24.13,92.02l-0.08-0.08v-0.01 c-0.02-0.02-0.03-0.04-0.05-0.05l-5.5-10.54c-0.12-0.31,0.01-0.66,0.31-0.82c0.14-0.07,0.29-0.09,0.42-0.06c0.02,0,0.03,0,0.04,0.01 l5.02,1.09l1.18-0.62l0.89-0.46l12.63-6.6c0.08-0.04,0.17-0.09,0.25-0.13l6.18-3.22l-1-1.91c-0.41-0.79-0.29-1.72,0.23-2.38 l-3.46-3.34c-0.1-0.08-0.19-0.17-0.28-0.27c-0.17-0.19-0.31-0.39-0.43-0.62c-0.77-1.47-0.2-3.27,1.27-4.04l16.81-8.77 c1.11-0.58,2.42-0.39,3.32,0.38l0.26,0.25l8.04,7.77l4.36-2.27c0.08-0.05,0.17-0.09,0.25-0.14l12.83-6.7l6.47-15.51 c0.01-0.03,0.02-0.06,0.04-0.08c0.12-0.28,0.34-0.52,0.63-0.68c0.5-0.26,1.08-0.2,1.51,0.11L115.5,50.65z"
|
|
||||||
id="path51"
|
|
||||||
fill="#273445"
|
|
||||||
opacity="0.5"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M108.571,55.606l-11.964-22.92c-0.018-0.027-0.034-0.051-0.055-0.075c-0.082-0.106-0.178-0.199-0.284-0.275 c-0.426-0.311-1.007-0.375-1.507-0.114c-0.293,0.155-0.511,0.396-0.636,0.678c-0.018,0.025-0.026,0.054-0.037,0.083l-6.467,15.511 l-12.831,6.699c-0.084,0.044-0.171,0.088-0.251,0.134l-4.363,2.278l-3.801,1.984l-0.994-1.905c-0.564-1.079-1.89-1.496-2.969-0.932 c-1.077,0.562-1.496,1.89-0.932,2.969l0.994,1.905l-3.672,1.917l-3.172-6.075l5.693-2.971c1.461-0.765,2.029-2.571,1.266-4.032 c-0.123-0.238-0.274-0.45-0.449-0.638l-0.259-0.251c-0.899-0.769-2.213-0.958-3.323-0.378l-16.812,8.775 c-1.463,0.765-2.031,2.571-1.268,4.034c0.12,0.229,0.265,0.437,0.433,0.62c0.087,0.099,0.182,0.192,0.282,0.273 c0.899,0.764,2.208,0.951,3.318,0.371l5.691-2.969l3.17,6.075l-4.054,2.116l-0.994-1.905c-0.564-1.077-1.894-1.494-2.969-0.932 c-0.284,0.148-0.521,0.349-0.709,0.585c-0.52,0.66-0.639,1.589-0.225,2.384l0.994,1.905l-6.172,3.223 c-0.084,0.042-0.169,0.086-0.253,0.13l-14.7,7.674l-5.016-1.089c-0.014-0.004-0.027-0.006-0.038-0.008 c-0.139-0.027-0.289-0.006-0.424,0.063c-0.3,0.157-0.428,0.511-0.309,0.818l5.501,10.535c0.014,0.02,0.028,0.04,0.043,0.058 l0.007,0.009l0.08,0.075c0.195,0.162,0.476,0.198,0.718,0.073c0.135-0.072,0.234-0.181,0.293-0.312 c0.007-0.011,0.011-0.023,0.015-0.036l2.014-4.828l5.884-3.073c0.023,1.672,0.427,3.363,1.253,4.946 c2.823,5.406,9.497,7.502,14.906,4.679c0.084-0.044,0.169-0.088,0.251-0.134l6.172-3.223l1.154,2.211 c0.562,1.077,1.894,1.494,2.969,0.932c1.077-0.562,1.496-1.892,0.934-2.969l-1.154-2.209l4.054-2.118l3.367,6.448 c0.784,1.501,2.632,2.082,4.133,1.298c0.42-0.217,0.768-0.521,1.029-0.875c0.688-0.914,0.831-2.177,0.267-3.258l-3.364-6.448 l3.672-1.917l1.154,2.209c0.562,1.077,1.892,1.496,2.969,0.934c1.079-0.564,1.494-1.894,0.932-2.969l-1.154-2.211l8.517-4.446 c0.588-0.314,1.135-0.675,1.642-1.075c4.085-3.225,5.435-8.995,2.94-13.776c-0.825-1.581-1.982-2.879-3.34-3.855l4.014-2.095 l16.518,3.58c0.031,0.008,0.059,0.017,0.088,0.019c0.305,0.058,0.627,0.019,0.92-0.136C108.55,57.044,108.829,56.275,108.571,55.606 z M46.51,88.011c-0.038,0.023-0.073,0.042-0.113,0.062c-2.431,1.269-5.427,0.329-6.697-2.102c-1.267-2.429-0.329-5.425,2.098-6.694 l6.432-3.357l4.592,8.797L46.51,88.011z M56.724,82.679l-4.592-8.797l0.269-0.139l3.786-1.977l4.592,8.797l-0.864,0.451 L56.724,82.679z M66.209,77.729l-4.592-8.797l3.672-1.917l4.592,8.797L66.209,77.729z M81.444,69.766 c-0.053,0.034-0.109,0.067-0.164,0.095l-7.498,3.915l-4.592-8.797l5.591-2.92l1.792-0.934c0.095-0.049,0.191-0.095,0.289-0.139 c2.374-1.056,5.189-0.094,6.408,2.241c1.075,2.056,0.567,4.516-1.086,5.995C81.958,69.421,81.712,69.605,81.444,69.766z"
|
|
||||||
id="path53" /></svg>
|
|
Before Width: | Height: | Size: 9.5 KiB |
|
@ -1,125 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4372"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="16x16.svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="16px"
|
|
||||||
height="16px"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
enable-background="new 0 0 16 16"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata34"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs32" /><sodipodi:namedview
|
|
||||||
inkscape:zoom="32"
|
|
||||||
objecttolerance="10"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
showgrid="true"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
inkscape:cx="5.6537797"
|
|
||||||
inkscape:cy="7.7070694"
|
|
||||||
id="namedview23"
|
|
||||||
inkscape:current-layer="svg4372"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:pageshadow="2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
empspacing="4"
|
|
||||||
id="grid4151" /><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
color="#ff00ff"
|
|
||||||
opacity="0.2627451"
|
|
||||||
spacingy="0.5"
|
|
||||||
empopacity="0"
|
|
||||||
empcolor="#ff3fff"
|
|
||||||
empspacing="4"
|
|
||||||
spacingx="0.5"
|
|
||||||
id="grid4153" /></sodipodi:namedview><radialGradient
|
|
||||||
id="SVGID_1_"
|
|
||||||
cx="-5.4128"
|
|
||||||
cy="8.277"
|
|
||||||
r="40.7317"
|
|
||||||
fx="-7.1172"
|
|
||||||
fy="8.277"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#95A3AB"
|
|
||||||
id="stop7" /><stop
|
|
||||||
offset="0.2624"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop9" /><stop
|
|
||||||
offset="0.705"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop11" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop13" /></radialGradient><path
|
|
||||||
fill="url(#SVGID_1_)"
|
|
||||||
d="M14.5,2.5v11c0,0.52-0.4,0.94-0.9,0.99c-0.03,0.01-0.07,0.01-0.1,0.01h-11c-0.55,0-1-0.45-1-1v-11 c0-0.55,0.45-1,1-1h11C14.05,1.5,14.5,1.95,14.5,2.5z"
|
|
||||||
id="path15" /><linearGradient
|
|
||||||
id="rect6741-0-3-5_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="502.1291"
|
|
||||||
y1="2616.1348"
|
|
||||||
x2="502.1291"
|
|
||||||
y2="2578.1348"
|
|
||||||
gradientTransform="matrix(0.2973 0 0 -0.2973 -141.2816 780.1212)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop18" /><stop
|
|
||||||
offset="0.0632"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop20" /><stop
|
|
||||||
offset="0.9506"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop22" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop24" /></linearGradient><rect
|
|
||||||
id="rect6741-0-3-5"
|
|
||||||
x="2.5"
|
|
||||||
y="2.5"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741-0-3-5_1_)"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
width="11"
|
|
||||||
height="11" /><path
|
|
||||||
id="rect5505-21-2-8"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M2.5,1.5h11c0.552,0,1,0.448,1,1v11c0,0.552-0.448,1-1,1h-11c-0.552,0-1-0.448-1-1v-11C1.5,1.948,1.948,1.5,2.5,1.5z" /><path
|
|
||||||
opacity="0.5"
|
|
||||||
fill="#273445"
|
|
||||||
d="M14.5,6.56v6.94c0,0.52-0.4,0.94-0.9,0.99H6.77l-3.23-3.14v-0.01c-0.01,0-0.01,0-0.01,0 l-0.3-0.58l-0.35-0.68c-0.01-0.03,0-0.07,0.04-0.09c0.02-0.01,0.03-0.01,0.05-0.01l0.26,0.06l0.33,0.07l0.15-0.07l0.11-0.06 l1.49-0.77c0.01-0.02,0.02-0.02,0.03-0.02l0.73-0.38L5.95,8.58C5.9,8.48,5.92,8.37,5.98,8.29L5.57,7.9L5.54,7.87 C5.52,7.85,5.5,7.82,5.48,7.79C5.4,7.62,5.46,7.41,5.63,7.32l2-1.04c0.13-0.07,0.29-0.05,0.4,0.05l0.02,0.02l0.96,0.92l0.51-0.26 C9.53,7,9.54,7,9.55,6.99l1.52-0.8l0.77-1.84c0.01-0.04,0.05-0.06,0.07-0.09c0.06-0.03,0.14-0.02,0.19,0.02L14.5,6.56z"
|
|
||||||
id="path28"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M13.555,7.038l-1.418-2.716c-0.002-0.003-0.004-0.006-0.007-0.009c-0.01-0.013-0.021-0.024-0.034-0.033 c-0.05-0.037-0.119-0.044-0.179-0.014c-0.035,0.018-0.061,0.047-0.075,0.08c-0.002,0.003-0.003,0.006-0.004,0.01l-0.766,1.838 L9.551,6.989c-0.01,0.005-0.02,0.01-0.03,0.016l-0.517,0.27L8.554,7.51L8.436,7.284c-0.067-0.128-0.224-0.177-0.352-0.11 C7.957,7.24,7.907,7.397,7.974,7.525l0.118,0.226L7.656,7.978l-0.376-0.72l0.675-0.352c0.173-0.091,0.24-0.305,0.15-0.478 C8.091,6.4,8.073,6.375,8.052,6.353l-0.031-0.03C7.915,6.232,7.759,6.21,7.627,6.278l-1.992,1.04 c-0.173,0.091-0.241,0.305-0.15,0.478C5.499,7.823,5.516,7.848,5.536,7.87c0.01,0.012,0.022,0.023,0.033,0.032 c0.107,0.091,0.262,0.113,0.393,0.044l0.674-0.352l0.376,0.72l-0.48,0.251L6.415,8.339C6.348,8.212,6.19,8.162,6.063,8.229 C6.029,8.246,6.001,8.27,5.979,8.298C5.917,8.376,5.903,8.486,5.952,8.581L6.07,8.806L5.338,9.188c-0.01,0.005-0.02,0.01-0.03,0.015 l-1.742,0.909L2.972,9.984C2.97,9.984,2.968,9.983,2.967,9.983C2.951,9.98,2.933,9.982,2.917,9.991 c-0.036,0.019-0.051,0.061-0.037,0.097l0.652,1.249c0.002,0.002,0.003,0.005,0.005,0.007l0.001,0.001l0.009,0.009 c0.023,0.019,0.056,0.023,0.085,0.009c0.016-0.008,0.028-0.021,0.035-0.037c0.001-0.001,0.001-0.003,0.002-0.004l0.239-0.572 l0.697-0.364c0.003,0.198,0.051,0.399,0.148,0.586c0.335,0.641,1.126,0.889,1.767,0.555c0.01-0.005,0.02-0.01,0.03-0.016 l0.731-0.382l0.137,0.262c0.067,0.128,0.225,0.177,0.352,0.11c0.128-0.067,0.177-0.224,0.111-0.352l-0.137-0.262l0.48-0.251 l0.399,0.764c0.093,0.178,0.312,0.247,0.49,0.154c0.05-0.026,0.091-0.062,0.122-0.104c0.082-0.108,0.098-0.258,0.032-0.386 l-0.399-0.764l0.435-0.227l0.137,0.262c0.067,0.128,0.224,0.177,0.352,0.111c0.128-0.067,0.177-0.225,0.11-0.352L9.765,9.83 l1.009-0.527c0.07-0.037,0.135-0.08,0.195-0.127c0.484-0.382,0.644-1.066,0.348-1.633c-0.098-0.187-0.235-0.341-0.396-0.457 l0.476-0.248l1.958,0.424c0.004,0.001,0.007,0.002,0.01,0.002c0.036,0.007,0.074,0.002,0.109-0.016 C13.552,7.208,13.585,7.117,13.555,7.038z M6.2,10.878c-0.004,0.003-0.009,0.005-0.013,0.007c-0.288,0.15-0.643,0.039-0.794-0.249 c-0.15-0.288-0.039-0.643,0.249-0.793l0.762-0.398l0.544,1.043L6.2,10.878z M7.41,10.246L6.866,9.204l0.032-0.016l0.449-0.234 l0.544,1.043l-0.102,0.053L7.41,10.246z M8.534,9.66L7.99,8.617L8.425,8.39l0.544,1.043L8.534,9.66z M10.34,8.716 c-0.006,0.004-0.013,0.008-0.019,0.011L9.432,9.191L8.888,8.148L9.55,7.802l0.212-0.111C9.774,7.686,9.785,7.68,9.797,7.675 c0.281-0.125,0.615-0.011,0.759,0.266c0.127,0.244,0.067,0.535-0.129,0.71C10.401,8.675,10.372,8.697,10.34,8.716z"
|
|
||||||
id="path30" /></svg>
|
|
Before Width: | Height: | Size: 6.9 KiB |
|
@ -1,189 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4157"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="24x24.svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="24px"
|
|
||||||
height="24px"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
enable-background="new 0 0 24 24"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata56"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs54" /><sodipodi:namedview
|
|
||||||
inkscape:zoom="16"
|
|
||||||
objecttolerance="10"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
showgrid="true"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
inkscape:cx="12.702408"
|
|
||||||
inkscape:cy="9.3530973"
|
|
||||||
id="namedview46"
|
|
||||||
inkscape:current-layer="svg4157"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:pageshadow="2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
empspacing="4"
|
|
||||||
id="grid4179" /><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
color="#ff3fff"
|
|
||||||
opacity="0.1254902"
|
|
||||||
spacingy="0.5"
|
|
||||||
empopacity="0.11764706"
|
|
||||||
empcolor="#3ff9ff"
|
|
||||||
empspacing="2"
|
|
||||||
spacingx="0.5"
|
|
||||||
id="grid4181" /></sodipodi:namedview><g
|
|
||||||
id="g2036-4"
|
|
||||||
transform="matrix(0.55,0,0,0.3333336,-1.2000011,7.33333)"><g
|
|
||||||
id="g3712-8"
|
|
||||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
|
||||||
opacity="0.4"><radialGradient
|
|
||||||
id="rect2801-6_1_"
|
|
||||||
cx="-410.7503"
|
|
||||||
cy="7665.8701"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(1.1601 0 0 -0.6 514.4986 4643.021)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop9" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop11" /></radialGradient><rect
|
|
||||||
id="rect2801-6"
|
|
||||||
x="38"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect2801-6_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><radialGradient
|
|
||||||
id="rect3696-20_1_"
|
|
||||||
cx="-1259.9097"
|
|
||||||
cy="10842.7783"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(-1.1601 0 0 0.6 -1451.5964 -6462.1655)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop15" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop17" /></radialGradient><rect
|
|
||||||
id="rect3696-20"
|
|
||||||
x="5"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3696-20_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><linearGradient
|
|
||||||
id="rect3700-5_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="48.2663"
|
|
||||||
y1="7022.2568"
|
|
||||||
x2="48.2663"
|
|
||||||
y2="7029.2852"
|
|
||||||
gradientTransform="matrix(0.5789 0 0 -0.4286 -3.9436 3056.5657)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop21" /><stop
|
|
||||||
offset="0.5"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop23" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop25" /></linearGradient><rect
|
|
||||||
id="rect3700-5"
|
|
||||||
x="10"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3700-5_1_)"
|
|
||||||
width="28"
|
|
||||||
height="7" /></g></g><radialGradient
|
|
||||||
id="SVGID_1_"
|
|
||||||
cx="-7.6034"
|
|
||||||
cy="12.4049"
|
|
||||||
r="59.5309"
|
|
||||||
fx="-10.0943"
|
|
||||||
fy="12.4049"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#95A3AB"
|
|
||||||
id="stop29" /><stop
|
|
||||||
offset="0.2624"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop31" /><stop
|
|
||||||
offset="0.705"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop33" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop35" /></radialGradient><path
|
|
||||||
fill="url(#SVGID_1_)"
|
|
||||||
d="M21.5,3.5v17c0,0.52-0.4,0.94-0.9,0.99c-0.03,0.01-0.07,0.01-0.1,0.01h-17c-0.55,0-1-0.45-1-1v-17 c0-0.55,0.45-1,1-1h17C21.05,2.5,21.5,2.95,21.5,3.5z"
|
|
||||||
id="path37" /><path
|
|
||||||
id="rect5505-21-8-1"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M3.5,2.5h17c0.552,0,1,0.448,1,1v17c0,0.552-0.448,1-1,1h-17c-0.552,0-1-0.448-1-1v-17C2.5,2.948,2.948,2.5,3.5,2.5z" /><linearGradient
|
|
||||||
id="rect6741-9_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="154.1423"
|
|
||||||
y1="3116.9497"
|
|
||||||
x2="154.1423"
|
|
||||||
y2="3082.2695"
|
|
||||||
gradientTransform="matrix(0.4595 0 0 -0.4595 -58.8209 1436.0929)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop41" /><stop
|
|
||||||
offset="0.0165"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop43" /><stop
|
|
||||||
offset="0.98"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop45" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop47" /></linearGradient><rect
|
|
||||||
id="rect6741-9"
|
|
||||||
x="3.501"
|
|
||||||
y="3.499"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741-9_1_)"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
width="17"
|
|
||||||
height="17" /><path
|
|
||||||
opacity="0.5"
|
|
||||||
fill="#273445"
|
|
||||||
d="M21.5,9.79V20.5c0,0.52-0.4,0.94-0.9,0.99H10.03l-4.77-4.64H5.25v-0.01 c-0.01-0.01-0.01-0.01-0.01-0.01l-0.45-0.85l-0.51-1c-0.02-0.05,0-0.11,0.05-0.14c0.03-0.02,0.05-0.02,0.08-0.02c0,0,0,0,0,0.01 l0.38,0.08l0.5,0.11l0.21-0.11l0.16-0.08l2.21-1.15c0.01-0.02,0.03-0.02,0.05-0.02L9,13.09l-0.18-0.34 c-0.07-0.14-0.05-0.3,0.04-0.42l-0.61-0.58c-0.01-0.01-0.03-0.03-0.04-0.05c-0.03-0.03-0.06-0.07-0.08-0.11 c-0.13-0.25-0.04-0.57,0.22-0.7l2.95-1.54c0.19-0.1,0.43-0.07,0.59,0.07l0.04,0.04l1.41,1.36l0.76-0.39 c0.02-0.02,0.03-0.02,0.05-0.03l2.24-1.18l1.14-2.72c0,0,0,0,0-0.01c0.02-0.05,0.07-0.09,0.11-0.12c0.09-0.05,0.2-0.04,0.27,0.02 L21.5,9.79z"
|
|
||||||
id="path50"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M20.068,10.472l-2.097-4.018c-0.003-0.005-0.006-0.009-0.01-0.013c-0.014-0.019-0.031-0.035-0.05-0.048 c-0.075-0.055-0.177-0.066-0.264-0.02c-0.051,0.027-0.09,0.069-0.112,0.119c-0.003,0.004-0.005,0.009-0.006,0.015l-1.134,2.719 l-2.25,1.174c-0.015,0.008-0.03,0.015-0.044,0.023l-0.765,0.399l-0.666,0.348l-0.174-0.334c-0.099-0.189-0.331-0.262-0.521-0.163 c-0.189,0.099-0.262,0.331-0.163,0.521l0.174,0.334l-0.644,0.336l-0.556-1.065l0.998-0.521c0.256-0.134,0.356-0.451,0.222-0.707 c-0.021-0.042-0.048-0.079-0.079-0.112l-0.045-0.044C11.725,9.28,11.495,9.247,11.3,9.349l-2.947,1.538 c-0.257,0.134-0.356,0.451-0.222,0.707c0.021,0.04,0.046,0.077,0.076,0.109c0.015,0.017,0.032,0.034,0.049,0.048 c0.158,0.134,0.387,0.167,0.582,0.065l0.998-0.521l0.556,1.065L9.68,12.731l-0.174-0.334c-0.099-0.189-0.332-0.262-0.521-0.163 c-0.05,0.026-0.091,0.061-0.124,0.103c-0.091,0.116-0.112,0.279-0.039,0.418l0.174,0.334l-1.082,0.565 c-0.015,0.007-0.03,0.015-0.044,0.023l-2.577,1.345l-0.879-0.191C4.41,14.83,4.408,14.83,4.406,14.829 c-0.024-0.005-0.051-0.001-0.074,0.011c-0.053,0.028-0.075,0.09-0.054,0.143l0.964,1.847c0.002,0.003,0.005,0.007,0.008,0.01 l0.001,0.002l0.014,0.013c0.034,0.028,0.083,0.035,0.126,0.013c0.024-0.013,0.041-0.032,0.051-0.055 c0.001-0.002,0.002-0.004,0.003-0.006l0.353-0.846l1.032-0.539c0.004,0.293,0.075,0.59,0.22,0.867 c0.495,0.948,1.665,1.315,2.613,0.82c0.015-0.008,0.03-0.015,0.044-0.023l1.082-0.565l0.202,0.388 c0.099,0.189,0.332,0.262,0.521,0.163c0.189-0.099,0.262-0.332,0.164-0.521l-0.202-0.387l0.711-0.371l0.59,1.13 c0.137,0.263,0.461,0.365,0.725,0.228c0.074-0.038,0.135-0.091,0.18-0.153c0.121-0.16,0.146-0.382,0.047-0.571l-0.59-1.13 l0.644-0.336l0.202,0.387c0.099,0.189,0.332,0.262,0.521,0.164c0.189-0.099,0.262-0.332,0.163-0.521l-0.202-0.388l1.493-0.78 c0.103-0.055,0.199-0.118,0.288-0.188c0.716-0.565,0.953-1.577,0.515-2.415c-0.145-0.277-0.348-0.505-0.586-0.676l0.704-0.367 l2.896,0.628c0.006,0.001,0.01,0.003,0.015,0.003c0.054,0.01,0.11,0.003,0.161-0.024C20.065,10.724,20.114,10.589,20.068,10.472z M9.188,16.153c-0.007,0.004-0.013,0.007-0.02,0.011c-0.426,0.223-0.952,0.058-1.174-0.369c-0.222-0.426-0.058-0.951,0.368-1.174 l1.128-0.589l0.805,1.542L9.188,16.153z M10.978,15.219l-0.805-1.542l0.047-0.024l0.664-0.347l0.805,1.542l-0.151,0.079 L10.978,15.219z M12.641,14.351l-0.805-1.542l0.644-0.336l0.805,1.542L12.641,14.351z M15.312,12.955 c-0.009,0.006-0.019,0.012-0.029,0.017l-1.315,0.686l-0.805-1.542l0.98-0.512l0.314-0.164c0.017-0.009,0.033-0.017,0.051-0.024 c0.416-0.185,0.91-0.017,1.123,0.393c0.188,0.36,0.099,0.792-0.19,1.051C15.402,12.894,15.359,12.926,15.312,12.955z"
|
|
||||||
id="path52" /></svg>
|
|
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,185 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4588"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="32x32.svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="32px"
|
|
||||||
height="32px"
|
|
||||||
viewBox="0 0 32 32"
|
|
||||||
enable-background="new 0 0 32 32"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata56"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs54" /><sodipodi:namedview
|
|
||||||
inkscape:zoom="16"
|
|
||||||
objecttolerance="10"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
showgrid="true"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
inkscape:cx="13.84291"
|
|
||||||
inkscape:cy="15.588816"
|
|
||||||
id="namedview46"
|
|
||||||
inkscape:current-layer="svg4588"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:pageshadow="2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
empspacing="4"
|
|
||||||
id="grid4203" /><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
color="#ff3fff"
|
|
||||||
opacity="0.37254902"
|
|
||||||
spacingy="0.5"
|
|
||||||
empopacity="0.1254902"
|
|
||||||
empcolor="#3fffff"
|
|
||||||
empspacing="2"
|
|
||||||
spacingx="0.5"
|
|
||||||
id="grid4205" /></sodipodi:namedview><g
|
|
||||||
id="g2036-2"
|
|
||||||
transform="matrix(0.6999997,0,0,0.3333336,-0.8000002,15.33333)"><g
|
|
||||||
id="g3712-3"
|
|
||||||
transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"
|
|
||||||
opacity="0.4"><radialGradient
|
|
||||||
id="rect2801-0_1_"
|
|
||||||
cx="-392.7515"
|
|
||||||
cy="7672.0923"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(1.4765 0 0 -0.6 617.8796 4646.7544)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop9" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop11" /></radialGradient><rect
|
|
||||||
id="rect2801-0"
|
|
||||||
x="38"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect2801-0_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><radialGradient
|
|
||||||
id="rect3696-2_1_"
|
|
||||||
cx="-909.6242"
|
|
||||||
cy="10817.8896"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(-1.4765 0 0 0.6 -1333.028 -6447.2324)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop15" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop17" /></radialGradient><rect
|
|
||||||
id="rect3696-2"
|
|
||||||
x="5"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3696-2_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><linearGradient
|
|
||||||
id="rect3700-1_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-107.542"
|
|
||||||
y1="7034.7012"
|
|
||||||
x2="-107.542"
|
|
||||||
y2="7041.7295"
|
|
||||||
gradientTransform="matrix(0.7368 0 0 -0.4286 103.2414 3061.8992)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop21" /><stop
|
|
||||||
offset="0.5"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop23" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop25" /></linearGradient><rect
|
|
||||||
id="rect3700-1"
|
|
||||||
x="10"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3700-1_1_)"
|
|
||||||
width="28"
|
|
||||||
height="7" /></g></g><radialGradient
|
|
||||||
id="SVGID_1_"
|
|
||||||
cx="-11.8574"
|
|
||||||
cy="16.5754"
|
|
||||||
r="84.5966"
|
|
||||||
fx="-15.3972"
|
|
||||||
fy="16.5754"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#95A3AB"
|
|
||||||
id="stop29" /><stop
|
|
||||||
offset="0.2624"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop31" /><stop
|
|
||||||
offset="0.705"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop33" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop35" /></radialGradient><path
|
|
||||||
fill="url(#SVGID_1_)"
|
|
||||||
d="M29.5,4.66v22.68c0,1.06-0.78,1.95-1.79,2.13c-0.12,0.02-0.24,0.03-0.37,0.03H4.66 c-1.19,0-2.16-0.97-2.16-2.16V4.66c0-1.19,0.97-2.16,2.16-2.16h22.68C28.53,2.5,29.5,3.47,29.5,4.66z"
|
|
||||||
id="path37" /><linearGradient
|
|
||||||
id="rect6741-7-4_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-46.3832"
|
|
||||||
y1="3411.5254"
|
|
||||||
x2="-46.3832"
|
|
||||||
y2="3376.2197"
|
|
||||||
gradientTransform="matrix(0.6757 0 0 -0.6757 47.34 2309.1753)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop40" /><stop
|
|
||||||
offset="0.038"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop42" /><stop
|
|
||||||
offset="0.962"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop44" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop46" /></linearGradient><path
|
|
||||||
id="rect6741-7-4"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741-7-4_1_)"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M4.587,3.5h22.826c0.6,0,1.087,0.487,1.087,1.087v22.826c0,0.6-0.487,1.087-1.087,1.087H4.587c-0.6,0-1.087-0.487-1.087-1.087V4.587 C3.5,3.987,3.987,3.5,4.587,3.5z" /><path
|
|
||||||
id="rect5505-6"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M4.66,2.5h22.68c1.193,0,2.16,0.967,2.16,2.16v22.68c0,1.193-0.967,2.16-2.16,2.16H4.66c-1.193,0-2.16-0.967-2.16-2.16V4.66 C2.5,3.467,3.467,2.5,4.66,2.5z" /><path
|
|
||||||
opacity="0.5"
|
|
||||||
fill="#273445"
|
|
||||||
d="M29.5,12.53v14.81c0,1.06-0.78,1.95-1.79,2.13H12.87l-6.9-6.72l-0.02-0.01v-0.01 c-0.01-0.01-0.01-0.01-0.01-0.01l-0.65-1.24l-0.75-1.44c-0.03-0.08,0.01-0.16,0.08-0.21c0.04-0.02,0.07-0.02,0.11-0.02 c0,0,0,0,0,0.01l0.56,0.12l0.72,0.16l0.3-0.16l0.23-0.12l3.2-1.67c0.02-0.02,0.05-0.03,0.07-0.03l1.57-0.82l-0.26-0.49 c-0.1-0.2-0.07-0.44,0.06-0.61l-0.88-0.84c-0.02-0.02-0.05-0.04-0.07-0.07c-0.04-0.05-0.08-0.1-0.11-0.16 c-0.19-0.37-0.05-0.83,0.32-1.02l4.27-2.23c0.28-0.15,0.62-0.1,0.85,0.1l0.06,0.06l2.04,1.97l1.11-0.57 c0.02-0.02,0.04-0.03,0.07-0.04l3.25-1.7l1.65-3.94c0-0.01,0-0.01,0-0.02c0.03-0.07,0.09-0.13,0.16-0.17 c0.13-0.07,0.28-0.06,0.39,0.02L29.5,12.53z"
|
|
||||||
id="path50"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M27.412,13.506l-3.038-5.82c-0.005-0.007-0.009-0.013-0.014-0.019c-0.021-0.027-0.045-0.05-0.072-0.07 c-0.108-0.079-0.256-0.095-0.383-0.029c-0.074,0.039-0.13,0.101-0.161,0.172c-0.004,0.006-0.007,0.014-0.009,0.021L22.092,11.7 l-3.258,1.701c-0.021,0.011-0.043,0.022-0.064,0.034l-1.108,0.578l-0.965,0.504l-0.252-0.484c-0.143-0.274-0.48-0.38-0.754-0.237 c-0.273,0.143-0.38,0.48-0.237,0.754l0.252,0.484l-0.932,0.487l-0.806-1.542l1.445-0.754c0.371-0.194,0.515-0.653,0.321-1.024 c-0.031-0.06-0.07-0.114-0.114-0.162l-0.066-0.064c-0.228-0.195-0.562-0.243-0.844-0.096l-4.269,2.228 c-0.372,0.194-0.516,0.653-0.322,1.024c0.031,0.058,0.067,0.111,0.11,0.157c0.022,0.025,0.046,0.049,0.072,0.069 c0.228,0.194,0.561,0.242,0.843,0.094l1.445-0.754l0.805,1.542l-1.029,0.537l-0.252-0.484c-0.143-0.273-0.481-0.379-0.754-0.237 c-0.072,0.038-0.132,0.089-0.18,0.149c-0.132,0.168-0.162,0.404-0.057,0.605l0.252,0.484l-1.567,0.818 c-0.021,0.011-0.043,0.022-0.064,0.033l-3.733,1.949l-1.274-0.276c-0.003-0.001-0.007-0.002-0.01-0.002 c-0.035-0.007-0.073-0.002-0.108,0.016c-0.076,0.04-0.109,0.13-0.078,0.208l1.397,2.675c0.003,0.005,0.007,0.01,0.011,0.015 l0.002,0.002l0.02,0.019c0.05,0.041,0.121,0.05,0.182,0.019c0.034-0.018,0.06-0.046,0.074-0.079 c0.002-0.003,0.003-0.006,0.004-0.009l0.511-1.226l1.494-0.78c0.006,0.425,0.108,0.854,0.318,1.256 c0.717,1.373,2.412,1.905,3.785,1.188c0.021-0.011,0.043-0.022,0.064-0.034l1.567-0.818l0.293,0.562 c0.143,0.273,0.481,0.379,0.754,0.237c0.273-0.143,0.38-0.48,0.237-0.754l-0.293-0.561l1.029-0.538l0.855,1.637 c0.199,0.381,0.668,0.529,1.049,0.33c0.107-0.055,0.195-0.132,0.261-0.222c0.175-0.232,0.211-0.553,0.068-0.827l-0.854-1.637 l0.932-0.487l0.293,0.561c0.143,0.273,0.48,0.38,0.754,0.237c0.274-0.143,0.379-0.481,0.237-0.754l-0.293-0.562l2.163-1.129 c0.149-0.08,0.288-0.171,0.417-0.273c1.037-0.819,1.38-2.284,0.746-3.498c-0.21-0.401-0.503-0.731-0.848-0.979l1.019-0.532 l4.194,0.909c0.008,0.002,0.015,0.004,0.022,0.005c0.078,0.015,0.159,0.005,0.234-0.035C27.406,13.871,27.477,13.676,27.412,13.506z M11.653,21.735c-0.01,0.006-0.019,0.011-0.029,0.016c-0.617,0.322-1.378,0.084-1.7-0.534c-0.322-0.617-0.084-1.378,0.533-1.7 l1.633-0.853l1.166,2.234L11.653,21.735z M14.246,20.381l-1.166-2.234l0.068-0.035l0.961-0.502l1.166,2.234l-0.219,0.114 L14.246,20.381z M16.655,19.124l-1.166-2.234l0.932-0.487l1.166,2.234L16.655,19.124z M20.523,17.102 c-0.014,0.009-0.028,0.017-0.042,0.024l-1.904,0.994l-1.166-2.234l1.42-0.742l0.455-0.237c0.024-0.012,0.048-0.024,0.073-0.035 c0.603-0.268,1.318-0.024,1.627,0.569c0.273,0.522,0.144,1.147-0.276,1.522C20.654,17.014,20.592,17.061,20.523,17.102z"
|
|
||||||
id="path52" /></svg>
|
|
Before Width: | Height: | Size: 9.2 KiB |
|
@ -1,184 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4405"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="48x48.svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="48px"
|
|
||||||
height="48px"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
enable-background="new 0 0 48 48"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata57"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs55" /><sodipodi:namedview
|
|
||||||
inkscape:zoom="8"
|
|
||||||
objecttolerance="10"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
showgrid="true"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
inkscape:cx="18.590413"
|
|
||||||
inkscape:cy="19.492052"
|
|
||||||
id="namedview45"
|
|
||||||
inkscape:current-layer="g6"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:pageshadow="2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
empspacing="4"
|
|
||||||
id="grid4182" /><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
color="#ff3fff"
|
|
||||||
opacity="0.31372549"
|
|
||||||
spacingy="0.5"
|
|
||||||
empopacity="0.11764706"
|
|
||||||
empcolor="#3fffff"
|
|
||||||
empspacing="2"
|
|
||||||
spacingx="0.5"
|
|
||||||
id="grid4184" /></sodipodi:namedview><g
|
|
||||||
id="g6"><g
|
|
||||||
id="g3712-0"
|
|
||||||
transform="matrix(1.1578952,0,0,0.57142859,-3.789476,19.142856)"
|
|
||||||
opacity="0.4"><radialGradient
|
|
||||||
id="rect2801-4_1_"
|
|
||||||
cx="-303.9869"
|
|
||||||
cy="6065.9893"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(2.3202 0 0 -0.8 743.2948 4896.2915)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop10" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop12" /></radialGradient><rect
|
|
||||||
id="rect2801-4"
|
|
||||||
x="38"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect2801-4_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><radialGradient
|
|
||||||
id="rect3696-8_1_"
|
|
||||||
cx="-507.3432"
|
|
||||||
cy="7800.5005"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(-2.3202 0 0 0.8 -1167.1165 -6196.9004)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop16" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop18" /></radialGradient><rect
|
|
||||||
id="rect3696-8"
|
|
||||||
x="5"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3696-8_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><linearGradient
|
|
||||||
id="rect3700-7_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-185.5088"
|
|
||||||
y1="5712.9136"
|
|
||||||
x2="-185.5088"
|
|
||||||
y2="5719.9419"
|
|
||||||
gradientTransform="matrix(1.1579 0 0 -0.5714 238.7997 3311.5498)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop22" /><stop
|
|
||||||
offset="0.5"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop24" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop26" /></linearGradient><rect
|
|
||||||
id="rect3700-7"
|
|
||||||
x="10"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3700-7_1_)"
|
|
||||||
width="28"
|
|
||||||
height="7" /></g><radialGradient
|
|
||||||
id="SVGID_1_"
|
|
||||||
cx="-16.2385"
|
|
||||||
cy="25.8311"
|
|
||||||
r="122.195"
|
|
||||||
fx="-21.3515"
|
|
||||||
fy="25.8311"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#95A3AB"
|
|
||||||
id="stop30" /><stop
|
|
||||||
offset="0.2624"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop32" /><stop
|
|
||||||
offset="0.705"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop34" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop36" /></radialGradient><path
|
|
||||||
fill="url(#SVGID_1_)"
|
|
||||||
d="M43.5,7.5v35c0,0.96-0.69,1.77-1.6,1.96c-0.13,0.03-0.26,0.04-0.4,0.04h-35c-1.1,0-2-0.9-2-2v-35 c0-1.1,0.9-2,2-2h35C42.6,5.5,43.5,6.4,43.5,7.5z"
|
|
||||||
id="path38" /><linearGradient
|
|
||||||
id="rect6741-1_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-181.34"
|
|
||||||
y1="3621.929"
|
|
||||||
x2="-181.34"
|
|
||||||
y2="3586.1492"
|
|
||||||
gradientTransform="matrix(1 0 0 -1 205.34 3629.04)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop41" /><stop
|
|
||||||
offset="0.021"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop43" /><stop
|
|
||||||
offset="0.977"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop45" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop47" /></linearGradient><path
|
|
||||||
id="rect6741-1"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741-1_1_)"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M6.5,6.5h35c0.552,0,1,0.448,1,1v35c0,0.552-0.448,1-1,1h-35c-0.552,0-1-0.448-1-1v-35C5.5,6.948,5.948,6.5,6.5,6.5z" /><path
|
|
||||||
id="rect5505-21-6"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M6.5,5.5h35c1.105,0,2,0.895,2,2v35c0,1.105-0.895,2-2,2h-35c-1.105,0-2-0.895-2-2v-35C4.5,6.395,5.395,5.5,6.5,5.5z" /><path
|
|
||||||
opacity="0.5"
|
|
||||||
fill="#273445"
|
|
||||||
d="M43.5,19.234v23.477c0,0.857-0.616,1.579-1.428,1.749H19.773L9.689,34.644l-0.027-0.027 v-0.009c-0.009-0.009-0.009-0.018-0.018-0.018l-0.946-1.811l-1.098-2.097c-0.045-0.116,0.009-0.241,0.116-0.303 c0.054-0.027,0.107-0.036,0.161-0.027c0,0,0.009,0,0.009,0.009l0.812,0.178l1.053,0.223l0.437-0.232l0.33-0.17l4.685-2.445 c0.027-0.018,0.062-0.036,0.089-0.044l2.293-1.196l-0.375-0.714c-0.152-0.286-0.107-0.634,0.089-0.883l-1.285-1.232 c-0.036-0.036-0.071-0.062-0.098-0.107c-0.062-0.063-0.116-0.143-0.161-0.223c-0.285-0.544-0.08-1.214,0.464-1.499l6.237-3.257 c0.41-0.214,0.901-0.143,1.231,0.143l0.098,0.098l2.98,2.873l1.615-0.839c0.027-0.018,0.062-0.036,0.098-0.054l4.756-2.48 l2.4-5.756c0-0.009,0-0.018,0.009-0.027c0.045-0.107,0.125-0.197,0.232-0.25c0.187-0.098,0.402-0.08,0.562,0.036L43.5,19.234z"
|
|
||||||
id="path51"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M41.005,21.139l-4.436-8.499c-0.007-0.01-0.013-0.019-0.02-0.028c-0.031-0.039-0.066-0.074-0.105-0.102 c-0.158-0.115-0.373-0.139-0.559-0.042c-0.109,0.057-0.189,0.147-0.236,0.251c-0.007,0.009-0.01,0.02-0.014,0.031l-2.398,5.752 l-4.758,2.484c-0.031,0.016-0.063,0.033-0.093,0.05l-1.618,0.845l-1.41,0.736l-0.369-0.706c-0.209-0.4-0.701-0.555-1.101-0.346 c-0.399,0.208-0.555,0.701-0.346,1.101l0.369,0.706l-1.362,0.711l-1.176-2.253l2.111-1.102c0.542-0.284,0.752-0.953,0.469-1.495 c-0.045-0.088-0.102-0.167-0.167-0.236l-0.096-0.093c-0.334-0.285-0.82-0.355-1.232-0.14l-6.234,3.254 c-0.543,0.284-0.753,0.953-0.47,1.496c0.045,0.085,0.098,0.162,0.161,0.23c0.032,0.037,0.068,0.071,0.105,0.101 c0.334,0.283,0.819,0.353,1.23,0.138l2.11-1.101l1.176,2.253l-1.503,0.785l-0.369-0.706c-0.209-0.399-0.703-0.554-1.101-0.346 c-0.105,0.055-0.193,0.129-0.263,0.217c-0.193,0.245-0.237,0.589-0.083,0.884l0.369,0.706l-2.289,1.195 c-0.031,0.015-0.063,0.032-0.094,0.048L9.75,30.763l-1.86-0.404c-0.005-0.002-0.01-0.002-0.014-0.003 c-0.052-0.01-0.107-0.002-0.157,0.023c-0.111,0.058-0.159,0.189-0.115,0.303l2.04,3.907c0.005,0.007,0.01,0.015,0.016,0.021 l0.003,0.003l0.03,0.028c0.072,0.06,0.176,0.073,0.266,0.027c0.05-0.027,0.087-0.067,0.109-0.116 c0.002-0.004,0.004-0.008,0.006-0.013l0.747-1.79l2.182-1.14c0.009,0.62,0.158,1.247,0.464,1.834 c1.047,2.005,3.522,2.782,5.528,1.735c0.031-0.016,0.063-0.033,0.093-0.05l2.289-1.195l0.428,0.82 c0.208,0.399,0.703,0.554,1.101,0.346c0.399-0.208,0.555-0.702,0.346-1.101l-0.428-0.819l1.503-0.785l1.248,2.391 c0.291,0.557,0.976,0.772,1.533,0.481c0.156-0.081,0.285-0.193,0.382-0.324c0.255-0.339,0.308-0.807,0.099-1.208l-1.248-2.391 l1.362-0.711l0.428,0.819c0.208,0.399,0.702,0.555,1.101,0.346c0.4-0.209,0.554-0.703,0.346-1.101l-0.428-0.82l3.158-1.649 c0.218-0.117,0.421-0.25,0.609-0.399c1.515-1.196,2.015-3.336,1.09-5.108c-0.306-0.586-0.735-1.067-1.238-1.43l1.489-0.777 l6.125,1.327c0.012,0.003,0.022,0.006,0.033,0.007c0.113,0.021,0.233,0.007,0.341-0.05C40.997,21.673,41.1,21.388,41.005,21.139z M17.991,33.156c-0.014,0.009-0.027,0.015-0.042,0.023c-0.902,0.471-2.013,0.122-2.483-0.78c-0.47-0.901-0.122-2.012,0.778-2.482 l2.385-1.245l1.703,3.262L17.991,33.156z M21.778,31.179l-1.703-3.262l0.1-0.051l1.404-0.733l1.703,3.262l-0.32,0.167 L21.778,31.179z M25.296,29.343l-1.703-3.262l1.362-0.711l1.703,3.262L25.296,29.343z M30.945,26.39 c-0.02,0.013-0.04,0.025-0.061,0.035l-2.78,1.452l-1.703-3.262l2.073-1.083l0.664-0.346c0.035-0.018,0.071-0.035,0.107-0.052 c0.88-0.392,1.924-0.035,2.376,0.831c0.399,0.762,0.21,1.675-0.403,2.223C31.136,26.263,31.045,26.331,30.945,26.39z"
|
|
||||||
id="path53" /></g></svg>
|
|
Before Width: | Height: | Size: 9.3 KiB |
|
@ -1,184 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4332"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="64x64.svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="64px"
|
|
||||||
height="64px"
|
|
||||||
viewBox="0 0 64 64"
|
|
||||||
enable-background="new 0 0 64 64"
|
|
||||||
xml:space="preserve"><metadata
|
|
||||||
id="metadata54"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs52" /><sodipodi:namedview
|
|
||||||
inkscape:zoom="8"
|
|
||||||
objecttolerance="10"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
showgrid="true"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
inkscape:cx="16.97465"
|
|
||||||
inkscape:cy="32.785763"
|
|
||||||
id="namedview46"
|
|
||||||
inkscape:current-layer="svg4332"
|
|
||||||
inkscape:window-height="705"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:pageshadow="2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
empspacing="4"
|
|
||||||
id="grid4203" /><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
color="#ff3fff"
|
|
||||||
opacity="0.29803922"
|
|
||||||
spacingy="0.5"
|
|
||||||
empopacity="0.1372549"
|
|
||||||
empcolor="#3fffff"
|
|
||||||
empspacing="2"
|
|
||||||
spacingx="0.5"
|
|
||||||
id="grid4187" /></sodipodi:namedview><g
|
|
||||||
id="g3712"
|
|
||||||
transform="matrix(1.5789502,0,0,0.7142857,-5.8947511,28.428574)"
|
|
||||||
opacity="0.4"><radialGradient
|
|
||||||
id="rect2801_1_"
|
|
||||||
cx="-238.0086"
|
|
||||||
cy="5000.9541"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(3.1639 0 0 -1 791.0224 5044.4541)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop8" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop10" /></radialGradient><rect
|
|
||||||
id="rect2801"
|
|
||||||
x="38"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect2801_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><radialGradient
|
|
||||||
id="rect3696_1_"
|
|
||||||
cx="-344.166"
|
|
||||||
cy="6088.6416"
|
|
||||||
r="2.4994"
|
|
||||||
gradientTransform="matrix(-3.1639 0 0 1 -1078.8911 -6045.1416)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop14" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop16" /></radialGradient><rect
|
|
||||||
id="rect3696"
|
|
||||||
x="5"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3696_1_)"
|
|
||||||
width="5"
|
|
||||||
height="7" /><linearGradient
|
|
||||||
id="rect3700_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-172.6913"
|
|
||||||
y1="4778.4775"
|
|
||||||
x2="-172.6913"
|
|
||||||
y2="4785.5059"
|
|
||||||
gradientTransform="matrix(1.579 0 0 -0.7143 296.6709 3460.2258)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop20" /><stop
|
|
||||||
offset="0.5"
|
|
||||||
style="stop-color:#181818"
|
|
||||||
id="stop22" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#181818;stop-opacity:0"
|
|
||||||
id="stop24" /></linearGradient><rect
|
|
||||||
id="rect3700"
|
|
||||||
x="10"
|
|
||||||
y="40"
|
|
||||||
fill="url(#rect3700_1_)"
|
|
||||||
width="28"
|
|
||||||
height="7" /></g><radialGradient
|
|
||||||
id="rect5505-21-3-8-5-2-9_1_"
|
|
||||||
cx="-434.767"
|
|
||||||
cy="4038.6685"
|
|
||||||
r="12.6719"
|
|
||||||
fx="-435.2973"
|
|
||||||
fy="4038.6685"
|
|
||||||
gradientTransform="matrix(0 13.394 16.4435 0 -66376.8047 5798.5156)"
|
|
||||||
gradientUnits="userSpaceOnUse"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop28" /><stop
|
|
||||||
offset="0.2624"
|
|
||||||
style="stop-color:#9BAEC8"
|
|
||||||
id="stop30" /><stop
|
|
||||||
offset="0.705"
|
|
||||||
style="stop-color:#8093AA"
|
|
||||||
id="stop32" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#273445"
|
|
||||||
id="stop34" /></radialGradient><path
|
|
||||||
id="rect5505-21-3-8-5-2-9"
|
|
||||||
fill="url(#rect5505-21-3-8-5-2-9_1_)"
|
|
||||||
d="M7.5,4.5h49c1.657,0,3,1.343,3,3v49c0,1.657-1.343,3-3,3 h-49c-1.657,0-3-1.343-3-3v-49C4.5,5.843,5.843,4.5,7.5,4.5z" /><linearGradient
|
|
||||||
id="rect6741_1_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-261.3883"
|
|
||||||
y1="3756.1287"
|
|
||||||
x2="-261.3883"
|
|
||||||
y2="3719.9463"
|
|
||||||
gradientTransform="matrix(1.4324 0 0 -1.4363 406.4211 5400.8813)"><stop
|
|
||||||
offset="0"
|
|
||||||
style="stop-color:#FFFFFF"
|
|
||||||
id="stop38" /><stop
|
|
||||||
offset="0.0349"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.2353"
|
|
||||||
id="stop40" /><stop
|
|
||||||
offset="0.9622"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.1569"
|
|
||||||
id="stop42" /><stop
|
|
||||||
offset="1"
|
|
||||||
style="stop-color:#FFFFFF;stop-opacity:0.3922"
|
|
||||||
id="stop44" /></linearGradient><path
|
|
||||||
id="rect6741"
|
|
||||||
opacity="0.3"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#rect6741_1_)"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M7.5,5.429h49c1.105,0,2,0.895,2,2v49.142c0,1.105-0.895,2-2,2h-49c-1.105,0-2-0.895-2-2V7.429C5.5,6.324,6.395,5.429,7.5,5.429z" /><path
|
|
||||||
id="rect5505-21-9"
|
|
||||||
opacity="0.5"
|
|
||||||
fill="none"
|
|
||||||
stroke="#0E141F"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
enable-background="new "
|
|
||||||
d=" M7.5,4.5h49c1.657,0,3,1.343,3,3v49c0,1.657-1.343,3-3,3h-49c-1.657,0-3-1.343-3-3v-49C4.5,5.843,5.843,4.5,7.5,4.5z" /><path
|
|
||||||
opacity="0.5"
|
|
||||||
fill="#273445"
|
|
||||||
d="M59.5,23.606v32.74c0,1.77-1.436,3.205-3.205,3.205H25.493L11.094,45.523l-0.042-0.042v-0.005 c-0.011-0.011-0.016-0.021-0.026-0.027l-2.914-5.584c-0.064-0.164,0.005-0.35,0.164-0.434c0.074-0.037,0.154-0.048,0.222-0.032 c0.011,0,0.016,0,0.021,0.005l2.659,0.578l0.625-0.329l0.472-0.244l6.691-3.497c0.042-0.021,0.09-0.048,0.132-0.069l3.274-1.706 l-0.53-1.012c-0.217-0.419-0.154-0.911,0.122-1.261l-1.833-1.77c-0.053-0.042-0.101-0.09-0.148-0.143 c-0.09-0.101-0.164-0.207-0.228-0.328c-0.408-0.779-0.106-1.732,0.673-2.14l8.906-4.646c0.588-0.307,1.282-0.207,1.759,0.201 l0.138,0.132l4.259,4.116l2.31-1.203c0.042-0.027,0.09-0.048,0.132-0.074l6.797-3.55l3.428-8.217 c0.005-0.016,0.011-0.032,0.021-0.042c0.064-0.148,0.18-0.275,0.334-0.36c0.265-0.138,0.572-0.106,0.8,0.058L59.5,23.606z"
|
|
||||||
id="path48"
|
|
||||||
style="opacity:0.15" /><path
|
|
||||||
fill="#FFFFFF"
|
|
||||||
d="M55.829,26.231l-6.338-12.142c-0.01-0.014-0.018-0.027-0.029-0.04c-0.044-0.056-0.094-0.105-0.151-0.146 c-0.226-0.165-0.534-0.199-0.798-0.06c-0.155,0.082-0.271,0.21-0.337,0.359c-0.009,0.013-0.014,0.029-0.019,0.044l-3.426,8.217 l-6.798,3.549c-0.045,0.023-0.091,0.047-0.133,0.071l-2.311,1.207l-2.014,1.051l-0.527-1.009c-0.299-0.572-1.001-0.793-1.573-0.494 c-0.57,0.298-0.793,1.001-0.494,1.573l0.527,1.009l-1.946,1.016l-1.681-3.218l3.016-1.574c0.774-0.405,1.075-1.362,0.671-2.136 c-0.065-0.126-0.145-0.238-0.238-0.338l-0.137-0.133c-0.477-0.407-1.172-0.507-1.76-0.2l-8.907,4.649 c-0.775,0.405-1.076,1.362-0.672,2.137c0.064,0.121,0.14,0.231,0.23,0.328c0.046,0.053,0.097,0.102,0.149,0.145 c0.476,0.405,1.17,0.504,1.758,0.196l3.015-1.573l1.679,3.218l-2.148,1.121l-0.527-1.009c-0.299-0.57-1.004-0.791-1.573-0.494 c-0.151,0.078-0.276,0.185-0.376,0.31c-0.275,0.35-0.338,0.842-0.119,1.263l0.527,1.009l-3.27,1.708 c-0.045,0.022-0.089,0.045-0.134,0.069l-7.788,4.066L8.52,39.402C8.513,39.4,8.506,39.399,8.5,39.398 c-0.074-0.014-0.153-0.003-0.225,0.033c-0.159,0.083-0.227,0.271-0.164,0.433l2.914,5.581c0.007,0.011,0.015,0.021,0.023,0.03 l0.004,0.005l0.042,0.04c0.103,0.086,0.252,0.105,0.38,0.039c0.072-0.038,0.124-0.096,0.155-0.165 c0.003-0.006,0.006-0.012,0.008-0.019l1.067-2.558l3.117-1.628c0.012,0.886,0.226,1.782,0.664,2.62 c1.496,2.864,5.032,3.975,7.897,2.479c0.045-0.023,0.089-0.047,0.133-0.071l3.27-1.708l0.611,1.172 c0.298,0.57,1.004,0.791,1.573,0.494c0.57-0.298,0.793-1.002,0.495-1.573l-0.611-1.17l2.148-1.122l1.784,3.416 c0.415,0.795,1.394,1.103,2.189,0.688c0.222-0.115,0.407-0.276,0.545-0.463c0.365-0.484,0.44-1.153,0.141-1.726l-1.782-3.416 l1.946-1.016l0.611,1.17c0.298,0.57,1.002,0.793,1.573,0.495c0.572-0.299,0.791-1.004,0.494-1.573l-0.611-1.172l4.512-2.356 c0.312-0.166,0.602-0.358,0.87-0.57c2.164-1.708,2.879-4.766,1.557-7.298c-0.437-0.837-1.05-1.525-1.769-2.042l2.127-1.11 l8.751,1.897c0.017,0.004,0.031,0.009,0.046,0.01c0.162,0.031,0.332,0.01,0.488-0.072C55.818,26.993,55.966,26.586,55.829,26.231z M22.951,43.399c-0.02,0.012-0.039,0.022-0.06,0.033c-1.288,0.673-2.875,0.174-3.548-1.114c-0.671-1.287-0.174-2.874,1.111-3.547 l3.408-1.779l2.433,4.66L22.951,43.399z M28.361,40.574l-2.433-4.66l0.142-0.074l2.006-1.047l2.433,4.66l-0.458,0.239L28.361,40.574 z M33.386,37.952l-2.433-4.66l1.946-1.016l2.433,4.66L33.386,37.952z M41.458,33.733c-0.028,0.018-0.058,0.035-0.087,0.05 l-3.972,2.074l-2.433-4.66l2.962-1.547l0.949-0.495c0.051-0.026,0.101-0.05,0.153-0.074c1.258-0.559,2.749-0.05,3.395,1.187 c0.57,1.089,0.3,2.393-0.575,3.176C41.73,33.55,41.6,33.648,41.458,33.733z"
|
|
||||||
id="path50" /></svg>
|
|
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2018 CallMeFib3r
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="radial0" gradientUnits="userSpaceOnUse" cx="26.10651" cy="259.029419" fx="26.10651" fy="259.029419" r="16.961348" gradientTransform="matrix(1.293566,0.0000000199752,-0.0000000199752,1.293566,-279.770477,-257.071594)">
|
||||||
|
<stop offset="0" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
|
||||||
|
<stop offset="0.4572" style="stop-color:rgb(96.078432%,76.078433%,6.666667%);stop-opacity:1;"/>
|
||||||
|
<stop offset="0.685319" style="stop-color:rgb(97.647059%,85.882354%,57.647061%);stop-opacity:1;"/>
|
||||||
|
<stop offset="0.822382" style="stop-color:rgb(96.078432%,74.901962%,17.647059%);stop-opacity:1;"/>
|
||||||
|
<stop offset="1" style="stop-color:rgb(89.803922%,64.705884%,3.921569%);stop-opacity:1;"/>
|
||||||
|
<stop offset="1" style="stop-color:rgb(89.803922%,64.705884%,3.921569%);stop-opacity:1;"/>
|
||||||
|
</radialGradient>
|
||||||
|
<image id="image1393" width="192" height="152" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAACYCAYAAACyAeadAAAABmJLR0QA/wD/AP+gvaeTAAAMNElEQVR4nO3dy3MbV3bH8e+53Q2A4Et8gHrLiETJNlWWa8IxPXEqJWaTVSo75d+R/G94l8rO3E5VtvQqKy1SiZUal8pRObIVh7YlkiJBAN3nlwUIiXIcj+OhSIo4nwWLJYGNbvKc+74XEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghHBI77hsIx0/ajwMDAx3z7RypSIARJmF8jHF7NT3Y2EhLfF5xBzcbnSSIBBhREsYa6VGrXUyXqZ5VvWxri84lHvdGKQnScd9AOHpiEPxPxi7UW5VNF93qQqbiUpHXJ2m1C0aoYIwEGDEC49NB8Bf17EzVVbuX7FYfv5YKTX+3VS9YuxMJEE4pYbTaxVSqTdXK7IqSvW/i/UR2vkDNfKqb0doYmQTIj/sGwtHRPRL/tFg8h8kKv4TpFuK3mE0JtsxSzk7f7n+9PTIJEDXAiJAwlm7mX+91JspUXRC8h/ShxDvJmJNUYDKmYfnG5Eh0gCESYCRIGPeXc1o7zelGdjbJlgQfgt0EzkmMDV5oYvN47/WoRRPolJOw+58s51dnnjbz8bTgpHcr6Xdm3BJclCkls+SiIpVON41M6Q9RA5xqEsb6arZ883kjTaR5TG87+tDgN4auAFOGFQgSKknJNZG0zvpx3/qRiRrglBoGP2w0Nnf25o38RmWsGLYMamM2bSKX6DmSkUpV+IwnrT4djUkwiBrgVBoEPxlsNJ7t7cxTpeuYVgw+wHQVmDGoa/j3N3OSl0WW/IfjvfUjFzXAKTMM/g1ajWJ3d85SuuboA5wPk3EdmAMacpIZpQwhXJ5K6LtXjZEp/SFqgFNlf3FbtrHRauTl5KxZuubOimG/M+OGjHlgDMiwF8sdZIaTqVSWXFN5JEB487wo+ZdajWJ8Yi71bRHTCqa/AN4GtWwY/AfW+gyWP8tNViozb/W+E3dGpw8QTaBT4GWbfxD8lmwR9IFLHxm8A1oAxiRyDgS/2E8AmZOpr7J0qmxkgh+iBnjj3btH4pPl/GFnsbnZa7YMbrj0oYbBbywATXg1+IdkSIarsoos+ROAj4/4IY5R1ABvMN0jPVi6mT+deTo2XfVapnzRTSsMZnlvCBZsMMv7k8FvIBcgPCX1VSavOjERFk44gUmkhyuLxRSbE3mpc4WKJZf9pWEfGfYOsGD2fwf/K5czuUhlPU9etnKNUAUQCfCmkQbr+Vlv1yZgany8dtHhlsRfIX0keBusZcYY+vng1+CCAqvAq05landy3b0bneBwAg07u9/una1nXU2NyS9KftPdVixxC+wtiVmDuvTqaM9PGWx7NBdWyagaWdcflnUWj+h5ToJIgDfAcPP6g7Wb+bmJbqM+V80Irrh4T8lWMmNJ4hIwbVAHJf74tka5wMANlfLku12geP3Pc5JEApxww83r3G4Xl58+G+9abd7wq8mzW0r6rcHbEucEkzYI318S/PsXRyRcUplZ5VU9afFyfWSaPxAJcGLtn9Vj3F/OaH1V3+3Xp7q17HyN6npl6c9dumVwFZg3aCIKEoZ+cfAPmkAyR5QSLhCfA0uv88lOlkiAE0Zg3MNYX02PNx4W07P/1dzz+mytLC9nyZdc/AZ41+AiMIPRkMgw0q/oug5mgc36GN4sEDuH/kgnWiTACTFs56/fJrUftfPp8qt6c7o+5V2dzcyvgb9n8B7YVYMFYAKshvRHO7s/xQYTYDLhoFKVu1KuB4xUBRAJcNxeBP46aXVlMbvVoe6z3Ql6ac6SLgveQbwns+ugi8CZ/TU9uX5ZZ/en33fwRTBIAJL5zg4sXR2t1aCRAMdAL9vpdv+T5Wz59vfZ+xupvjvVb9ZcM708vwhcM2nJEjdMdgU0J5gwqAkS9v9o7/+I2WD0H0BmDpQyXA0bqeCHSIAjITh40KCxvpoedh5nk9l2fv2tb+udTqNZTOtMX9m5Cv1ZgndIuo64LFnL0KRB3SDXcJTnTwhVHfxWckwlnvl4NVrLICAS4LUYBvzHH2N37wJrGPeXE/+9mb7NtvN67VExLRq1vDbe7xWzlfxs5lyR/Lph1zBdRDYPTAk1DMtBSYc4c2+YNKiLXFgpK52xPd2/P8ny8mG9y8kXCXDIXozbf7Kc7q5sJn6/mzHRzLef7RTdvFtPXjRrZT5Zyeesm84m1E6JtuCyZOcwZsEmgAZQGKTBeSWHe17nsAWE8GQqzc1VRhMo/Anu3SOxvpqejH1RG5/6ttHrWqMosjGXN/N+OZEpnbGU5vuVX8gyXXbpohkLYLOCKTOaQJ1XJ7Re0yltGuwFMFyy0pL5FqN1KBZEAhya/XU6id4P9cKz6Ur5fFGzlsGC4WcdzprsnCpaJJ+BNA1MIpoaBH2NwW6t1xz4HCj+4eUwaPJJkqi/tnc9kSIBDo9BO98otyZTPb9k0rsuezfBW5AWJM6Y2QTQRNYA1RiU9DkHg/7FTt2f6eYa/NoRoB8RDPoArxyKNTIng8Zy6MMkJh9VrVrq0NeWe3qeBn3NGaHzQhdknJWYA6YY7NKqAZm9LPGFD9rlQIVRAqVB36APlEC1/ypnEMD6NR9msf8zwvBRPRQLogY4NGZI96hYSXv9LH3foP+lzCYkO5PQlGTjDDq2w8/h8mHH9uWQPC72g19UQLX/usFLRMJIGBmDv12OkaRXmk2/qPzenweQhKPBoVgasUOxIBLgcN1Da58+LO+02tvPe/nXlUqDDHcvzawjdB5jUoOSfxi0g1IcKokS6IHtYewJdQ16mDmSychN1ARjhprCxmy//6DBiNGwD5F+STPJGOwHRl4WWe49f/HvIyMS4BAZSH+Pr336qP9Bq70197z+VVmrerm06fA16BrY/jqewRCnwDUo7XsYuxjbuJ4BT5OxCbaDVLpSslTVhU2aMYtbC9O8ZDMJpgWTwJigkSBHlh1YKvGTMb2/HMLNUr9b9l0vD8UamVogEuCQDZMAPerx4ObWsy96ZVGz7S72WOYt3OayzKYcGrgysCpL7LnbruHPXb5jZrtVZZ2Us+dV1VeW3KrK6lmeVdavJ2XNUkxbSi3JL7rsspkuSCwYzGJMyjQmDWaP95tOdrBWsEErSPu1S1nLku/tjtahWBAJ8Frsj69L9z4vz9xeff7k+Re98SJt15N90y99rJTV68qzvpOU8FJllWdFzyv6Ven9qqqVY81uWWW1Sr2GazeXWtDrdFMas7TTSUWhVCuystnr23RhmpXpvIk2ZleFLuG2YGZnQOPY/jKK4TZJcWAkVC5ZqQr36e/E34xO6Q+j1dw7FsONLfc/Wc6WL28mqt3sO5/M5ovKnhZu6id5P1PVeOb9zcL3Jhq+uDDtbE9q/bN1VpfQGnBneMHWqjG5bY/+9fusfaGTbfbrtSK3RpmyiXLP51LOuURqu2kRtzaJc7hmMSaAhqAwkQSVGU8N/s2Mf5TKf56uqif87Td7Zvhx/b6OWtQAr9lwuFG6PyxZbX4Nowv/0sJejLr8HeJj4MCJDKt//b8+ud3EOgj+4fdUS+PYndZql87j3Y2tze3G7NwPhXrfVJX+g1L/rsQVly2mRNvFBaQ5M5s0o44wwzpm2pPTU8oq5qRROhQLIgGOzIGx+v2+J7Y6/L+DQX7vZy+jYQP+xXW0bkDVWqPkzJUuXz7ffVbkW7XxaqPTa3yVWfkH0PkEbTeuAhdcNmvmDWEduf2gjB15r3z0h0Ltw3rgN0Q0gU6R4eYabq8mOo+z7/pZrarKMav1J+q5nalkrQTnjHQeaU5mmaEnZnxm/e6X/9mZ2Vq683l/VD4lHiIBTqUXibCEPeBmNjP2NG9aXu9XGlMqxou6j3tPTcuylMpqN/f09fiUNll91Bul9j9EApxqBzbiGOuDWuHbbDuv744VnbyWW+Y25r3y6XjqtDce9bmDj1LpD5EAI2O4DXNtjXSnhfHFsrEMbE+Kz9adu79uTdGbLhJgBP1oiyajGPghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhPDm+x+8b2xxUlIm4AAAAABJRU5ErkJggg=="/>
|
||||||
|
<image id="image1419" width="192" height="152" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAACYCAYAAACyAeadAAAABmJLR0QA/wD/AP+gvaeTAAAAiElEQVR4nO3BMQEAAADCoPVP7WsIoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAPIpwABWLk4FQAAAABJRU5ErkJggg=="/>
|
||||||
|
</defs>
|
||||||
|
<g id="surface1370">
|
||||||
|
<path style="fill:none;stroke-width:12;stroke-linecap:round;stroke-linejoin:miter;stroke:url(#radial0);stroke-miterlimit:4;" d="M -230 78 C -230 86.835938 -237.164062 94 -246 94 " transform="matrix(0.000000000000000061,-1,1,0.000000000000000061,-160,-172)"/>
|
||||||
|
<use xlink:href="#image1393" transform="matrix(1,0,0,1,-168,-16)"/>
|
||||||
|
<use xlink:href="#image1419" transform="matrix(1,0,0,1,-168,-16)"/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 14.476562 1 C 13.429688 3.382812 10.09375 5 8 5 L 8 7 C 10.09375 7 13.429688 8.617188 14.476562 11 L 15 11 L 15 1 Z M 14.476562 1 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 2 5 L 1.996094 7 L 8.257812 7.011719 L 8.265625 5.011719 Z M 2 5 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 7.5 5 C 5.023438 5 3 7.023438 3 9.5 C 3 11.976562 5.023438 14 7.5 14 C 9.976562 14 12 11.976562 12 9.5 C 12 7.023438 9.976562 5 7.5 5 Z M 7.5 7 C 8.902344 7 10 8.097656 10 9.5 C 10 10.902344 8.902344 12 7.5 12 C 6.097656 12 5 10.902344 5 9.5 C 5 8.097656 6.097656 7 7.5 7 Z M 7.5 7 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,12.156863%,19.215687%);fill-opacity:1;" d="M 0 4 L 1 5 L 1 7 L 0 8 Z M 0 4 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.0 KiB |
|
@ -1,14 +0,0 @@
|
||||||
@define-color colorAccent #9aa7c8;
|
|
||||||
@define-color colorPrimary #9aa7c8;
|
|
||||||
|
|
||||||
.header-counters{
|
|
||||||
background: rgba(255,255,255,.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment{
|
|
||||||
background: rgba (255,255,255,.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card{
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
BIN
data/logo128.png
Before Width: | Height: | Size: 5.6 KiB |
|
@ -1,15 +1,17 @@
|
||||||
icon_sizes = ['16', '24', '32', '48', '64', '128']
|
icons_dir = join_paths(get_option('datadir'), 'icons', 'hicolor')
|
||||||
|
scalable_dir = join_paths(icons_dir, 'scalable', 'apps')
|
||||||
|
symbolic_dir = join_paths(icons_dir, 'symbolic', 'apps')
|
||||||
|
|
||||||
foreach i : icon_sizes
|
install_data(
|
||||||
install_data(
|
join_paths('icons', 'color.svg'),
|
||||||
join_paths('icons', i, meson.project_name() + '.svg'),
|
install_dir: scalable_dir,
|
||||||
install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i, 'apps')
|
rename: meson.project_name() + '.svg'
|
||||||
)
|
)
|
||||||
install_data(
|
install_data(
|
||||||
join_paths('icons', i, meson.project_name() + '.svg'),
|
join_paths('icons', 'symbolic.svg'),
|
||||||
install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', i + 'x' + i + '@2', 'apps')
|
install_dir: symbolic_dir,
|
||||||
)
|
rename: meson.project_name() + '-symbolic.svg'
|
||||||
endforeach
|
)
|
||||||
|
|
||||||
install_data(
|
install_data(
|
||||||
meson.project_name() + '.gschema.xml',
|
meson.project_name() + '.gschema.xml',
|
||||||
|
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 246 KiB |
Before Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 244 KiB |
|
@ -0,0 +1,255 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkImage" id="image1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">dialog-warning-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="image2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">mail-attachment-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="image4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">face-smile-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<template class="TootleDialogsCompose" parent="GtkWindow">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="type_hint">dialog</property>
|
||||||
|
<child type="titlebar">
|
||||||
|
<object class="GtkHeaderBar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="has_subtitle">False</property>
|
||||||
|
<property name="show_close_button">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuButton" id="visibility_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="visibility_icon">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="image3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">pan-down-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="post_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="horizontal"/>
|
||||||
|
<class name="linked"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinner" id="spinner">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="box">
|
||||||
|
<property name="width_request">500</property>
|
||||||
|
<property name="height_request">250</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRevealer" id="cw_revealer">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="cw">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="placeholder_text" translatable="yes">Write your warning here</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="width_request">350</property>
|
||||||
|
<property name="height_request">150</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<property name="overlay_scrolling">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="content">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="pixels_below_lines">8</property>
|
||||||
|
<property name="pixels_inside_wrap">8</property>
|
||||||
|
<property name="wrap_mode">word-char</property>
|
||||||
|
<property name="left_margin">8</property>
|
||||||
|
<property name="right_margin">8</property>
|
||||||
|
<property name="top_margin">8</property>
|
||||||
|
<property name="bottom_margin">8</property>
|
||||||
|
<property name="accepts_tab">False</property>
|
||||||
|
<property name="populate_all">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="emoji_button">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="image">image4</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="attach_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="image">image2</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkToggleButton" id="cw_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="image">image1</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="counter">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">250</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<template class="TootleDialogsMainWindow" parent="GtkWindow">
|
||||||
|
<property name="width_request">450</property>
|
||||||
|
<property name="height_request">600</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child type="titlebar">
|
||||||
|
<object class="GtkHeaderBar" id="header">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="show_close_button">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="back_button">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">go-previous-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="compose_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">document-edit-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="title">
|
||||||
|
<object class="GraniteWidgetsModeButton" id="timeline_switcher">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAccountsButton" id="accounts_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="view_stack">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="transition_type">slide-left-right</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="timeline_stack">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="transition_type">slide-left-right</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">0</property>
|
||||||
|
<property name="title" translatable="yes">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,155 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.2 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<template class="TootleViewsBase" parent="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolled">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hscrollbar_policy">never</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkViewport">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="shadow_type">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="view">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="states">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="vhomogeneous">False</property>
|
||||||
|
<property name="transition_type">crossfade</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="status">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin_top">16</property>
|
||||||
|
<property name="margin_bottom">16</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">16</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="width_request">128</property>
|
||||||
|
<property name="height_request">128</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="opacity">0.29999999999999999</property>
|
||||||
|
<property name="pixel_size">128</property>
|
||||||
|
<property name="icon_name">com.github.bleakgrey.tootle-symbolic</property>
|
||||||
|
<property name="icon_size">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="status_stack">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="opacity">0.5</property>
|
||||||
|
<property name="transition_type">crossfade</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="status_message_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">message</property>
|
||||||
|
<property name="title" translatable="yes">page0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinner" id="status_spinner">
|
||||||
|
<property name="height_request">32</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">spinner</property>
|
||||||
|
<property name="title" translatable="yes">page1</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="status_button">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="no_show_all">True</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">status</property>
|
||||||
|
<property name="title" translatable="yes">page1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="content">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">content</property>
|
||||||
|
<property name="title" translatable="yes">page0</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,224 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkGrid" id="wizard">
|
||||||
|
<property name="width_request">350</property>
|
||||||
|
<property name="height_request">400</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin_top">12</property>
|
||||||
|
<property name="margin_bottom">12</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="row_spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">12</property>
|
||||||
|
<property name="margin_right">12</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="next">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_default">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="icon_name">go-next-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="image-button"/>
|
||||||
|
<class name="circular"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="stack">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="transition_type">slide-left-right</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="step1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">12</property>
|
||||||
|
<property name="margin_right">12</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="row_spacing">6</property>
|
||||||
|
<property name="column_spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="step1_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label" translatable="yes">Which Instance?
|
||||||
|
</property>
|
||||||
|
<property name="justify">center</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="h2"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="label" translatable="yes"><a href="https://joinmastodon.org/">What's an instance?</a></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
<property name="justify">right</property>
|
||||||
|
<property name="track_visited_links">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="instance_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="activates_default">True</property>
|
||||||
|
<property name="caps_lock_warning">False</property>
|
||||||
|
<property name="placeholder_text" translatable="yes">instance.domain</property>
|
||||||
|
<property name="input_purpose">url</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="step2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">12</property>
|
||||||
|
<property name="margin_right">12</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="row_spacing">6</property>
|
||||||
|
<property name="column_spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="step2_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label" translatable="yes">Grant Account Access
|
||||||
|
</property>
|
||||||
|
<property name="justify">center</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="h2"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="code_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="secondary_icon_name">edit-paste-symbolic</property>
|
||||||
|
<property name="secondary_icon_tooltip_text" translatable="yes">Paste</property>
|
||||||
|
<property name="placeholder_text" translatable="yes">Paste your authorization code here</property>
|
||||||
|
<property name="input_purpose">url</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="reset">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="label" translatable="yes"><a href="">Try another instance?</a></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
<property name="justify">right</property>
|
||||||
|
<property name="track_visited_links">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">page1</property>
|
||||||
|
<property name="title" translatable="yes">page1</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="pixel_size">128</property>
|
||||||
|
<property name="icon_name">com.github.bleakgrey.tootle</property>
|
||||||
|
<property name="icon_size">6</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,525 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkPopover" id="filter_popover">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<property name="homogeneous">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="filter_all">
|
||||||
|
<property name="label" translatable="yes">Posts</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="filter_replies">
|
||||||
|
<property name="label" translatable="yes">Posts and Replies</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">filter_all</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="filter_media">
|
||||||
|
<property name="label" translatable="yes">Media</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">filter_all</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<object class="GtkMenu" id="options">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">__glade_unnamed_3</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">__glade_unnamed_8</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">__glade_unnamed_9</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<object class="GtkGrid" id="grid">
|
||||||
|
<property name="width_request">400</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="row_spacing">8</property>
|
||||||
|
<property name="column_spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="note">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
<property name="width_chars">25</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="label_item">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="height_request">40</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="spacing">32</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="posts_tab">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="posts_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label">0 Posts</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="following_tab">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">posts_tab</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="following_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label">0 Follows</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRadioButton" id="followers_tab">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="group">posts_tab</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="followers_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label">0 Followers</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="linked"/>
|
||||||
|
<class name="horizontal"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuButton" id="filter_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="popover">filter_popover</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">view-more-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="label_item">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="width_request">100</property>
|
||||||
|
<property name="height_request">80</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAvatar" id="avatar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="size">128</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="name">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="handle">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="relationship">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="actions">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="follow_button">
|
||||||
|
<property name="label" translatable="yes">Follow</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuButton" id="options_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<property name="popup">options</property>
|
||||||
|
<property name="use_popover">False</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="horizontal"/>
|
||||||
|
<class name="linked"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,240 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkPopover" id="popover">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="stack">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hhomogeneous">False</property>
|
||||||
|
<property name="vhomogeneous">False</property>
|
||||||
|
<property name="transition_type">slide-left-right</property>
|
||||||
|
<property name="interpolate_size">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="border_width">4</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_accounts">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes"> </property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
<property name="menu_name">accounts</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_favs">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Favorites</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_direct">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Conversations</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_watchlist">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Watchlist</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_refresh">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Refresh</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">7</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_search">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Search</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">8</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton" id="item_prefs">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Preferences</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">9</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">menu</property>
|
||||||
|
<property name="title" translatable="yes">page0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="width_request">400</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="border_width">4</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkModelButton">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="text" translatable="yes">Accounts</property>
|
||||||
|
<property name="menu_name">menu</property>
|
||||||
|
<property name="inverted">True</property>
|
||||||
|
<property name="centered">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<property name="max_content_height">300</property>
|
||||||
|
<property name="propagate_natural_height">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkViewport">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="account_list">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">accounts</property>
|
||||||
|
<property name="title" translatable="yes">page1</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<template class="TootleWidgetsAccountsButton" parent="GtkMenuButton">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="popover">popover</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkOverlay">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAvatar" id="avatar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="size">24</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pass_through">True</property>
|
||||||
|
<property name="index">-1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="overlay">
|
||||||
|
<object class="GtkSpinner" id="spinner">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="active">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pass_through">True</property>
|
||||||
|
<property name="index">-1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<template class="TootleWidgetsAccountsButtonItem" parent="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<property name="column_spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAvatar" id="avatar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="size">48</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="remove">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">edit-delete-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="circular"/>
|
||||||
|
<class name="image-button"/>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">3</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="name">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label" translatable="yes">Name</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="single_line_mode">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="handle">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label" translatable="yes">Handle</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="single_line_mode">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="profile">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Open profile</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">avatar-default-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="circular"/>
|
||||||
|
<class name="image-button"/>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">2</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,293 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkImage" id="image2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">emblem-favorite-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="image3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">mail-replied-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="reblog_icon">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="icon_name">media-playlist-repeat-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<template class="TootleWidgetsStatus" parent="GtkEventBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator" id="separator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="grid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">8</property>
|
||||||
|
<property name="margin_right">8</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="column_spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="header_icon">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="margin_bottom">8</property>
|
||||||
|
<property name="icon_name">applications-development-symbolic</property>
|
||||||
|
<property name="icon_size">1</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="actions">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkToggleButton" id="reblog_button">
|
||||||
|
<property name="label" translatable="yes">0</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="image">reblog_icon</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkToggleButton" id="favorite_button">
|
||||||
|
<property name="label" translatable="yes">0</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="image">image2</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="reply_button">
|
||||||
|
<property name="label" translatable="yes">0</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="image">image3</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">4</property>
|
||||||
|
<property name="width">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="handle_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label">Handle</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="date_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="opacity">0.5</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">Yesterday</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="pin_indicator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="icon_name">view-pin-symbolic</property>
|
||||||
|
<property name="icon_size">1</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRevealer" id="revealer">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="reveal_child">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="revealer_content">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="wrap_mode">word-char</property>
|
||||||
|
<property name="width_chars">15</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAttachmentBox" id="attachments">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin_top">8</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
<property name="width">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsAvatar" id="avatar">
|
||||||
|
<property name="width_request">48</property>
|
||||||
|
<property name="height_request">48</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="visible">true</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="height">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="header_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="TootleWidgetsRichLabel" id="content">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="wrap_mode">word-char</property>
|
||||||
|
<property name="width_chars">15</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="width">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,4 @@
|
||||||
|
meson build --prefix=/usr
|
||||||
|
cd build
|
||||||
|
sudo ninja install
|
||||||
|
com.github.bleakgrey.tootle
|
50
meson.build
|
@ -3,6 +3,9 @@ project('com.github.bleakgrey.tootle', 'vala', 'c')
|
||||||
gnome = import('gnome')
|
gnome = import('gnome')
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
|
|
||||||
|
#add_project_arguments(['--disable-warnings', '-g', '-X', '-rdynamic'], language: 'vala')
|
||||||
|
add_project_arguments(['-g', '-rdynamic', '-export-dynamic'], language: 'c')
|
||||||
|
|
||||||
add_global_arguments([
|
add_global_arguments([
|
||||||
'-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name())
|
'-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name())
|
||||||
],
|
],
|
||||||
|
@ -10,7 +13,7 @@ add_global_arguments([
|
||||||
)
|
)
|
||||||
|
|
||||||
asresources = gnome.compile_resources(
|
asresources = gnome.compile_resources(
|
||||||
'as-resources', 'data/' + meson.project_name() + '.gresource.xml',
|
'as-resources', 'data/gresource.xml',
|
||||||
source_dir: 'data',
|
source_dir: 'data',
|
||||||
c_name: 'as'
|
c_name: 'as'
|
||||||
)
|
)
|
||||||
|
@ -18,42 +21,48 @@ asresources = gnome.compile_resources(
|
||||||
executable(
|
executable(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
asresources,
|
asresources,
|
||||||
|
|
||||||
|
'src/Stacktrace.vala', #TODO: move into a separate lib
|
||||||
|
|
||||||
|
'src/Build.vala',
|
||||||
'src/Application.vala',
|
'src/Application.vala',
|
||||||
'src/Desktop.vala',
|
'src/Desktop.vala',
|
||||||
'src/Drawing.vala',
|
'src/Drawing.vala',
|
||||||
'src/Html.vala',
|
'src/Html.vala',
|
||||||
'src/Settings.vala',
|
'src/Utils.vala',
|
||||||
'src/Accounts.vala',
|
'src/Request.vala',
|
||||||
'src/ImageCache.vala',
|
|
||||||
'src/Network.vala',
|
|
||||||
'src/Watchlist.vala',
|
|
||||||
'src/Notificator.vala',
|
|
||||||
'src/InstanceAccount.vala',
|
'src/InstanceAccount.vala',
|
||||||
|
'src/Services/Streams.vala',
|
||||||
|
'src/Services/Settings.vala',
|
||||||
|
'src/Services/Accounts.vala',
|
||||||
|
'src/Services/IAccountListener.vala',
|
||||||
|
'src/Services/IStreamListener.vala',
|
||||||
|
'src/Services/Cache.vala',
|
||||||
|
'src/Services/Network.vala',
|
||||||
'src/API/Account.vala',
|
'src/API/Account.vala',
|
||||||
'src/API/Relationship.vala',
|
'src/API/Relationship.vala',
|
||||||
'src/API/Mention.vala',
|
'src/API/Mention.vala',
|
||||||
'src/API/Tag.vala',
|
'src/API/Tag.vala',
|
||||||
'src/API/Status.vala',
|
'src/API/Status.vala',
|
||||||
'src/API/StatusVisibility.vala',
|
'src/API/Visibility.vala',
|
||||||
'src/API/Notification.vala',
|
'src/API/Notification.vala',
|
||||||
'src/API/NotificationType.vala',
|
'src/API/NotificationType.vala',
|
||||||
'src/API/Attachment.vala',
|
'src/API/Attachment.vala',
|
||||||
|
'src/Widgets/Avatar.vala',
|
||||||
|
'src/Widgets/AccountsButton.vala',
|
||||||
'src/Widgets/AlignedLabel.vala',
|
'src/Widgets/AlignedLabel.vala',
|
||||||
'src/Widgets/RichLabel.vala',
|
'src/Widgets/RichLabel.vala',
|
||||||
'src/Widgets/ImageToggleButton.vala',
|
|
||||||
'src/Widgets/AccountsButton.vala',
|
|
||||||
'src/Widgets/Status.vala',
|
'src/Widgets/Status.vala',
|
||||||
'src/Widgets/Account.vala',
|
|
||||||
'src/Widgets/Notification.vala',
|
'src/Widgets/Notification.vala',
|
||||||
'src/Widgets/ImageAttachment.vala',
|
'src/Widgets/VisibilityPopover.vala',
|
||||||
'src/Widgets/AttachmentGrid.vala',
|
'src/Widgets/Attachment/Box.vala',
|
||||||
|
'src/Widgets/Attachment/Item.vala',
|
||||||
'src/Dialogs/ISavedWindow.vala',
|
'src/Dialogs/ISavedWindow.vala',
|
||||||
'src/Dialogs/MainWindow.vala',
|
'src/Dialogs/MainWindow.vala',
|
||||||
'src/Dialogs/NewAccount.vala',
|
|
||||||
'src/Dialogs/Compose.vala',
|
'src/Dialogs/Compose.vala',
|
||||||
'src/Dialogs/Preferences.vala',
|
'src/Dialogs/Preferences.vala',
|
||||||
'src/Dialogs/WatchlistEditor.vala',
|
'src/Views/Base.vala',
|
||||||
'src/Views/Abstract.vala',
|
'src/Views/NewAccount.vala',
|
||||||
'src/Views/Timeline.vala',
|
'src/Views/Timeline.vala',
|
||||||
'src/Views/Home.vala',
|
'src/Views/Home.vala',
|
||||||
'src/Views/Local.vala',
|
'src/Views/Local.vala',
|
||||||
|
@ -62,8 +71,6 @@ executable(
|
||||||
'src/Views/Direct.vala',
|
'src/Views/Direct.vala',
|
||||||
'src/Views/ExpandedStatus.vala',
|
'src/Views/ExpandedStatus.vala',
|
||||||
'src/Views/Profile.vala',
|
'src/Views/Profile.vala',
|
||||||
'src/Views/Followers.vala',
|
|
||||||
'src/Views/Following.vala',
|
|
||||||
'src/Views/Favorites.vala',
|
'src/Views/Favorites.vala',
|
||||||
'src/Views/Search.vala',
|
'src/Views/Search.vala',
|
||||||
'src/Views/Hashtag.vala',
|
'src/Views/Hashtag.vala',
|
||||||
|
@ -73,9 +80,12 @@ executable(
|
||||||
dependency('gee-0.8', version: '>=0.8.5'),
|
dependency('gee-0.8', version: '>=0.8.5'),
|
||||||
dependency('granite', version: '>=5.2.0'),
|
dependency('granite', version: '>=5.2.0'),
|
||||||
dependency('json-glib-1.0'),
|
dependency('json-glib-1.0'),
|
||||||
dependency('libsoup-2.4')
|
dependency('libsoup-2.4'),
|
||||||
|
|
||||||
|
meson.get_compiler('vala').find_library('linux', required: true), #Required by Stacktrace.vala
|
||||||
],
|
],
|
||||||
install: true
|
install: true,
|
||||||
|
link_args: '-export-dynamic'
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('data')
|
subdir('data')
|
||||||
|
|
74
po/POTFILES
|
@ -1,47 +1,53 @@
|
||||||
data/com.github.bleakgrey.tootle.desktop.in
|
data/com.github.bleakgrey.tootle.desktop.in
|
||||||
data/com.github.bleakgrey.tootle.appdata.xml.in
|
data/com.github.bleakgrey.tootle.appdata.xml.in
|
||||||
src/Accounts.vala
|
src/Build.vala
|
||||||
src/Application.vala
|
src/Application.vala
|
||||||
src/Desktop.vala
|
src/Desktop.vala
|
||||||
|
src/Drawing.vala
|
||||||
src/Html.vala
|
src/Html.vala
|
||||||
src/ImageCache.vala
|
src/Utils.vala
|
||||||
|
src/Request.vala
|
||||||
src/InstanceAccount.vala
|
src/InstanceAccount.vala
|
||||||
src/MainWindow.vala
|
src/Services/Notificator.vala
|
||||||
src/Network.vala
|
src/Services/Settings.vala
|
||||||
src/Watchlist.vala
|
src/Services/Accounts.vala
|
||||||
src/Notificator.vala
|
src/Services/IAccountListener.vala
|
||||||
src/Settings.vala
|
src/Services/Cache.vala
|
||||||
|
src/Services/Network.vala
|
||||||
|
src/Services/Watchlist.vala
|
||||||
src/API/Account.vala
|
src/API/Account.vala
|
||||||
src/API/Attachment.vala
|
src/API/Relationship.vala
|
||||||
src/API/Mention.vala
|
src/API/Mention.vala
|
||||||
|
src/API/Tag.vala
|
||||||
|
src/API/Status.vala
|
||||||
|
src/API/Visibility.vala
|
||||||
src/API/Notification.vala
|
src/API/Notification.vala
|
||||||
src/API/NotificationType.vala
|
src/API/NotificationType.vala
|
||||||
src/API/Relationship.vala
|
src/API/Attachment.vala
|
||||||
src/API/Status.vala
|
src/Widgets/Avatar.vala
|
||||||
src/API/StatusVisibility.vala
|
|
||||||
src/Widgets/AccountsButton.vala
|
src/Widgets/AccountsButton.vala
|
||||||
src/Widgets/AccountWidget.vala
|
|
||||||
src/Widgets/AlignedLabel.vala
|
src/Widgets/AlignedLabel.vala
|
||||||
src/Widgets/AttachmentBox.vala
|
|
||||||
src/Widgets/AttachmentWidget.vala
|
|
||||||
src/Widgets/ImageToggleButton.vala
|
|
||||||
src/Widgets/NotificationWidget.vala
|
|
||||||
src/Widgets/RichLabel.vala
|
src/Widgets/RichLabel.vala
|
||||||
src/Widgets/StatusWidget.vala
|
src/Widgets/Status.vala
|
||||||
src/Dialogs/NewAccountDialog.vala
|
src/Widgets/Notification.vala
|
||||||
src/Dialogs/PostDialog.vala
|
src/Widgets/VisibilityPopover.vala
|
||||||
src/Dialogs/SettingsDialog.vala
|
src/Widgets/Attachment/Box.vala
|
||||||
src/Dialogs/WatchlistDialog.vala
|
src/Widgets/Attachment/Item.vala
|
||||||
src/Views/AbstractView.vala
|
src/Dialogs/ISavedWindow.vala
|
||||||
src/Views/AccountView.vala
|
src/Dialogs/MainWindow.vala
|
||||||
src/Views/FavoritesView.vala
|
src/Dialogs/Compose.vala
|
||||||
src/Views/DirectView.vala
|
src/Dialogs/Preferences.vala
|
||||||
src/Views/FederatedView.vala
|
src/Dialogs/WatchlistEditor.vala
|
||||||
src/Views/FollowersView.vala
|
src/Views/Base.vala
|
||||||
src/Views/FollowingView.vala
|
src/Views/NewAccount.vala
|
||||||
src/Views/HomeView.vala
|
src/Views/Timeline.vala
|
||||||
src/Views/LocalView.vala
|
src/Views/Home.vala
|
||||||
src/Views/NotificationsView.vala
|
src/Views/Local.vala
|
||||||
src/Views/SearchView.vala
|
src/Views/Federated.vala
|
||||||
src/Views/StatusView.vala
|
src/Views/Notifications.vala
|
||||||
src/Views/TimelineView.vala
|
src/Views/Direct.vala
|
||||||
|
src/Views/ExpandedStatus.vala
|
||||||
|
src/Views/Profile.vala
|
||||||
|
src/Views/Favorites.vala
|
||||||
|
src/Views/Search.vala
|
||||||
|
src/Views/Hashtag.vala
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -18,7 +18,7 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||||
msgid "Tootle"
|
msgid "Tootle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -42,366 +42,300 @@ msgstr ""
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||||
"microblogging network with real-time notifications and multiple accounts "
|
"microblogging network with real-time notifications and support for multiple "
|
||||||
"support."
|
"accounts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||||
msgid ""
|
msgid ""
|
||||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||||
"free, independent and popular alternative to the centralized social networks."
|
"free, independent, and popular alternative to the centralized social "
|
||||||
|
"networks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||||
msgid ""
|
msgid ""
|
||||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||||
"accounts, the content they produce, and the content they are subscribed. "
|
"accounts, the content they produce, and the content to which they are "
|
||||||
"Every user can follow each other and share their posts regardless of their "
|
"subscribed. Every user can follow each other and share their posts "
|
||||||
"server."
|
"regardless of their server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:26
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:26
|
||||||
msgid "bleak_grey"
|
msgid "bleak_grey"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
msgid "Added Watchlist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||||
|
msgid "Added Redraft support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||||
|
msgid "Added Pinning support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||||
|
msgid "Added Simplified Chinese and German translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||||
|
msgid "Added --hidden Start Flag"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||||
|
msgid "Added Shortcuts and Back mouse button support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||||
|
msgid "Changed Notifications screen behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||||
|
msgid "Fixed minor bugs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||||
|
msgid "Added Russian, French and Polish translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||||
|
msgid "Added Direct timeline"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||||
|
msgid "Added support for custom character limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||||
|
msgid "Added support for streaming all timelines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||||
|
msgid "Added tooltips for image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||||
|
msgid "Added remove action for attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||||
|
msgid "Changed behavior for mentioning users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||||
|
msgid "Changed behavior for missing image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||||
|
msgid "Initial release"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Desktop.vala:10
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Desktop.vala:46
|
#: src/Desktop.vala:47
|
||||||
msgid "Media downloaded"
|
msgid "Media downloaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/MainWindow.vala:48
|
#: src/Services/Accounts.vala:31
|
||||||
msgid "Back"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
|
||||||
msgid "Toot"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "TLS Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "Can't ensure secure connection: "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Network.vala:66
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: %s"
|
msgid ""
|
||||||
|
"This instance has invalidated this session. Please sign in again.\n"
|
||||||
|
"\n"
|
||||||
|
"%s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:50
|
#: src/Services/Network.vala:86
|
||||||
#, c-format
|
msgid "Network Error"
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:52
|
#: src/API/Visibility.vala:36
|
||||||
#, c-format
|
msgid "Unlisted"
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:54
|
#: src/API/Visibility.vala:38
|
||||||
#, c-format
|
msgid "Followers-only"
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:40
|
||||||
|
msgid "Direct"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:42
|
||||||
|
msgid "Public"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:49
|
||||||
|
msgid "Don't post to public timelines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:51
|
||||||
|
msgid "Post to followers only"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:53
|
||||||
|
msgid "Post to mentioned users only"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:55
|
||||||
|
msgid "Post to public timelines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:56
|
#: src/API/NotificationType.vala:56
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:58
|
#: src/API/NotificationType.vala:58
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||||
|
"span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:60
|
#: src/API/NotificationType.vala:60
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:174
|
#: src/API/NotificationType.vala:62
|
||||||
msgid "Boosted!"
|
#, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||||
|
"span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:176
|
#: src/API/NotificationType.vala:64
|
||||||
msgid "Removed boost"
|
#, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:189
|
#: src/API/NotificationType.vala:66
|
||||||
msgid "Favorited!"
|
#, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||||
|
"span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:191
|
#: src/API/NotificationType.vala:68
|
||||||
msgid "Removed from favorites"
|
#, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:204
|
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||||
msgid "Muted!"
|
#: src/Widgets/Account.vala:28
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/Status.vala:206
|
|
||||||
msgid "Conversation unmuted"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/Status.vala:219
|
|
||||||
msgid "Pinned!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/Status.vala:221
|
|
||||||
msgid "Unpinned from profile"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/Status.vala:231
|
|
||||||
msgid "Poof!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:40
|
|
||||||
msgid "Post to public timelines"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:42
|
|
||||||
msgid "Don't post to public timelines"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:44
|
|
||||||
msgid "Post to followers only"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:46
|
|
||||||
msgid "Post to mentioned users only"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:67
|
|
||||||
msgid "Refresh"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:71
|
|
||||||
msgid "Favorites"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
|
||||||
msgid "Direct Messages"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
|
||||||
msgid "Search"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:83
|
|
||||||
msgid "Watchlist"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
|
||||||
msgid "Settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:142
|
|
||||||
msgid "<b>New Account</b>"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:143
|
|
||||||
msgid "Click to add"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
|
||||||
#: src/Widgets/StatusWidget.vala:289
|
|
||||||
msgid "Open in Browser"
|
msgid "Open in Browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
#: src/Widgets/AccountsButton.vala:62
|
||||||
#: src/Widgets/StatusWidget.vala:291
|
msgid "Refresh"
|
||||||
msgid "Copy Link"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:41
|
#: src/Widgets/AccountsButton.vala:67
|
||||||
msgid "Select media files to add"
|
msgid "Favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:44
|
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||||
msgid "_Cancel"
|
msgid "Direct Messages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:46
|
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||||
msgid "_Open"
|
msgid "Search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:67
|
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||||
#, c-format
|
msgid "Watchlist"
|
||||||
msgid "Click to open %s media"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:84
|
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||||
msgid "Uploading..."
|
msgid "Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
#: src/Widgets/Status.vala:52
|
||||||
msgid "File read error"
|
msgid "[ Show more ]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
#: src/Widgets/Status.vala:120
|
||||||
#, c-format
|
|
||||||
msgid "Can't read file %s: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:124
|
|
||||||
msgid "Remove"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:134
|
|
||||||
msgid "Download"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:20
|
|
||||||
msgid "Unknown Notification"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:25
|
|
||||||
msgid "Dismiss"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:64
|
|
||||||
msgid "Accept"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:66
|
|
||||||
msgid "Reject"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:84
|
|
||||||
msgid "Boost"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:91
|
|
||||||
msgid "Favorite"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:98
|
|
||||||
msgid "Reply"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:136
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:151
|
|
||||||
msgid "Toggle content"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:165
|
|
||||||
msgid "[ This post contains sensitive content ]"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:234
|
|
||||||
msgid "This post can't be boosted"
|
msgid "This post can't be boosted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||||
msgid "Unmute Conversation"
|
msgid "Copy Link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
#: src/Widgets/Status.vala:210
|
||||||
msgid "Mute Conversation"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:293
|
|
||||||
msgid "Copy Text"
|
msgid "Copy Text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
#: src/Widgets/Status.vala:217
|
||||||
msgid "Unpin from Profile"
|
msgid "Unpin from Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
#: src/Widgets/Status.vala:217
|
||||||
msgid "Pin on Profile"
|
msgid "Pin on Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:304
|
#: src/Widgets/Status.vala:221
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||||
msgid "Redraft"
|
msgid "Redraft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:27
|
#: src/Widgets/Attachment/Box.vala:28
|
||||||
msgid "New Account"
|
msgid "Select media files to add"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:38
|
#: src/Widgets/Attachment/Box.vala:31
|
||||||
msgid "What's an instance?"
|
msgid "_Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:42
|
#: src/Widgets/Attachment/Box.vala:33
|
||||||
msgid "Code:"
|
msgid "_Open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:46
|
#: src/Dialogs/MainWindow.vala:49
|
||||||
msgid "Paste your instance authorization code here"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:49
|
#: src/Dialogs/MainWindow.vala:58
|
||||||
msgid "Add Account"
|
msgid "Toot"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:60
|
#: src/Dialogs/MainWindow.vala:63
|
||||||
msgid "Instance:"
|
msgid "Toggle content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:102
|
#: src/Dialogs/Compose.vala:69
|
||||||
msgid "Please paste valid instance authorization code"
|
msgid "Post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:110
|
#: src/Dialogs/Preferences.vala:36
|
||||||
msgid "Network Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:45
|
|
||||||
msgid "Post Visibility"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:52
|
|
||||||
msgid "Add Media"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:61
|
|
||||||
msgid "Spoiler Warning"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:68
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:77
|
|
||||||
msgid "Toot!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:85
|
|
||||||
msgid "Write your warning here"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:37
|
|
||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:38
|
#: src/Dialogs/Preferences.vala:37
|
||||||
msgid "Dark theme:"
|
msgid "Dark theme:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:41
|
#: src/Dialogs/Preferences.vala:40
|
||||||
msgid "Timelines"
|
msgid "Timelines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:42
|
#: src/Dialogs/Preferences.vala:41
|
||||||
msgid "Real-time updates:"
|
msgid "Real-time updates:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:44
|
#: src/Dialogs/Preferences.vala:43
|
||||||
msgid "Update public timelines:"
|
msgid "Update public timelines:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -409,140 +343,115 @@ msgstr ""
|
||||||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||||
#. grid.attach (cache_size, 1, i++);
|
#. grid.attach (cache_size, 1, i++);
|
||||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:56
|
#: src/Dialogs/Preferences.vala:55
|
||||||
msgid "Display notifications:"
|
msgid "Display notifications:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:58
|
#: src/Dialogs/Preferences.vala:57
|
||||||
msgid "Always receive notifications:"
|
msgid "Always receive notifications:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:64
|
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||||
msgid "_Close"
|
msgid "_Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:20
|
#: src/Dialogs/WatchlistEditor.vala:20
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots from this user appear in your Home timeline."
|
"You'll be notified when toots from this user appear in your Home timeline."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:21
|
#: src/Dialogs/WatchlistEditor.vala:21
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots with this hashtag appear in any public "
|
"You'll be notified when toots with this hashtag appear in any public "
|
||||||
"timelines."
|
"timelines."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:137
|
#: src/Dialogs/WatchlistEditor.vala:108
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||||
msgid "Hashtags"
|
msgid "Hashtags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:148
|
#: src/Dialogs/WatchlistEditor.vala:122
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AbstractView.vala:59
|
#: src/Views/Base.vala:6
|
||||||
msgid "Nothing to see here"
|
msgid "Nothing to see here"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:79
|
#: src/Views/NewAccount.vala:91
|
||||||
msgid "Edit Profile"
|
msgid "Instance URL is invalid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:80
|
#: src/Views/NewAccount.vala:133
|
||||||
msgid "Mention"
|
msgid "Please paste a valid authorization code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:81
|
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||||
msgid "Report"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
|
||||||
msgid "Block"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:95
|
|
||||||
msgid "More Actions"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:115
|
|
||||||
msgid "Toots"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:116
|
|
||||||
msgid "Follows"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:120
|
|
||||||
msgid "Followers"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:155
|
|
||||||
msgid "Unfollow"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:159
|
|
||||||
msgid "Follow"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:166
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:167
|
|
||||||
msgid "Unmute"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:228
|
|
||||||
msgid "Sent follow request"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:230
|
|
||||||
msgid "Blocked"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:232
|
|
||||||
msgid "Follows you"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:234
|
|
||||||
msgid "Blocking this instance"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:269
|
|
||||||
msgid "User not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/FederatedView.vala:12
|
|
||||||
msgid "Federated Timeline"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/LocalView.vala:12
|
#: src/Views/Local.vala:12
|
||||||
msgid "Local Timeline"
|
msgid "Local Timeline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:82
|
#: src/Views/Federated.vala:12
|
||||||
|
msgid "Federated Timeline"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:61
|
||||||
|
#, c-format
|
||||||
|
msgid "%s Posts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:67
|
||||||
|
#, c-format
|
||||||
|
msgid "%s Follows"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:73
|
||||||
|
#, c-format
|
||||||
|
msgid "%s Followers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:109
|
||||||
|
msgid "Sent follow request"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:111
|
||||||
|
msgid "Mutually follows you"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:113
|
||||||
|
msgid "Follows you"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:124
|
||||||
|
msgid "Follow back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:126
|
||||||
|
msgid "Unfollow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:128
|
||||||
|
msgid "Follow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Search.vala:82
|
||||||
msgid "Accounts"
|
msgid "Accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:91
|
#: src/Views/Search.vala:91
|
||||||
msgid "Statuses"
|
msgid "Statuses"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
896
po/de_DE.po
808
po/fr_FR.po
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||||
"PO-Revision-Date: 2018-06-17 10:07+0200\n"
|
"PO-Revision-Date: 2018-06-17 10:07+0200\n"
|
||||||
"Last-Translator: Guillaume\n"
|
"Last-Translator: Guillaume\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
|
@ -20,7 +20,7 @@ msgstr ""
|
||||||
"X-Poedit-SourceCharset: UTF-8\n"
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||||
msgid "Tootle"
|
msgid "Tootle"
|
||||||
msgstr "Tootle"
|
msgstr "Tootle"
|
||||||
|
|
||||||
|
@ -42,30 +42,34 @@ msgid "Lightning fast client for Mastodon"
|
||||||
msgstr "Client léger et rapide pour Mastodon"
|
msgstr "Client léger et rapide pour Mastodon"
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||||
"microblogging network with real-time notifications and multiple accounts "
|
"microblogging network with real-time notifications and support for multiple "
|
||||||
"support."
|
"accounts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tootle est un client pour le plus grand réseau mondial de microblogging "
|
"Tootle est un client pour le plus grand réseau mondial de microblogging "
|
||||||
"décentralisé, libre et open-source, avec un support multicomptes et la "
|
"décentralisé, libre et open-source, avec un support multicomptes et la "
|
||||||
"gestion des notifications instantanées."
|
"gestion des notifications instantanées."
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||||
"free, independent and popular alternative to the centralized social networks."
|
"free, independent, and popular alternative to the centralized social "
|
||||||
|
"networks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Mastodon est conçu avec amour dans un objectif de puissance et de vitesse, "
|
"Mastodon est conçu avec amour dans un objectif de puissance et de vitesse, "
|
||||||
"en faisant une alternative libre, indépendante et populaire aux réseaux "
|
"en faisant une alternative libre, indépendante et populaire aux réseaux "
|
||||||
"sociaux centralisés."
|
"sociaux centralisés."
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||||
"accounts, the content they produce, and the content they are subscribed. "
|
"accounts, the content they produce, and the content to which they are "
|
||||||
"Every user can follow each other and share their posts regardless of their "
|
"subscribed. Every user can follow each other and share their posts "
|
||||||
"server."
|
"regardless of their server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"N'importe qui peut faire tourner un serveur Mastodon. Chaque serveur héberge "
|
"N'importe qui peut faire tourner un serveur Mastodon. Chaque serveur héberge "
|
||||||
"des comptes utilisateurs individuels, le contenu qu'ils produisent et le "
|
"des comptes utilisateurs individuels, le contenu qu'ils produisent et le "
|
||||||
|
@ -76,348 +80,282 @@ msgstr ""
|
||||||
msgid "bleak_grey"
|
msgid "bleak_grey"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
#, fuzzy
|
||||||
|
msgid "Added Watchlist"
|
||||||
|
msgstr "Liste de veille"
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||||
|
msgid "Added Redraft support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||||
|
msgid "Added Pinning support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||||
|
msgid "Added Simplified Chinese and German translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||||
|
msgid "Added --hidden Start Flag"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||||
|
msgid "Added Shortcuts and Back mouse button support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||||
|
msgid "Changed Notifications screen behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||||
|
msgid "Fixed minor bugs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||||
|
msgid "Added Russian, French and Polish translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Added Direct timeline"
|
||||||
|
msgstr "Mettre à jour le fil public:"
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||||
|
msgid "Added support for custom character limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||||
|
msgid "Added support for streaming all timelines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||||
|
msgid "Added tooltips for image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||||
|
msgid "Added remove action for attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||||
|
msgid "Changed behavior for mentioning users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||||
|
msgid "Changed behavior for missing image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||||
|
msgid "Initial release"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Desktop.vala:10
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Erreur"
|
msgstr "Erreur"
|
||||||
|
|
||||||
#: src/Desktop.vala:46
|
#: src/Desktop.vala:47
|
||||||
msgid "Media downloaded"
|
msgid "Media downloaded"
|
||||||
msgstr "Médias téléchargés"
|
msgstr "Médias téléchargés"
|
||||||
|
|
||||||
#: src/MainWindow.vala:48
|
#: src/Services/Accounts.vala:31
|
||||||
msgid "Back"
|
|
||||||
msgstr "Retour"
|
|
||||||
|
|
||||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
|
||||||
msgid "Toot"
|
|
||||||
msgstr "Écrire un message"
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "TLS Error"
|
|
||||||
msgstr "Erreur TLS"
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "Can't ensure secure connection: "
|
|
||||||
msgstr "Impossible d'assurer une connexion sécurisée:"
|
|
||||||
|
|
||||||
#: src/Network.vala:66
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: %s"
|
msgid ""
|
||||||
msgstr "Erreur: %s"
|
"This instance has invalidated this session. Please sign in again.\n"
|
||||||
|
"\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:50
|
#: src/Services/Network.vala:86
|
||||||
#, c-format
|
msgid "Network Error"
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
msgstr "Erreur réseau"
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous a mentionné"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:52
|
#: src/API/Visibility.vala:36
|
||||||
#, c-format
|
msgid "Unlisted"
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
msgstr ""
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé votre pouet"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:54
|
#: src/API/Visibility.vala:38
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> a ajouté votre pouet à ses favoris"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:56
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> vous suit désormais"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:58
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> demande à vous suivre"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:60
|
|
||||||
#, fuzzy, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> a publié un pouet"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:174
|
|
||||||
msgid "Boosted!"
|
|
||||||
msgstr "Partagé!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:176
|
|
||||||
msgid "Removed boost"
|
|
||||||
msgstr "Partage annulé"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:189
|
|
||||||
msgid "Favorited!"
|
|
||||||
msgstr "Ajouté aux favoris!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:191
|
|
||||||
msgid "Removed from favorites"
|
|
||||||
msgstr "Supprimé des favoris"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:204
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Muted!"
|
msgid "Followers-only"
|
||||||
msgstr "Masqué!"
|
msgstr "Abonnés"
|
||||||
|
|
||||||
#: src/API/Status.vala:206
|
#: src/API/Visibility.vala:40
|
||||||
msgid "Conversation unmuted"
|
msgid "Direct"
|
||||||
msgstr "Discussion visible"
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:219
|
#: src/API/Visibility.vala:42
|
||||||
msgid "Pinned!"
|
msgid "Public"
|
||||||
msgstr "Épinglé!"
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/Status.vala:221
|
#: src/API/Visibility.vala:49
|
||||||
#, fuzzy
|
|
||||||
msgid "Unpinned from profile"
|
|
||||||
msgstr "Désépinglé du profil"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:231
|
|
||||||
msgid "Poof!"
|
|
||||||
msgstr "Poof!"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:40
|
|
||||||
msgid "Post to public timelines"
|
|
||||||
msgstr "Afficher dans le fil public"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:42
|
|
||||||
msgid "Don't post to public timelines"
|
msgid "Don't post to public timelines"
|
||||||
msgstr "Ne pas afficher dans le fil public"
|
msgstr "Ne pas afficher dans le fil public"
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:44
|
#: src/API/Visibility.vala:51
|
||||||
msgid "Post to followers only"
|
msgid "Post to followers only"
|
||||||
msgstr "Afficher seulement pour les abonnés"
|
msgstr "Afficher seulement pour les abonnés"
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:46
|
#: src/API/Visibility.vala:53
|
||||||
msgid "Post to mentioned users only"
|
msgid "Post to mentioned users only"
|
||||||
msgstr "N'envoyer qu'aux personnes mentionnées"
|
msgstr "N'envoyer qu'aux personnes mentionnées"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:67
|
#: src/API/Visibility.vala:55
|
||||||
msgid "Refresh"
|
msgid "Post to public timelines"
|
||||||
msgstr "Actualiser"
|
msgstr "Afficher dans le fil public"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:71
|
#: src/API/NotificationType.vala:56
|
||||||
msgid "Favorites"
|
#, fuzzy, c-format
|
||||||
msgstr "Favoris"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> vous a mentionné"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
#: src/API/NotificationType.vala:58
|
||||||
msgid "Direct Messages"
|
#, fuzzy, c-format
|
||||||
msgstr "Messages directs"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé votre pouet"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
#: src/API/NotificationType.vala:60
|
||||||
msgid "Search"
|
#, fuzzy, c-format
|
||||||
msgstr "Chercher"
|
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:83
|
#: src/API/NotificationType.vala:62
|
||||||
msgid "Watchlist"
|
#, fuzzy, c-format
|
||||||
msgstr "Liste de veille"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> a ajouté votre pouet à ses favoris"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
#: src/API/NotificationType.vala:64
|
||||||
msgid "Settings"
|
#, fuzzy, c-format
|
||||||
msgstr "Paramètres"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> vous suit désormais"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:142
|
#: src/API/NotificationType.vala:66
|
||||||
msgid "<b>New Account</b>"
|
#, fuzzy, c-format
|
||||||
msgstr "<b>Nouveau Compte</b>"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> demande à vous suivre"
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:143
|
#: src/API/NotificationType.vala:68
|
||||||
msgid "Click to add"
|
#, fuzzy, c-format
|
||||||
msgstr "Cliquer pour ajouter"
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> a publié un pouet"
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||||
#: src/Widgets/StatusWidget.vala:289
|
#: src/Widgets/Account.vala:28
|
||||||
msgid "Open in Browser"
|
msgid "Open in Browser"
|
||||||
msgstr "Ouvrir dans le navigateur"
|
msgstr "Ouvrir dans le navigateur"
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
#: src/Widgets/AccountsButton.vala:62
|
||||||
#: src/Widgets/StatusWidget.vala:291
|
msgid "Refresh"
|
||||||
msgid "Copy Link"
|
msgstr "Actualiser"
|
||||||
msgstr "Copier le lien"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:41
|
#: src/Widgets/AccountsButton.vala:67
|
||||||
msgid "Select media files to add"
|
msgid "Favorites"
|
||||||
msgstr "Sélectionner les média à ajouter"
|
msgstr "Favoris"
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:44
|
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||||
msgid "_Cancel"
|
msgid "Direct Messages"
|
||||||
msgstr "_Annuler"
|
msgstr "Messages directs"
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:46
|
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||||
msgid "_Open"
|
msgid "Search"
|
||||||
msgstr "_Ouvrir"
|
msgstr "Chercher"
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:67
|
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||||
#, c-format
|
msgid "Watchlist"
|
||||||
msgid "Click to open %s media"
|
msgstr "Liste de veille"
|
||||||
msgstr "Cliquer pour ouvrir les médias %s"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:84
|
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||||
msgid "Uploading..."
|
msgid "Settings"
|
||||||
msgstr "Téléchargement en cours..."
|
msgstr "Paramètres"
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
#: src/Widgets/Status.vala:52
|
||||||
msgid "File read error"
|
msgid "[ Show more ]"
|
||||||
msgstr "Erreur de lecture du fichier"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
#: src/Widgets/Status.vala:120
|
||||||
#, c-format
|
|
||||||
msgid "Can't read file %s: %s"
|
|
||||||
msgstr "Impossible de lire le fichier %s: %s"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:124
|
|
||||||
msgid "Remove"
|
|
||||||
msgstr "Supprimer"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:134
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "Télécharger"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:20
|
|
||||||
msgid "Unknown Notification"
|
|
||||||
msgstr "Notification inconnue"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:25
|
|
||||||
msgid "Dismiss"
|
|
||||||
msgstr "Annuler"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:64
|
|
||||||
msgid "Accept"
|
|
||||||
msgstr "Accepter"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:66
|
|
||||||
msgid "Reject"
|
|
||||||
msgstr "Rejeter"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:84
|
|
||||||
msgid "Boost"
|
|
||||||
msgstr "Partager"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:91
|
|
||||||
msgid "Favorite"
|
|
||||||
msgstr "Ajouter aux favoris"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:98
|
|
||||||
msgid "Reply"
|
|
||||||
msgstr "Répondre"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:136
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> a partagé"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:151
|
|
||||||
msgid "Toggle content"
|
|
||||||
msgstr "Afficher le contenu"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:165
|
|
||||||
msgid "[ This post contains sensitive content ]"
|
|
||||||
msgstr "[ Ce message comporte un contenu sensible ]"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:234
|
|
||||||
msgid "This post can't be boosted"
|
msgid "This post can't be boosted"
|
||||||
msgstr "Ce message ne peut être partagé"
|
msgstr "Ce message ne peut être partagé"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||||
msgid "Unmute Conversation"
|
msgid "Copy Link"
|
||||||
msgstr "Rétablir la discussion"
|
msgstr "Copier le lien"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
#: src/Widgets/Status.vala:210
|
||||||
msgid "Mute Conversation"
|
|
||||||
msgstr "Masquer la discussion"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:293
|
|
||||||
msgid "Copy Text"
|
msgid "Copy Text"
|
||||||
msgstr "Copier le texte"
|
msgstr "Copier le texte"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
#: src/Widgets/Status.vala:217
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Unpin from Profile"
|
msgid "Unpin from Profile"
|
||||||
msgstr "Désépingler du profil"
|
msgstr "Désépingler du profil"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
#: src/Widgets/Status.vala:217
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "Pin on Profile"
|
msgid "Pin on Profile"
|
||||||
msgstr "Épingler sur le profil"
|
msgstr "Épingler sur le profil"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:304
|
#: src/Widgets/Status.vala:221
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||||
msgid "Redraft"
|
msgid "Redraft"
|
||||||
msgstr "Réecrire"
|
msgstr "Réecrire"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:27
|
#: src/Widgets/Attachment/Box.vala:28
|
||||||
msgid "New Account"
|
msgid "Select media files to add"
|
||||||
msgstr "Nouveau compte"
|
msgstr "Sélectionner les média à ajouter"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:38
|
#: src/Widgets/Attachment/Box.vala:31
|
||||||
msgid "What's an instance?"
|
msgid "_Cancel"
|
||||||
msgstr "Qu'est une instance?"
|
msgstr "_Annuler"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:42
|
#: src/Widgets/Attachment/Box.vala:33
|
||||||
msgid "Code:"
|
msgid "_Open"
|
||||||
msgstr "Code:"
|
msgstr "_Ouvrir"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:46
|
#: src/Dialogs/MainWindow.vala:49
|
||||||
msgid "Paste your instance authorization code here"
|
msgid "Back"
|
||||||
msgstr "Coller le code d'autorisation de votre instance ici"
|
msgstr "Retour"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:49
|
#: src/Dialogs/MainWindow.vala:58
|
||||||
msgid "Add Account"
|
msgid "Toot"
|
||||||
msgstr "Ajouter un compte"
|
msgstr "Écrire un message"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:60
|
#: src/Dialogs/MainWindow.vala:63
|
||||||
msgid "Instance:"
|
msgid "Toggle content"
|
||||||
msgstr "Instance:"
|
msgstr "Afficher le contenu"
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:102
|
#: src/Dialogs/Compose.vala:69
|
||||||
msgid "Please paste valid instance authorization code"
|
msgid "Post"
|
||||||
msgstr "Veuillez coller un code d'autorisation valide"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:110
|
#: src/Dialogs/Preferences.vala:36
|
||||||
msgid "Network Error"
|
|
||||||
msgstr "Erreur réseau"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:45
|
|
||||||
msgid "Post Visibility"
|
|
||||||
msgstr "Visibilité du message"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:52
|
|
||||||
msgid "Add Media"
|
|
||||||
msgstr "Ajouter un média"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:61
|
|
||||||
msgid "Spoiler Warning"
|
|
||||||
msgstr "Ajouter un avertissement"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:68
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr "Annuler"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:77
|
|
||||||
msgid "Toot!"
|
|
||||||
msgstr "Envoyer le message!"
|
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:85
|
|
||||||
msgid "Write your warning here"
|
|
||||||
msgstr "Écrire votre avertissement ici"
|
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:37
|
|
||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "Apparence"
|
msgstr "Apparence"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:38
|
#: src/Dialogs/Preferences.vala:37
|
||||||
msgid "Dark theme:"
|
msgid "Dark theme:"
|
||||||
msgstr "Thème sombre:"
|
msgstr "Thème sombre:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:41
|
#: src/Dialogs/Preferences.vala:40
|
||||||
msgid "Timelines"
|
msgid "Timelines"
|
||||||
msgstr "Fils"
|
msgstr "Fils"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:42
|
#: src/Dialogs/Preferences.vala:41
|
||||||
msgid "Real-time updates:"
|
msgid "Real-time updates:"
|
||||||
msgstr "Mises à jour instantanées:"
|
msgstr "Mises à jour instantanées:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:44
|
#: src/Dialogs/Preferences.vala:43
|
||||||
msgid "Update public timelines:"
|
msgid "Update public timelines:"
|
||||||
msgstr "Mettre à jour le fil public:"
|
msgstr "Mettre à jour le fil public:"
|
||||||
|
|
||||||
|
@ -425,144 +363,290 @@ msgstr "Mettre à jour le fil public:"
|
||||||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||||
#. grid.attach (cache_size, 1, i++);
|
#. grid.attach (cache_size, 1, i++);
|
||||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notifications"
|
msgstr "Notifications"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:56
|
#: src/Dialogs/Preferences.vala:55
|
||||||
msgid "Display notifications:"
|
msgid "Display notifications:"
|
||||||
msgstr "Afficher les notifications:"
|
msgstr "Afficher les notifications:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:58
|
#: src/Dialogs/Preferences.vala:57
|
||||||
msgid "Always receive notifications:"
|
msgid "Always receive notifications:"
|
||||||
msgstr "Toujours recevoir les notifications:"
|
msgstr "Toujours recevoir les notifications:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:64
|
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||||
msgid "_Close"
|
msgid "_Close"
|
||||||
msgstr "_Fermer"
|
msgstr "_Fermer"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:20
|
#: src/Dialogs/WatchlistEditor.vala:20
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots from this user appear in your Home timeline."
|
"You'll be notified when toots from this user appear in your Home timeline."
|
||||||
msgstr "Vous serez averti lorsque des messages de cet utilisateur apparaîtront sur votre fil d'actualité."
|
msgstr ""
|
||||||
|
"Vous serez averti lorsque des messages de cet utilisateur apparaîtront sur "
|
||||||
|
"votre fil d'actualité."
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:21
|
#: src/Dialogs/WatchlistEditor.vala:21
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots with this hashtag appear in any public "
|
"You'll be notified when toots with this hashtag appear in any public "
|
||||||
"timelines."
|
"timelines."
|
||||||
msgstr "Vous serez averti lorsque des toots contenant ce hashtag apparaîtront dans n'importe quel fil public."
|
msgstr ""
|
||||||
|
"Vous serez averti lorsque des toots contenant ce hashtag apparaîtront dans "
|
||||||
|
"n'importe quel fil public."
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:137
|
#: src/Dialogs/WatchlistEditor.vala:108
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilisateurs"
|
msgstr "Utilisateurs"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||||
msgid "Hashtags"
|
msgid "Hashtags"
|
||||||
msgstr "Hashtags"
|
msgstr "Hashtags"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:148
|
#: src/Dialogs/WatchlistEditor.vala:122
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Ajouter"
|
msgstr "Ajouter"
|
||||||
|
|
||||||
#: src/Views/AbstractView.vala:59
|
#: src/Views/Base.vala:6
|
||||||
msgid "Nothing to see here"
|
msgid "Nothing to see here"
|
||||||
msgstr "Rien à voir par ici"
|
msgstr "Rien à voir par ici"
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:79
|
#: src/Views/NewAccount.vala:91
|
||||||
msgid "Edit Profile"
|
msgid "Instance URL is invalid"
|
||||||
msgstr "Éditer votre profil"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:80
|
#: src/Views/NewAccount.vala:133
|
||||||
msgid "Mention"
|
#, fuzzy
|
||||||
msgstr "Mentionner"
|
msgid "Please paste a valid authorization code"
|
||||||
|
msgstr "Veuillez coller un code d'autorisation valide"
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:81
|
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||||
msgid "Report"
|
|
||||||
msgstr "Signaler"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr "Masquer"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
|
||||||
msgid "Block"
|
|
||||||
msgstr "Bloquer"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:95
|
|
||||||
msgid "More Actions"
|
|
||||||
msgstr "Plus d'actions"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:115
|
|
||||||
msgid "Toots"
|
|
||||||
msgstr "Pouets"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:116
|
|
||||||
msgid "Follows"
|
|
||||||
msgstr "Abonnements"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:120
|
|
||||||
msgid "Followers"
|
|
||||||
msgstr "Abonnés"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:155
|
|
||||||
msgid "Unfollow"
|
|
||||||
msgstr "Ne plus suivre"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:159
|
|
||||||
msgid "Follow"
|
|
||||||
msgstr "Suivre"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:166
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr "Débloquer"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:167
|
|
||||||
msgid "Unmute"
|
|
||||||
msgstr "Rétablir"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:228
|
|
||||||
msgid "Sent follow request"
|
|
||||||
msgstr "Demande de suivi envoyée"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:230
|
|
||||||
msgid "Blocked"
|
|
||||||
msgstr "Bloqué"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:232
|
|
||||||
msgid "Follows you"
|
|
||||||
msgstr "Vous suis actuellement"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:234
|
|
||||||
msgid "Blocking this instance"
|
|
||||||
msgstr "Blocage de cette instance"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:269
|
|
||||||
msgid "User not found"
|
|
||||||
msgstr "Utilisateur non trouvé"
|
|
||||||
|
|
||||||
#: src/Views/FederatedView.vala:12
|
|
||||||
msgid "Federated Timeline"
|
|
||||||
msgstr "Fil global"
|
|
||||||
|
|
||||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Accueil"
|
msgstr "Accueil"
|
||||||
|
|
||||||
#: src/Views/LocalView.vala:12
|
#: src/Views/Local.vala:12
|
||||||
msgid "Local Timeline"
|
msgid "Local Timeline"
|
||||||
msgstr "Fil local"
|
msgstr "Fil local"
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:82
|
#: src/Views/Federated.vala:12
|
||||||
|
msgid "Federated Timeline"
|
||||||
|
msgstr "Fil global"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:61
|
||||||
|
#, c-format
|
||||||
|
msgid "%s Posts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:67
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid "%s Follows"
|
||||||
|
msgstr "Abonnements"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:73
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid "%s Followers"
|
||||||
|
msgstr "Abonnés"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:109
|
||||||
|
msgid "Sent follow request"
|
||||||
|
msgstr "Demande de suivi envoyée"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:111
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Mutually follows you"
|
||||||
|
msgstr "Vous suis actuellement"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:113
|
||||||
|
msgid "Follows you"
|
||||||
|
msgstr "Vous suis actuellement"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:124
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Follow back"
|
||||||
|
msgstr "Suivre"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:126
|
||||||
|
msgid "Unfollow"
|
||||||
|
msgstr "Ne plus suivre"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:128
|
||||||
|
msgid "Follow"
|
||||||
|
msgstr "Suivre"
|
||||||
|
|
||||||
|
#: src/Views/Search.vala:82
|
||||||
msgid "Accounts"
|
msgid "Accounts"
|
||||||
msgstr "Comptes"
|
msgstr "Comptes"
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:91
|
#: src/Views/Search.vala:91
|
||||||
msgid "Statuses"
|
msgid "Statuses"
|
||||||
msgstr "Statuts"
|
msgstr "Statuts"
|
||||||
|
|
||||||
|
#~ msgid "TLS Error"
|
||||||
|
#~ msgstr "Erreur TLS"
|
||||||
|
|
||||||
|
#~ msgid "Can't ensure secure connection: "
|
||||||
|
#~ msgstr "Impossible d'assurer une connexion sécurisée:"
|
||||||
|
|
||||||
|
#~ msgid "Error: %s"
|
||||||
|
#~ msgstr "Erreur: %s"
|
||||||
|
|
||||||
|
#~ msgid "Boosted!"
|
||||||
|
#~ msgstr "Partagé!"
|
||||||
|
|
||||||
|
#~ msgid "Removed boost"
|
||||||
|
#~ msgstr "Partage annulé"
|
||||||
|
|
||||||
|
#~ msgid "Favorited!"
|
||||||
|
#~ msgstr "Ajouté aux favoris!"
|
||||||
|
|
||||||
|
#~ msgid "Removed from favorites"
|
||||||
|
#~ msgstr "Supprimé des favoris"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Muted!"
|
||||||
|
#~ msgstr "Masqué!"
|
||||||
|
|
||||||
|
#~ msgid "Conversation unmuted"
|
||||||
|
#~ msgstr "Discussion visible"
|
||||||
|
|
||||||
|
#~ msgid "Pinned!"
|
||||||
|
#~ msgstr "Épinglé!"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Unpinned from profile"
|
||||||
|
#~ msgstr "Désépinglé du profil"
|
||||||
|
|
||||||
|
#~ msgid "Poof!"
|
||||||
|
#~ msgstr "Poof!"
|
||||||
|
|
||||||
|
#~ msgid "<b>New Account</b>"
|
||||||
|
#~ msgstr "<b>Nouveau Compte</b>"
|
||||||
|
|
||||||
|
#~ msgid "Click to add"
|
||||||
|
#~ msgstr "Cliquer pour ajouter"
|
||||||
|
|
||||||
|
#~ msgid "Click to open %s media"
|
||||||
|
#~ msgstr "Cliquer pour ouvrir les médias %s"
|
||||||
|
|
||||||
|
#~ msgid "Uploading..."
|
||||||
|
#~ msgstr "Téléchargement en cours..."
|
||||||
|
|
||||||
|
#~ msgid "File read error"
|
||||||
|
#~ msgstr "Erreur de lecture du fichier"
|
||||||
|
|
||||||
|
#~ msgid "Can't read file %s: %s"
|
||||||
|
#~ msgstr "Impossible de lire le fichier %s: %s"
|
||||||
|
|
||||||
|
#~ msgid "Remove"
|
||||||
|
#~ msgstr "Supprimer"
|
||||||
|
|
||||||
|
#~ msgid "Download"
|
||||||
|
#~ msgstr "Télécharger"
|
||||||
|
|
||||||
|
#~ msgid "Unknown Notification"
|
||||||
|
#~ msgstr "Notification inconnue"
|
||||||
|
|
||||||
|
#~ msgid "Dismiss"
|
||||||
|
#~ msgstr "Annuler"
|
||||||
|
|
||||||
|
#~ msgid "Accept"
|
||||||
|
#~ msgstr "Accepter"
|
||||||
|
|
||||||
|
#~ msgid "Reject"
|
||||||
|
#~ msgstr "Rejeter"
|
||||||
|
|
||||||
|
#~ msgid "Boost"
|
||||||
|
#~ msgstr "Partager"
|
||||||
|
|
||||||
|
#~ msgid "Favorite"
|
||||||
|
#~ msgstr "Ajouter aux favoris"
|
||||||
|
|
||||||
|
#~ msgid "Reply"
|
||||||
|
#~ msgstr "Répondre"
|
||||||
|
|
||||||
|
#~ msgid "[ This post contains sensitive content ]"
|
||||||
|
#~ msgstr "[ Ce message comporte un contenu sensible ]"
|
||||||
|
|
||||||
|
#~ msgid "Unmute Conversation"
|
||||||
|
#~ msgstr "Rétablir la discussion"
|
||||||
|
|
||||||
|
#~ msgid "Mute Conversation"
|
||||||
|
#~ msgstr "Masquer la discussion"
|
||||||
|
|
||||||
|
#~ msgid "New Account"
|
||||||
|
#~ msgstr "Nouveau compte"
|
||||||
|
|
||||||
|
#~ msgid "What's an instance?"
|
||||||
|
#~ msgstr "Qu'est une instance?"
|
||||||
|
|
||||||
|
#~ msgid "Code:"
|
||||||
|
#~ msgstr "Code:"
|
||||||
|
|
||||||
|
#~ msgid "Paste your instance authorization code here"
|
||||||
|
#~ msgstr "Coller le code d'autorisation de votre instance ici"
|
||||||
|
|
||||||
|
#~ msgid "Add Account"
|
||||||
|
#~ msgstr "Ajouter un compte"
|
||||||
|
|
||||||
|
#~ msgid "Instance:"
|
||||||
|
#~ msgstr "Instance:"
|
||||||
|
|
||||||
|
#~ msgid "Post Visibility"
|
||||||
|
#~ msgstr "Visibilité du message"
|
||||||
|
|
||||||
|
#~ msgid "Add Media"
|
||||||
|
#~ msgstr "Ajouter un média"
|
||||||
|
|
||||||
|
#~ msgid "Spoiler Warning"
|
||||||
|
#~ msgstr "Ajouter un avertissement"
|
||||||
|
|
||||||
|
#~ msgid "Cancel"
|
||||||
|
#~ msgstr "Annuler"
|
||||||
|
|
||||||
|
#~ msgid "Toot!"
|
||||||
|
#~ msgstr "Envoyer le message!"
|
||||||
|
|
||||||
|
#~ msgid "Write your warning here"
|
||||||
|
#~ msgstr "Écrire votre avertissement ici"
|
||||||
|
|
||||||
|
#~ msgid "Edit Profile"
|
||||||
|
#~ msgstr "Éditer votre profil"
|
||||||
|
|
||||||
|
#~ msgid "Mention"
|
||||||
|
#~ msgstr "Mentionner"
|
||||||
|
|
||||||
|
#~ msgid "Report"
|
||||||
|
#~ msgstr "Signaler"
|
||||||
|
|
||||||
|
#~ msgid "Mute"
|
||||||
|
#~ msgstr "Masquer"
|
||||||
|
|
||||||
|
#~ msgid "Block"
|
||||||
|
#~ msgstr "Bloquer"
|
||||||
|
|
||||||
|
#~ msgid "More Actions"
|
||||||
|
#~ msgstr "Plus d'actions"
|
||||||
|
|
||||||
|
#~ msgid "Toots"
|
||||||
|
#~ msgstr "Pouets"
|
||||||
|
|
||||||
|
#~ msgid "Unblock"
|
||||||
|
#~ msgstr "Débloquer"
|
||||||
|
|
||||||
|
#~ msgid "Unmute"
|
||||||
|
#~ msgstr "Rétablir"
|
||||||
|
|
||||||
|
#~ msgid "Blocked"
|
||||||
|
#~ msgstr "Bloqué"
|
||||||
|
|
||||||
|
#~ msgid "Blocking this instance"
|
||||||
|
#~ msgstr "Blocage de cette instance"
|
||||||
|
|
||||||
|
#~ msgid "User not found"
|
||||||
|
#~ msgstr "Utilisateur non trouvé"
|
||||||
|
|
||||||
#~ msgid "Conversation muted"
|
#~ msgid "Conversation muted"
|
||||||
#~ msgstr "Discussion masquée"
|
#~ msgstr "Discussion masquée"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
cd .
|
||||||
|
cd build
|
||||||
|
ninja com.github.bleakgrey.tootle-update-po
|
734
po/ru.po
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
"Project-Id-Version: com.github.bleakgrey.tootle\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-10-30 19:17+0300\n"
|
"POT-Creation-Date: 2019-09-16 16:00+0300\n"
|
||||||
"PO-Revision-Date: 2018-05-10 00:35+0300\n"
|
"PO-Revision-Date: 2018-05-10 00:35+0300\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
|
@ -19,7 +19,7 @@ msgstr ""
|
||||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
#: data/com.github.bleakgrey.tootle.desktop.in:4
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7 src/MainWindow.vala:68
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:7
|
||||||
msgid "Tootle"
|
msgid "Tootle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -41,30 +41,34 @@ msgid "Lightning fast client for Mastodon"
|
||||||
msgstr "Молниеносный клиент для Мастодонта"
|
msgstr "Молниеносный клиент для Мастодонта"
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:11
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
"Tootle is a client for the world’s largest free, open-source, decentralized "
|
||||||
"microblogging network with real-time notifications and multiple accounts "
|
"microblogging network with real-time notifications and support for multiple "
|
||||||
"support."
|
"accounts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tootle - клиент для крупнейшей в мире свободной, децентрализованной сети "
|
"Tootle - клиент для крупнейшей в мире свободной, децентрализованной сети "
|
||||||
"микроблогинга с открытым исходным кодом. Он поддерживает уведомления в "
|
"микроблогинга с открытым исходным кодом. Он поддерживает уведомления в "
|
||||||
"реальном времени и несколько рабочих аккаунтов."
|
"реальном времени и несколько рабочих аккаунтов."
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:14
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Mastodon is lovely crafted with power and speed in mind, resulting in a "
|
"Mastodon is lovingly crafted with power and speed in mind, resulting in a "
|
||||||
"free, independent and popular alternative to the centralized social networks."
|
"free, independent, and popular alternative to the centralized social "
|
||||||
|
"networks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Мастодонт был создан с учётом множества возможностей и скорости, что привело "
|
"Мастодонт был создан с учётом множества возможностей и скорости, что привело "
|
||||||
"к созданию свободной альтернативы популярным централизованным социальным "
|
"к созданию свободной альтернативы популярным централизованным социальным "
|
||||||
"сетям."
|
"сетям."
|
||||||
|
|
||||||
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:17
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Anyone can run a server of Mastodon. Each server hosts individual user "
|
"Anyone can run a Mastodon server. Each server hosts individual user "
|
||||||
"accounts, the content they produce, and the content they are subscribed. "
|
"accounts, the content they produce, and the content to which they are "
|
||||||
"Every user can follow each other and share their posts regardless of their "
|
"subscribed. Every user can follow each other and share their posts "
|
||||||
"server."
|
"regardless of their server."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Каждый может запустить свою копию Мастодонта. Сервер хранит аккаунты "
|
"Каждый может запустить свою копию Мастодонта. Сервер хранит аккаунты "
|
||||||
"пользователей, контент, который они создают, и контент, на который они "
|
"пользователей, контент, который они создают, и контент, на который они "
|
||||||
|
@ -75,344 +79,280 @@ msgstr ""
|
||||||
msgid "bleak_grey"
|
msgid "bleak_grey"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Desktop.vala:10 src/API/Account.vala:123 src/API/Account.vala:142
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:80
|
||||||
#: src/API/Account.vala:161 src/Dialogs/NewAccountDialog.vala:102
|
#, fuzzy
|
||||||
|
msgid "Added Watchlist"
|
||||||
|
msgstr "Список Наблюдения"
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:81
|
||||||
|
msgid "Added Redraft support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:82
|
||||||
|
msgid "Added Pinning support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:83
|
||||||
|
msgid "Added Simplified Chinese and German translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:84
|
||||||
|
msgid "Added --hidden Start Flag"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:85
|
||||||
|
msgid "Added Shortcuts and Back mouse button support"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:86
|
||||||
|
msgid "Changed Notifications screen behavior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:87
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:102
|
||||||
|
msgid "Fixed minor bugs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:94
|
||||||
|
msgid "Added Russian, French and Polish translations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:95
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Added Direct timeline"
|
||||||
|
msgstr "Обновлять публичные ленты:"
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:96
|
||||||
|
msgid "Added support for custom character limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:97
|
||||||
|
msgid "Added support for streaming all timelines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:98
|
||||||
|
msgid "Added tooltips for image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:99
|
||||||
|
msgid "Added remove action for attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:100
|
||||||
|
msgid "Changed behavior for mentioning users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:101
|
||||||
|
msgid "Changed behavior for missing image attachments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/com.github.bleakgrey.tootle.appdata.xml.in:109
|
||||||
|
msgid "Initial release"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Desktop.vala:10
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Ошибка"
|
msgstr "Ошибка"
|
||||||
|
|
||||||
#: src/Desktop.vala:46
|
#: src/Desktop.vala:47
|
||||||
msgid "Media downloaded"
|
msgid "Media downloaded"
|
||||||
msgstr "Медиаконтент загружен"
|
msgstr "Медиаконтент загружен"
|
||||||
|
|
||||||
#: src/MainWindow.vala:48
|
#: src/Services/Accounts.vala:31
|
||||||
msgid "Back"
|
|
||||||
msgstr "Назад"
|
|
||||||
|
|
||||||
#: src/MainWindow.vala:54 src/Dialogs/PostDialog.vala:29
|
|
||||||
msgid "Toot"
|
|
||||||
msgstr "Статус"
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "TLS Error"
|
|
||||||
msgstr "Ошибка TLS"
|
|
||||||
|
|
||||||
#: src/Network.vala:58
|
|
||||||
msgid "Can't ensure secure connection: "
|
|
||||||
msgstr "Не удалось установить безопасное соединение:"
|
|
||||||
|
|
||||||
#: src/Network.vala:66
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: %s"
|
msgid ""
|
||||||
msgstr "Ошибка: %s"
|
"This instance has invalidated this session. Please sign in again.\n"
|
||||||
|
"\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:50
|
#: src/Services/Network.vala:86
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> mentioned you"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> упомянул вас"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:52
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted your toot"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул ваш статус"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:54
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> favorited your toot"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> понравился ваш статус"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:56
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> now follows you"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> подписался на вас"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:58
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> wants to follow you"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> хочет на вас подписаться"
|
|
||||||
|
|
||||||
#: src/API/NotificationType.vala:60
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> posted a toot"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> опубликовал статус"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:174
|
|
||||||
msgid "Boosted!"
|
|
||||||
msgstr "Продвинуто!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:176
|
|
||||||
msgid "Removed boost"
|
|
||||||
msgstr "Продвижение отменено"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:189
|
|
||||||
msgid "Favorited!"
|
|
||||||
msgstr "Добавлено в понравившиеся!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:191
|
|
||||||
msgid "Removed from favorites"
|
|
||||||
msgstr "Удалено из понравившихся"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:204
|
|
||||||
msgid "Muted!"
|
|
||||||
msgstr "Заглушено!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:206
|
|
||||||
msgid "Conversation unmuted"
|
|
||||||
msgstr "Переписка включена"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:219
|
|
||||||
msgid "Pinned!"
|
|
||||||
msgstr "Закреплено!"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:221
|
|
||||||
msgid "Unpinned from profile"
|
|
||||||
msgstr "Откреплено от профиля"
|
|
||||||
|
|
||||||
#: src/API/Status.vala:231
|
|
||||||
msgid "Poof!"
|
|
||||||
msgstr "Вжух!"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:40
|
|
||||||
msgid "Post to public timelines"
|
|
||||||
msgstr "Видно в публичных лентах"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:42
|
|
||||||
msgid "Don't post to public timelines"
|
|
||||||
msgstr "Не видно в публичных лентах"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:44
|
|
||||||
msgid "Post to followers only"
|
|
||||||
msgstr "Только для подписчиков"
|
|
||||||
|
|
||||||
#: src/API/StatusVisibility.vala:46
|
|
||||||
msgid "Post to mentioned users only"
|
|
||||||
msgstr "Только для упомянутых"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:67
|
|
||||||
msgid "Refresh"
|
|
||||||
msgstr "Обновить"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:71
|
|
||||||
msgid "Favorites"
|
|
||||||
msgstr "Понравившиеся"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:75 src/Views/DirectView.vala:12
|
|
||||||
msgid "Direct Messages"
|
|
||||||
msgstr "Личные Сообщения"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:79 src/Views/SearchView.vala:12
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Поиск"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:83
|
|
||||||
msgid "Watchlist"
|
|
||||||
msgstr "Список Наблюдения"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:87 src/Dialogs/SettingsDialog.vala:18
|
|
||||||
msgid "Settings"
|
|
||||||
msgstr "Настройки"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:142
|
|
||||||
msgid "<b>New Account</b>"
|
|
||||||
msgstr "<b>Новый аккаунт</b>"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountsButton.vala:143
|
|
||||||
msgid "Click to add"
|
|
||||||
msgstr "Нажмите, чтобы добавить"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:24 src/Widgets/AttachmentWidget.vala:130
|
|
||||||
#: src/Widgets/StatusWidget.vala:289
|
|
||||||
msgid "Open in Browser"
|
|
||||||
msgstr "Открыть в Браузере"
|
|
||||||
|
|
||||||
#: src/Widgets/AccountWidget.vala:26 src/Widgets/AttachmentWidget.vala:132
|
|
||||||
#: src/Widgets/StatusWidget.vala:291
|
|
||||||
msgid "Copy Link"
|
|
||||||
msgstr "Скопировать Ссылку"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:41
|
|
||||||
msgid "Select media files to add"
|
|
||||||
msgstr "Выберите файлы для добавления"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:44
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr "_Отмена"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentBox.vala:46
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr "_Выбрать"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:67
|
|
||||||
#, c-format
|
|
||||||
msgid "Click to open %s media"
|
|
||||||
msgstr "Нажмите, чтобы открыть %s"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:84
|
|
||||||
msgid "Uploading..."
|
|
||||||
msgstr "Загрузка..."
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
|
||||||
msgid "File read error"
|
|
||||||
msgstr "Ошибка чтения файла"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:105
|
|
||||||
#, c-format
|
|
||||||
msgid "Can't read file %s: %s"
|
|
||||||
msgstr "Не удалось прочитать файл %s: %s"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:124
|
|
||||||
msgid "Remove"
|
|
||||||
msgstr "Удалить"
|
|
||||||
|
|
||||||
#: src/Widgets/AttachmentWidget.vala:134
|
|
||||||
msgid "Download"
|
|
||||||
msgstr "Скачать"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:20
|
|
||||||
msgid "Unknown Notification"
|
|
||||||
msgstr "Неизвестное уведомление"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:25
|
|
||||||
msgid "Dismiss"
|
|
||||||
msgstr "Скрыть"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:64
|
|
||||||
msgid "Accept"
|
|
||||||
msgstr "Принять"
|
|
||||||
|
|
||||||
#: src/Widgets/NotificationWidget.vala:66
|
|
||||||
msgid "Reject"
|
|
||||||
msgstr "Отклонить"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:84
|
|
||||||
msgid "Boost"
|
|
||||||
msgstr "Продвинуть"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:91
|
|
||||||
msgid "Favorite"
|
|
||||||
msgstr "Нравится"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:98
|
|
||||||
msgid "Reply"
|
|
||||||
msgstr "Ответить"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:136
|
|
||||||
#, c-format
|
|
||||||
msgid "<a href=\"%s\"><b>%s</b></a> boosted"
|
|
||||||
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:151
|
|
||||||
msgid "Toggle content"
|
|
||||||
msgstr "Развернуть"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:165
|
|
||||||
msgid "[ This post contains sensitive content ]"
|
|
||||||
msgstr "[ Данный статус содержит чувствительный контент ]"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:234
|
|
||||||
msgid "This post can't be boosted"
|
|
||||||
msgstr "Этот статус нельзя продвинуть"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
|
||||||
msgid "Unmute Conversation"
|
|
||||||
msgstr "Включить переписку"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:287
|
|
||||||
msgid "Mute Conversation"
|
|
||||||
msgstr "Заглушить Переписку"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:293
|
|
||||||
msgid "Copy Text"
|
|
||||||
msgstr "Скопировать Текст"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
|
||||||
msgid "Unpin from Profile"
|
|
||||||
msgstr "Открепить от Профиля"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:300
|
|
||||||
msgid "Pin on Profile"
|
|
||||||
msgstr "Закрепить на Профиле"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:304
|
|
||||||
msgid "Delete"
|
|
||||||
msgstr "Удалить"
|
|
||||||
|
|
||||||
#: src/Widgets/StatusWidget.vala:308 src/Dialogs/PostDialog.vala:72
|
|
||||||
msgid "Redraft"
|
|
||||||
msgstr "Исправить"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:27
|
|
||||||
msgid "New Account"
|
|
||||||
msgstr "Новый аккаунт"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:38
|
|
||||||
msgid "What's an instance?"
|
|
||||||
msgstr "Что такое узел?"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:42
|
|
||||||
msgid "Code:"
|
|
||||||
msgstr "Код:"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:46
|
|
||||||
msgid "Paste your instance authorization code here"
|
|
||||||
msgstr "Вставьте свой код авторизации здесь"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:49
|
|
||||||
msgid "Add Account"
|
|
||||||
msgstr "Добавить Аккаунт"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:60
|
|
||||||
msgid "Instance:"
|
|
||||||
msgstr "Узел:"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:102
|
|
||||||
msgid "Please paste valid instance authorization code"
|
|
||||||
msgstr "Пожалуйста, вставьте корректный код авторизации"
|
|
||||||
|
|
||||||
#: src/Dialogs/NewAccountDialog.vala:110
|
|
||||||
msgid "Network Error"
|
msgid "Network Error"
|
||||||
msgstr "Сетевая Ошибка"
|
msgstr "Сетевая Ошибка"
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:45
|
#: src/API/Visibility.vala:36
|
||||||
msgid "Post Visibility"
|
msgid "Unlisted"
|
||||||
msgstr "Видимость Статуса"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:52
|
#: src/API/Visibility.vala:38
|
||||||
msgid "Add Media"
|
#, fuzzy
|
||||||
msgstr "Добавить Медиаконтент"
|
msgid "Followers-only"
|
||||||
|
msgstr "Подписчиков"
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:61
|
#: src/API/Visibility.vala:40
|
||||||
msgid "Spoiler Warning"
|
msgid "Direct"
|
||||||
msgstr "Спойлер"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:68
|
#: src/API/Visibility.vala:42
|
||||||
msgid "Cancel"
|
msgid "Public"
|
||||||
msgstr "Отмена"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:77
|
#: src/API/Visibility.vala:49
|
||||||
msgid "Toot!"
|
msgid "Don't post to public timelines"
|
||||||
msgstr "Отправить!"
|
msgstr "Не видно в публичных лентах"
|
||||||
|
|
||||||
#: src/Dialogs/PostDialog.vala:85
|
#: src/API/Visibility.vala:51
|
||||||
msgid "Write your warning here"
|
msgid "Post to followers only"
|
||||||
msgstr "Напишите здесь предупреждение"
|
msgstr "Только для подписчиков"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:37
|
#: src/API/Visibility.vala:53
|
||||||
|
msgid "Post to mentioned users only"
|
||||||
|
msgstr "Только для упомянутых"
|
||||||
|
|
||||||
|
#: src/API/Visibility.vala:55
|
||||||
|
msgid "Post to public timelines"
|
||||||
|
msgstr "Видно в публичных лентах"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:56
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> упомянул вас"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:58
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул ваш статус"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:60
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid "<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> продвинул"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:62
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> понравился ваш статус"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:64
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> подписался на вас"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:66
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</"
|
||||||
|
"span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> хочет на вас подписаться"
|
||||||
|
|
||||||
|
#: src/API/NotificationType.vala:68
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid ""
|
||||||
|
"<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>"
|
||||||
|
msgstr "<a href=\"%s\"><b>%s</b></a> опубликовал статус"
|
||||||
|
|
||||||
|
#: src/Widgets/RichLabel.vala:105 src/Widgets/Status.vala:206
|
||||||
|
#: src/Widgets/Account.vala:28
|
||||||
|
msgid "Open in Browser"
|
||||||
|
msgstr "Открыть в Браузере"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:62
|
||||||
|
msgid "Refresh"
|
||||||
|
msgstr "Обновить"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:67
|
||||||
|
msgid "Favorites"
|
||||||
|
msgstr "Понравившиеся"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:71 src/Views/Direct.vala:12
|
||||||
|
msgid "Direct Messages"
|
||||||
|
msgstr "Личные Сообщения"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:75 src/Views/Search.vala:12
|
||||||
|
msgid "Search"
|
||||||
|
msgstr "Поиск"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:79 src/Dialogs/WatchlistEditor.vala:99
|
||||||
|
msgid "Watchlist"
|
||||||
|
msgstr "Список Наблюдения"
|
||||||
|
|
||||||
|
#: src/Widgets/AccountsButton.vala:83 src/Dialogs/Preferences.vala:17
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Настройки"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:52
|
||||||
|
msgid "[ Show more ]"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:120
|
||||||
|
msgid "This post can't be boosted"
|
||||||
|
msgstr "Этот статус нельзя продвинуть"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:208 src/Widgets/Account.vala:30
|
||||||
|
msgid "Copy Link"
|
||||||
|
msgstr "Скопировать Ссылку"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:210
|
||||||
|
msgid "Copy Text"
|
||||||
|
msgstr "Скопировать Текст"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:217
|
||||||
|
msgid "Unpin from Profile"
|
||||||
|
msgstr "Открепить от Профиля"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:217
|
||||||
|
msgid "Pin on Profile"
|
||||||
|
msgstr "Закрепить на Профиле"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:221
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Удалить"
|
||||||
|
|
||||||
|
#: src/Widgets/Status.vala:225 src/Dialogs/Compose.vala:73
|
||||||
|
msgid "Redraft"
|
||||||
|
msgstr "Исправить"
|
||||||
|
|
||||||
|
#: src/Widgets/Attachment/Box.vala:28
|
||||||
|
msgid "Select media files to add"
|
||||||
|
msgstr "Выберите файлы для добавления"
|
||||||
|
|
||||||
|
#: src/Widgets/Attachment/Box.vala:31
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Отмена"
|
||||||
|
|
||||||
|
#: src/Widgets/Attachment/Box.vala:33
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Выбрать"
|
||||||
|
|
||||||
|
#: src/Dialogs/MainWindow.vala:49
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Назад"
|
||||||
|
|
||||||
|
#: src/Dialogs/MainWindow.vala:58
|
||||||
|
msgid "Toot"
|
||||||
|
msgstr "Статус"
|
||||||
|
|
||||||
|
#: src/Dialogs/MainWindow.vala:63
|
||||||
|
msgid "Toggle content"
|
||||||
|
msgstr "Развернуть"
|
||||||
|
|
||||||
|
#: src/Dialogs/Compose.vala:69
|
||||||
|
msgid "Post"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Dialogs/Preferences.vala:36
|
||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "Внешний вид"
|
msgstr "Внешний вид"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:38
|
#: src/Dialogs/Preferences.vala:37
|
||||||
msgid "Dark theme:"
|
msgid "Dark theme:"
|
||||||
msgstr "Тёмная тема:"
|
msgstr "Тёмная тема:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:41
|
#: src/Dialogs/Preferences.vala:40
|
||||||
msgid "Timelines"
|
msgid "Timelines"
|
||||||
msgstr "Ленты"
|
msgstr "Ленты"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:42
|
#: src/Dialogs/Preferences.vala:41
|
||||||
msgid "Real-time updates:"
|
msgid "Real-time updates:"
|
||||||
msgstr "Обновления в реальном времени:"
|
msgstr "Обновления в реальном времени:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:44
|
#: src/Dialogs/Preferences.vala:43
|
||||||
msgid "Update public timelines:"
|
msgid "Update public timelines:"
|
||||||
msgstr "Обновлять публичные ленты:"
|
msgstr "Обновлять публичные ленты:"
|
||||||
|
|
||||||
|
@ -420,33 +360,33 @@ msgstr "Обновлять публичные ленты:"
|
||||||
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||||
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
#. grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||||
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
#. grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||||
#. var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
#. var cache_size = new SpinButton.with_range (16, 256, 1);
|
||||||
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
#. settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||||
#. grid.attach (cache_size, 1, i++);
|
#. grid.attach (cache_size, 1, i++);
|
||||||
#: src/Dialogs/SettingsDialog.vala:55 src/Views/NotificationsView.vala:34
|
#: src/Dialogs/Preferences.vala:54 src/Views/Notifications.vala:33
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Уведомления"
|
msgstr "Уведомления"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:56
|
#: src/Dialogs/Preferences.vala:55
|
||||||
msgid "Display notifications:"
|
msgid "Display notifications:"
|
||||||
msgstr "Отображать уведомления:"
|
msgstr "Отображать уведомления:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:58
|
#: src/Dialogs/Preferences.vala:57
|
||||||
msgid "Always receive notifications:"
|
msgid "Always receive notifications:"
|
||||||
msgstr "Всегда получать уведомления:"
|
msgstr "Всегда получать уведомления:"
|
||||||
|
|
||||||
#: src/Dialogs/SettingsDialog.vala:64
|
#: src/Dialogs/Preferences.vala:63 src/Dialogs/WatchlistEditor.vala:162
|
||||||
msgid "_Close"
|
msgid "_Close"
|
||||||
msgstr "_Закрыть"
|
msgstr "_Закрыть"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:20
|
#: src/Dialogs/WatchlistEditor.vala:20
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots from this user appear in your Home timeline."
|
"You'll be notified when toots from this user appear in your Home timeline."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Вы получите уведомление, когда статусы от этого пользователя появятся в "
|
"Вы получите уведомление, когда статусы от этого пользователя появятся в "
|
||||||
"вашей Главной ленте."
|
"вашей Главной ленте."
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:21
|
#: src/Dialogs/WatchlistEditor.vala:21
|
||||||
msgid ""
|
msgid ""
|
||||||
"You'll be notified when toots with this hashtag appear in any public "
|
"You'll be notified when toots with this hashtag appear in any public "
|
||||||
"timelines."
|
"timelines."
|
||||||
|
@ -454,110 +394,88 @@ msgstr ""
|
||||||
"Вы получите уведомление, когда статусы с данным хэштегом появятся в любой "
|
"Вы получите уведомление, когда статусы с данным хэштегом появятся в любой "
|
||||||
"публичной ленте."
|
"публичной ленте."
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:137
|
#: src/Dialogs/WatchlistEditor.vala:108
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Пользователи"
|
msgstr "Пользователи"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:138 src/Views/SearchView.vala:100
|
#: src/Dialogs/WatchlistEditor.vala:109 src/Views/Search.vala:100
|
||||||
msgid "Hashtags"
|
msgid "Hashtags"
|
||||||
msgstr "Хэштеги"
|
msgstr "Хэштеги"
|
||||||
|
|
||||||
#: src/Dialogs/WatchlistDialog.vala:148
|
#: src/Dialogs/WatchlistEditor.vala:122
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Добавить"
|
msgstr "Добавить"
|
||||||
|
|
||||||
#: src/Views/AbstractView.vala:59
|
#: src/Views/Base.vala:6
|
||||||
msgid "Nothing to see here"
|
msgid "Nothing to see here"
|
||||||
msgstr "Тут ничего нет"
|
msgstr "Тут ничего нет"
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:79
|
#: src/Views/NewAccount.vala:91
|
||||||
msgid "Edit Profile"
|
msgid "Instance URL is invalid"
|
||||||
msgstr "Редактировать Профиль"
|
msgstr ""
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:80
|
#: src/Views/NewAccount.vala:133
|
||||||
msgid "Mention"
|
#, fuzzy
|
||||||
msgstr "Упомянуть"
|
msgid "Please paste a valid authorization code"
|
||||||
|
msgstr "Пожалуйста, вставьте корректный код авторизации"
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:81
|
#: src/Views/Timeline.vala:34 src/Views/Home.vala:12
|
||||||
msgid "Report"
|
|
||||||
msgstr "Пожаловаться"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:82 src/Views/AccountView.vala:167
|
|
||||||
msgid "Mute"
|
|
||||||
msgstr "Заглушить"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:83 src/Views/AccountView.vala:166
|
|
||||||
msgid "Block"
|
|
||||||
msgstr "Заблокировать"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:95
|
|
||||||
msgid "More Actions"
|
|
||||||
msgstr "Больше Действий"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:115
|
|
||||||
msgid "Toots"
|
|
||||||
msgstr "Статусов"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:116
|
|
||||||
msgid "Follows"
|
|
||||||
msgstr "Подписок"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:120
|
|
||||||
msgid "Followers"
|
|
||||||
msgstr "Подписчиков"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:155
|
|
||||||
msgid "Unfollow"
|
|
||||||
msgstr "Отписаться"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:159
|
|
||||||
msgid "Follow"
|
|
||||||
msgstr "Подписаться"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:166
|
|
||||||
msgid "Unblock"
|
|
||||||
msgstr "Разблокировать"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:167
|
|
||||||
msgid "Unmute"
|
|
||||||
msgstr "Включить"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:228
|
|
||||||
msgid "Sent follow request"
|
|
||||||
msgstr "Отправлен запрос на подписку"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:230
|
|
||||||
msgid "Blocked"
|
|
||||||
msgstr "Заблокирован"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:232
|
|
||||||
msgid "Follows you"
|
|
||||||
msgstr "Подписан на вас"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:234
|
|
||||||
msgid "Blocking this instance"
|
|
||||||
msgstr "Данный узел заблокирован"
|
|
||||||
|
|
||||||
#: src/Views/AccountView.vala:269
|
|
||||||
msgid "User not found"
|
|
||||||
msgstr "Пользователь не найден"
|
|
||||||
|
|
||||||
#: src/Views/FederatedView.vala:12
|
|
||||||
msgid "Federated Timeline"
|
|
||||||
msgstr "Глобальная Лента"
|
|
||||||
|
|
||||||
#: src/Views/HomeView.vala:12 src/Views/TimelineView.vala:36
|
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Главная"
|
msgstr "Главная"
|
||||||
|
|
||||||
#: src/Views/LocalView.vala:12
|
#: src/Views/Local.vala:12
|
||||||
msgid "Local Timeline"
|
msgid "Local Timeline"
|
||||||
msgstr "Локальная Лента"
|
msgstr "Локальная Лента"
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:82
|
#: src/Views/Federated.vala:12
|
||||||
|
msgid "Federated Timeline"
|
||||||
|
msgstr "Глобальная Лента"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:61
|
||||||
|
#, c-format
|
||||||
|
msgid "%s Posts"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:67
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid "%s Follows"
|
||||||
|
msgstr "Подписок"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:73
|
||||||
|
#, fuzzy, c-format
|
||||||
|
msgid "%s Followers"
|
||||||
|
msgstr "Подписчиков"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:109
|
||||||
|
msgid "Sent follow request"
|
||||||
|
msgstr "Отправлен запрос на подписку"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:111
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Mutually follows you"
|
||||||
|
msgstr "Подписан на вас"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:113
|
||||||
|
msgid "Follows you"
|
||||||
|
msgstr "Подписан на вас"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:124
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Follow back"
|
||||||
|
msgstr "Подписаться"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:126
|
||||||
|
msgid "Unfollow"
|
||||||
|
msgstr "Отписаться"
|
||||||
|
|
||||||
|
#: src/Views/Profile.vala:128
|
||||||
|
msgid "Follow"
|
||||||
|
msgstr "Подписаться"
|
||||||
|
|
||||||
|
#: src/Views/Search.vala:82
|
||||||
msgid "Accounts"
|
msgid "Accounts"
|
||||||
msgstr "Аккаунты"
|
msgstr "Аккаунты"
|
||||||
|
|
||||||
#: src/Views/SearchView.vala:91
|
#: src/Views/Search.vala:91
|
||||||
msgid "Statuses"
|
msgid "Statuses"
|
||||||
msgstr "Статусы"
|
msgstr "Статусы"
|
||||||
|
|
896
po/zh_CN.po
|
@ -0,0 +1,10 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = false
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
[*.sh]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
|
@ -1,60 +1,57 @@
|
||||||
public class Tootle.API.Account {
|
public class Tootle.API.Account : GLib.Object {
|
||||||
|
|
||||||
public abstract signal void updated ();
|
public int64 id { get; set; }
|
||||||
|
public string username { get; set; }
|
||||||
public int64 id;
|
public string acct { get; set; }
|
||||||
public string username;
|
public string? _display_name = null;
|
||||||
public string acct;
|
public string display_name {
|
||||||
public string display_name;
|
set {
|
||||||
public string note;
|
this._display_name = value;
|
||||||
public string header;
|
}
|
||||||
public string avatar;
|
get {
|
||||||
public string url;
|
return (_display_name == null || _display_name == "") ? username : _display_name;
|
||||||
public string created_at;
|
}
|
||||||
public int64 followers_count;
|
|
||||||
public int64 following_count;
|
|
||||||
public int64 statuses_count;
|
|
||||||
|
|
||||||
public Relationship? rs;
|
|
||||||
|
|
||||||
public Account (int64 _id){
|
|
||||||
id = _id;
|
|
||||||
}
|
}
|
||||||
|
public string note { get; set; }
|
||||||
|
public string header { get; set; }
|
||||||
|
public string avatar { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
public string created_at { get; set; }
|
||||||
|
public int64 followers_count { get; set; }
|
||||||
|
public int64 following_count { get; set; }
|
||||||
|
public int64 posts_count { get; set; }
|
||||||
|
public Relationship? rs { get; set; default = null; }
|
||||||
|
|
||||||
public static Account parse(Json.Object obj) {
|
public Account (Json.Object obj) {
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
Object (
|
||||||
var account = new Account (id);
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
|
username: obj.get_string_member ("username"),
|
||||||
|
acct: obj.get_string_member ("acct"),
|
||||||
|
display_name: obj.get_string_member ("display_name"),
|
||||||
|
note: obj.get_string_member ("note"),
|
||||||
|
avatar: obj.get_string_member ("avatar"),
|
||||||
|
header: obj.get_string_member ("header"),
|
||||||
|
url: obj.get_string_member ("url"),
|
||||||
|
created_at: obj.get_string_member ("created_at"),
|
||||||
|
|
||||||
account.username = obj.get_string_member ("username");
|
followers_count: obj.get_int_member ("followers_count"),
|
||||||
account.acct = obj.get_string_member ("acct");
|
following_count: obj.get_int_member ("following_count"),
|
||||||
account.display_name = obj.get_string_member ("display_name");
|
posts_count: obj.get_int_member ("statuses_count")
|
||||||
if (account.display_name == "")
|
);
|
||||||
account.display_name = account.username;
|
|
||||||
account.note = obj.get_string_member ("note");
|
|
||||||
account.avatar = obj.get_string_member ("avatar");
|
|
||||||
account.header = obj.get_string_member ("header");
|
|
||||||
account.url = obj.get_string_member ("url");
|
|
||||||
account.created_at = obj.get_string_member ("created_at");
|
|
||||||
|
|
||||||
account.followers_count = obj.get_int_member ("followers_count");
|
|
||||||
account.following_count = obj.get_int_member ("following_count");
|
|
||||||
account.statuses_count = obj.get_int_member ("statuses_count");
|
|
||||||
|
|
||||||
if (obj.has_member ("fields")) {
|
if (obj.has_member ("fields")) {
|
||||||
obj.get_array_member ("fields").foreach_element ((array, i, node) => {
|
obj.get_array_member ("fields").foreach_element ((array, i, node) => {
|
||||||
var field_obj = node.get_object ();
|
var field_obj = node.get_object ();
|
||||||
var field_name = field_obj.get_string_member ("name");
|
var field_name = field_obj.get_string_member ("name");
|
||||||
var field_val = field_obj.get_string_member ("value");
|
var field_val = field_obj.get_string_member ("value");
|
||||||
account.note += "\n";
|
note += "\n";
|
||||||
account.note += field_name + ": ";
|
note += field_name + ": ";
|
||||||
account.note += field_val;
|
note += field_val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return account;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json.Node? serialize () {
|
public virtual Json.Node? serialize () {
|
||||||
var builder = new Json.Builder ();
|
var builder = new Json.Builder ();
|
||||||
builder.begin_object ();
|
builder.begin_object ();
|
||||||
builder.set_member_name ("id");
|
builder.set_member_name ("id");
|
||||||
|
@ -66,7 +63,7 @@ public class Tootle.API.Account {
|
||||||
builder.set_member_name ("followers_count");
|
builder.set_member_name ("followers_count");
|
||||||
builder.add_int_value (followers_count);
|
builder.add_int_value (followers_count);
|
||||||
builder.set_member_name ("statuses_count");
|
builder.set_member_name ("statuses_count");
|
||||||
builder.add_int_value (statuses_count);
|
builder.add_int_value (posts_count);
|
||||||
builder.set_member_name ("display_name");
|
builder.set_member_name ("display_name");
|
||||||
builder.add_string_value (display_name);
|
builder.add_string_value (display_name);
|
||||||
builder.set_member_name ("username");
|
builder.set_member_name ("username");
|
||||||
|
@ -86,83 +83,55 @@ public class Tootle.API.Account {
|
||||||
return builder.get_root ();
|
return builder.get_root ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_self (){
|
public bool is_self () {
|
||||||
return id == accounts.current.id;
|
return id == accounts.active.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message get_relationship (){
|
public Request get_relationship () {
|
||||||
var url = "%s/api/v1/accounts/relationships?id=%lld".printf (accounts.formal.instance, id);
|
return new Request.GET ("/api/v1/accounts/relationships")
|
||||||
var msg = new Soup.Message("GET", url);
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.with_param ("id", id.to_string ())
|
||||||
Tootle.network.queue (msg, (sess, mess) => {
|
.then_parse_array (node => {
|
||||||
try{
|
rs = new Relationship (node.get_object ());
|
||||||
var root = Tootle.network.parse_array (mess).get_object_element (0);
|
})
|
||||||
rs = Relationship.parse (root);
|
.on_error (network.on_error)
|
||||||
updated ();
|
.exec ();
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
warning ("Can't get account relationship:");
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message set_following (bool follow = true){
|
public Request set_following (bool state = true) {
|
||||||
var action = follow ? "follow" : "unfollow";
|
var action = state ? "follow" : "unfollow";
|
||||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
var msg = new Soup.Message("POST", url);
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.then ((sess, msg) => {
|
||||||
network.queue (msg, (sess, mess) => {
|
var root = network.parse (msg);
|
||||||
try{
|
rs = new Relationship (root);
|
||||||
var root = network.parse (mess);
|
})
|
||||||
rs = Relationship.parse (root);
|
.on_error (network.on_error)
|
||||||
updated ();
|
.exec ();
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
app.error (_("Error"), e.message);
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message set_muted (bool mute = true){
|
public Request set_muted (bool state = true) {
|
||||||
var action = mute ? "mute" : "unmute";
|
var action = state ? "mute" : "unmute";
|
||||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
var msg = new Soup.Message("POST", url);
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.then ((sess, msg) => {
|
||||||
network.queue (msg, (sess, mess) => {
|
var root = network.parse (msg);
|
||||||
try{
|
rs = new Relationship (root);
|
||||||
var root = network.parse (mess);
|
})
|
||||||
rs = Relationship.parse (root);
|
.on_error (network.on_error)
|
||||||
updated ();
|
.exec ();
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
app.error (_("Error"), e.message);
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message set_blocked (bool block = true){
|
public Request set_blocked (bool state = true) {
|
||||||
var action = block ? "block" : "unblock";
|
var action = state ? "block" : "unblock";
|
||||||
var url = "%s/api/v1/accounts/%lld/%s".printf (accounts.formal.instance, id, action);
|
return new Request.POST (@"/api/v1/accounts/$id/$action")
|
||||||
var msg = new Soup.Message("POST", url);
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.then ((sess, msg) => {
|
||||||
network.queue (msg, (sess, mess) => {
|
var root = network.parse (msg);
|
||||||
try{
|
rs = new Relationship (root);
|
||||||
var root = network.parse (mess);
|
})
|
||||||
rs = Relationship.parse (root);
|
.on_error (network.on_error)
|
||||||
updated ();
|
.exec ();
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
app.error (_("Error"), e.message);
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
public class Tootle.API.Attachment {
|
public class Tootle.API.Attachment : GLib.Object {
|
||||||
|
|
||||||
public int64 id;
|
public int64 id { get; construct set; }
|
||||||
public string type;
|
public string kind { get; set; }
|
||||||
public string url;
|
public string url { get; set; }
|
||||||
public string preview_url;
|
public string? description { get; set; default = null; }
|
||||||
public string? description;
|
|
||||||
|
|
||||||
public Attachment (int64 _id) {
|
public string? _preview_url = null;
|
||||||
id = _id;
|
public string preview_url {
|
||||||
|
set { this._preview_url = value; }
|
||||||
|
get { return (_preview_url == null || _preview_url == "") ? url : _preview_url; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Attachment parse (Json.Object obj) {
|
public Attachment (Json.Object obj) {
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
Object (
|
||||||
var attachment = new Attachment (id);
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
|
kind: obj.get_string_member ("type"),
|
||||||
attachment.type = obj.get_string_member ("type");
|
preview_url: obj.get_string_member ("preview_url"),
|
||||||
attachment.preview_url = obj.get_string_member ("preview_url");
|
url: obj.get_string_member ("url"),
|
||||||
attachment.url = obj.get_string_member ("url");
|
description: obj.get_string_member ("description")
|
||||||
|
);
|
||||||
if (obj.has_member ("description"))
|
|
||||||
attachment.description = obj.get_string_member ("description");
|
|
||||||
|
|
||||||
return attachment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json.Node? serialize () {
|
public Json.Node? serialize () {
|
||||||
|
@ -30,7 +27,7 @@ public class Tootle.API.Attachment {
|
||||||
builder.set_member_name ("id");
|
builder.set_member_name ("id");
|
||||||
builder.add_string_value (id.to_string ());
|
builder.add_string_value (id.to_string ());
|
||||||
builder.set_member_name ("type");
|
builder.set_member_name ("type");
|
||||||
builder.add_string_value (type);
|
builder.add_string_value (kind);
|
||||||
builder.set_member_name ("url");
|
builder.set_member_name ("url");
|
||||||
builder.add_string_value (url);
|
builder.add_string_value (url);
|
||||||
builder.set_member_name ("preview_url");
|
builder.set_member_name ("preview_url");
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
public class Tootle.API.Mention : GLib.Object {
|
public class Tootle.API.Mention : GLib.Object {
|
||||||
|
|
||||||
public int64 id;
|
public int64 id { get; construct set; }
|
||||||
public string username;
|
public string username { get; construct set; }
|
||||||
public string acct;
|
public string acct { get; construct set; }
|
||||||
public string url;
|
public string url { get; construct set; }
|
||||||
|
|
||||||
public Mention (int64 _id){
|
public Mention (Json.Object obj) {
|
||||||
id = _id;
|
Object (
|
||||||
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
|
username: obj.get_string_member ("username"),
|
||||||
|
acct: obj.get_string_member ("acct"),
|
||||||
|
url: obj.get_string_member ("url")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mention.from_account (Account account){
|
public Mention.from_account (Account account) {
|
||||||
id = account.id;
|
Object (
|
||||||
username = account.username;
|
id: account.id,
|
||||||
acct = account.acct;
|
username: account.username,
|
||||||
url = account.url;
|
acct: account.acct,
|
||||||
}
|
url: account.url
|
||||||
|
);
|
||||||
public static Mention parse (Json.Object obj){
|
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
|
||||||
var mention = new Mention (id);
|
|
||||||
|
|
||||||
mention.username = obj.get_string_member ("username");
|
|
||||||
mention.acct = obj.get_string_member ("acct");
|
|
||||||
mention.url = obj.get_string_member ("url");
|
|
||||||
|
|
||||||
return mention;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json.Node? serialize () {
|
public Json.Node? serialize () {
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
public class Tootle.API.Notification {
|
public class Tootle.API.Notification : GLib.Object {
|
||||||
|
|
||||||
public int64 id;
|
public int64 id { get; construct set; }
|
||||||
public NotificationType type;
|
public Account account { get; construct set; }
|
||||||
public string created_at;
|
|
||||||
|
|
||||||
public Status? status;
|
public NotificationType kind { get; set; }
|
||||||
public Account? account;
|
public string created_at { get; set; }
|
||||||
|
public Status? status { get; set; default = null; }
|
||||||
|
|
||||||
public Notification (int64 _id) {
|
public Notification (Json.Object obj) throws Oopsie {
|
||||||
id = _id;
|
Object (
|
||||||
}
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
|
kind: NotificationType.from_string (obj.get_string_member ("type")),
|
||||||
public static Notification parse (Json.Object obj) {
|
created_at: obj.get_string_member ("created_at"),
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
account: new Account (obj.get_object_member ("account"))
|
||||||
var notification = new Notification (id);
|
);
|
||||||
|
|
||||||
notification.type = NotificationType.from_string (obj.get_string_member ("type"));
|
|
||||||
notification.created_at = obj.get_string_member ("created_at");
|
|
||||||
|
|
||||||
if (obj.has_member ("status"))
|
if (obj.has_member ("status"))
|
||||||
notification.status = Status.parse (obj.get_object_member ("status"));
|
status = new Status (obj.get_object_member ("status"));
|
||||||
if (obj.has_member ("account"))
|
}
|
||||||
notification.account = Account.parse (obj.get_object_member ("account"));
|
|
||||||
|
|
||||||
return notification;
|
public Notification.follow_request (Json.Object obj) {
|
||||||
|
Object (
|
||||||
|
id: 0,
|
||||||
|
kind: NotificationType.FOLLOW_REQUEST,
|
||||||
|
account: new Account (obj)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json.Node? serialize () {
|
public Json.Node? serialize () {
|
||||||
|
@ -32,7 +33,7 @@ public class Tootle.API.Notification {
|
||||||
builder.set_member_name ("id");
|
builder.set_member_name ("id");
|
||||||
builder.add_string_value (id.to_string ());
|
builder.add_string_value (id.to_string ());
|
||||||
builder.set_member_name ("type");
|
builder.set_member_name ("type");
|
||||||
builder.add_string_value (type.to_string ());
|
builder.add_string_value (kind.to_string ());
|
||||||
builder.set_member_name ("created_at");
|
builder.set_member_name ("created_at");
|
||||||
builder.add_string_value (created_at);
|
builder.add_string_value (created_at);
|
||||||
|
|
||||||
|
@ -49,47 +50,35 @@ public class Tootle.API.Notification {
|
||||||
return builder.get_root ();
|
return builder.get_root ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Notification parse_follow_request (Json.Object obj) {
|
|
||||||
var notification = new Notification (-1);
|
|
||||||
var account = Account.parse (obj);
|
|
||||||
|
|
||||||
notification.type = NotificationType.FOLLOW_REQUEST;
|
|
||||||
notification.account = account;
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Soup.Message? dismiss () {
|
public Soup.Message? dismiss () {
|
||||||
if (type == NotificationType.WATCHLIST) {
|
if (kind == NotificationType.WATCHLIST) {
|
||||||
if (accounts.formal.cached_notifications.remove (this))
|
if (accounts.active.cached_notifications.remove (this))
|
||||||
accounts.save ();
|
accounts.save ();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == NotificationType.FOLLOW_REQUEST)
|
if (kind == NotificationType.FOLLOW_REQUEST)
|
||||||
return reject_follow_request ();
|
return reject_follow_request ();
|
||||||
|
|
||||||
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (accounts.formal.instance, id);
|
var req = new Request.POST ("/api/v1/notifications/dismiss")
|
||||||
var msg = new Soup.Message ("POST", url);
|
.with_account (accounts.active)
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.with_param ("id", id.to_string ())
|
||||||
network.queue (msg);
|
.exec ();
|
||||||
return msg;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message accept_follow_request () {
|
public Soup.Message accept_follow_request () {
|
||||||
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (accounts.formal.instance, account.id);
|
var req = new Request.POST (@"/api/v1/follow_requests/$(account.id)/authorize")
|
||||||
var msg = new Soup.Message ("POST", url);
|
.with_account (accounts.active)
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.exec ();
|
||||||
network.queue (msg);
|
return req;
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message reject_follow_request () {
|
public Soup.Message reject_follow_request () {
|
||||||
var url = "%s/api/v1/follow_requests/%lld/reject".printf (accounts.formal.instance, account.id);
|
var req = new Request.POST (@"/api/v1/follow_requests/$(account.id)/reject")
|
||||||
var msg = new Soup.Message ("POST", url);
|
.with_account (accounts.active)
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.exec ();
|
||||||
network.queue (msg);
|
return req;
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ public enum Tootle.API.NotificationType {
|
||||||
FOLLOW_REQUEST, // Internal
|
FOLLOW_REQUEST, // Internal
|
||||||
WATCHLIST; // Internal
|
WATCHLIST; // Internal
|
||||||
|
|
||||||
public string to_string() {
|
public string to_string () {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MENTION:
|
case MENTION:
|
||||||
return "mention";
|
return "mention";
|
||||||
|
@ -24,11 +24,12 @@ public enum Tootle.API.NotificationType {
|
||||||
case WATCHLIST:
|
case WATCHLIST:
|
||||||
return "watchlist";
|
return "watchlist";
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
warning (@"Unknown notification type: $this");
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NotificationType from_string (string str) {
|
public static NotificationType from_string (string str) throws Oopsie {
|
||||||
switch (str) {
|
switch (str) {
|
||||||
case "mention":
|
case "mention":
|
||||||
return MENTION;
|
return MENTION;
|
||||||
|
@ -45,7 +46,7 @@ public enum Tootle.API.NotificationType {
|
||||||
case "watchlist":
|
case "watchlist":
|
||||||
return WATCHLIST;
|
return WATCHLIST;
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
throw new Oopsie.INSTANCE (@"Unknown notification type: $str");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,19 +55,20 @@ public enum Tootle.API.NotificationType {
|
||||||
case MENTION:
|
case MENTION:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> mentioned you</span>").printf (account.url, account.display_name);
|
||||||
case REBLOG:
|
case REBLOG:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your toot</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted your status</span>").printf (account.url, account.display_name);
|
||||||
case REBLOG_REMOTE_USER:
|
case REBLOG_REMOTE_USER:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> boosted</span>").printf (account.url, account.display_name);
|
||||||
case FAVORITE:
|
case FAVORITE:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your toot</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> favorited your status</span>").printf (account.url, account.display_name);
|
||||||
case FOLLOW:
|
case FOLLOW:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> now follows you</span>").printf (account.url, account.display_name);
|
||||||
case FOLLOW_REQUEST:
|
case FOLLOW_REQUEST:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> wants to follow you</span>").printf (account.url, account.display_name);
|
||||||
case WATCHLIST:
|
case WATCHLIST:
|
||||||
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a toot</span>").printf (account.url, account.display_name);
|
return _("<span underline=\"none\"><a href=\"%s\"><b>%s</b></a> posted a status</span>").printf (account.url, account.display_name);
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
warning (@"Unknown notification type: $this");
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ public enum Tootle.API.NotificationType {
|
||||||
case WATCHLIST:
|
case WATCHLIST:
|
||||||
return "user-available-symbolic";
|
return "user-available-symbolic";
|
||||||
case REBLOG:
|
case REBLOG:
|
||||||
|
case REBLOG_REMOTE_USER:
|
||||||
return "media-playlist-repeat-symbolic";
|
return "media-playlist-repeat-symbolic";
|
||||||
case FAVORITE:
|
case FAVORITE:
|
||||||
return "emblem-favorite-symbolic";
|
return "emblem-favorite-symbolic";
|
||||||
|
@ -83,7 +86,8 @@ public enum Tootle.API.NotificationType {
|
||||||
case FOLLOW_REQUEST:
|
case FOLLOW_REQUEST:
|
||||||
return "contact-new-symbolic";
|
return "contact-new-symbolic";
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
warning (@"Unknown notification type: $this");
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,25 @@
|
||||||
using GLib;
|
public class Tootle.API.Relationship : GLib.Object {
|
||||||
|
|
||||||
public class Tootle.API.Relationship : Object {
|
public int64 id { get; construct set; }
|
||||||
|
public bool following { get; set; default = false; }
|
||||||
|
public bool followed_by { get; set; default = false; }
|
||||||
|
public bool muting { get; set; default = false; }
|
||||||
|
public bool muting_notifications { get; set; default = false; }
|
||||||
|
public bool requested { get; set; default = false; }
|
||||||
|
public bool blocking { get; set; default = false; }
|
||||||
|
public bool domain_blocking { get; set; default = false; }
|
||||||
|
|
||||||
public int64 id;
|
public Relationship (Json.Object obj) {
|
||||||
public bool following;
|
Object (
|
||||||
public bool followed_by;
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
public bool blocking;
|
following: obj.get_boolean_member ("following"),
|
||||||
public bool muting;
|
followed_by: obj.get_boolean_member ("followed_by"),
|
||||||
public bool muting_notifications;
|
blocking: obj.get_boolean_member ("blocking"),
|
||||||
public bool requested;
|
muting: obj.get_boolean_member ("muting"),
|
||||||
public bool domain_blocking;
|
muting_notifications: obj.get_boolean_member ("muting_notifications"),
|
||||||
|
requested: obj.get_boolean_member ("requested"),
|
||||||
public Relationship (int64 _id) {
|
domain_blocking: obj.get_boolean_member ("domain_blocking")
|
||||||
id = _id;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
public static Relationship parse (Json.Object obj) {
|
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
|
||||||
var relationship = new Relationship (id);
|
|
||||||
relationship.following = obj.get_boolean_member ("following");
|
|
||||||
relationship.followed_by = obj.get_boolean_member ("followed_by");
|
|
||||||
relationship.blocking = obj.get_boolean_member ("blocking");
|
|
||||||
relationship.muting = obj.get_boolean_member ("muting");
|
|
||||||
relationship.muting_notifications = obj.get_boolean_member ("muting_notifications");
|
|
||||||
relationship.requested = obj.get_boolean_member ("requested");
|
|
||||||
relationship.domain_blocking = obj.get_boolean_member ("domain_blocking");
|
|
||||||
return relationship;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +1,117 @@
|
||||||
public class Tootle.API.Status {
|
using Gee;
|
||||||
|
|
||||||
public signal void updated ();
|
public class Tootle.API.Status : GLib.Object {
|
||||||
|
|
||||||
public API.Account account;
|
|
||||||
public int64 id;
|
|
||||||
public string uri;
|
|
||||||
public string url;
|
|
||||||
public string? spoiler_text;
|
|
||||||
public string content;
|
|
||||||
public int64 replies_count;
|
|
||||||
public int64 reblogs_count;
|
|
||||||
public int64 favourites_count;
|
|
||||||
public string created_at;
|
|
||||||
public bool reblogged = false;
|
|
||||||
public bool favorited = false;
|
|
||||||
public bool sensitive = false;
|
|
||||||
public bool muted = false;
|
|
||||||
public bool pinned = false;
|
|
||||||
public API.StatusVisibility visibility;
|
|
||||||
public API.Status? reblog;
|
|
||||||
public API.Mention[]? mentions;
|
|
||||||
public API.Attachment[]? attachments;
|
|
||||||
|
|
||||||
public Status (int64 _id) {
|
public int64 id { get; construct set; }
|
||||||
id = _id;
|
public API.Account account { get; construct set; }
|
||||||
|
public string uri { get; set; }
|
||||||
|
public string? url { get; set; default = null; }
|
||||||
|
public string? spoiler_text { get; set; default = null; }
|
||||||
|
public string? in_reply_to_id { get; set; default = null; }
|
||||||
|
public string? in_reply_to_account_id { get; set; default = null; }
|
||||||
|
public string content { get; set; default = ""; }
|
||||||
|
public int64 replies_count { get; set; default = 0; }
|
||||||
|
public int64 reblogs_count { get; set; default = 0; }
|
||||||
|
public int64 favourites_count { get; set; default = 0; }
|
||||||
|
public string created_at { get; set; default = "0"; }
|
||||||
|
public bool reblogged { get; set; default = false; }
|
||||||
|
public bool favorited { get; set; default = false; }
|
||||||
|
public bool sensitive { get; set; default = false; }
|
||||||
|
public bool muted { get; set; default = false; }
|
||||||
|
public bool pinned { get; set; default = false; }
|
||||||
|
public API.Visibility visibility { get; set; default = API.Visibility.PUBLIC; }
|
||||||
|
public API.Status? reblog { get; set; default = null; }
|
||||||
|
public ArrayList<API.Mention>? mentions { get; set; default = null; }
|
||||||
|
public ArrayList<API.Attachment>? attachments { get; set; default = null; }
|
||||||
|
|
||||||
|
public Status formal {
|
||||||
|
get { return reblog ?? this; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status get_formal () {
|
public bool has_spoiler {
|
||||||
return reblog != null ? reblog : this;
|
get {
|
||||||
}
|
return formal.spoiler_text != null || formal.sensitive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Status parse (Json.Object obj) {
|
public Status (Json.Object obj) {
|
||||||
var id = int64.parse (obj.get_string_member ("id"));
|
Object (
|
||||||
var status = new Status (id);
|
id: int64.parse (obj.get_string_member ("id")),
|
||||||
|
account: new Account (obj.get_object_member ("account")),
|
||||||
|
uri: obj.get_string_member ("uri"),
|
||||||
|
created_at: obj.get_string_member ("created_at"),
|
||||||
|
content: Html.simplify ( obj.get_string_member ("content")),
|
||||||
|
sensitive: obj.get_boolean_member ("sensitive"),
|
||||||
|
visibility: Visibility.from_string (obj.get_string_member ("visibility")),
|
||||||
|
|
||||||
status.account = Account.parse (obj.get_object_member ("account"));
|
in_reply_to_id: obj.get_string_member ("in_reply_to_id") ?? null,
|
||||||
status.uri = obj.get_string_member ("uri");
|
in_reply_to_account_id: obj.get_string_member ("in_reply_to_account_id") ?? null,
|
||||||
status.created_at = obj.get_string_member ("created_at");
|
|
||||||
status.replies_count = obj.get_int_member ("replies_count");
|
replies_count: obj.get_int_member ("replies_count"),
|
||||||
status.reblogs_count = obj.get_int_member ("reblogs_count");
|
reblogs_count: obj.get_int_member ("reblogs_count"),
|
||||||
status.favourites_count = obj.get_int_member ("favourites_count");
|
favourites_count: obj.get_int_member ("favourites_count")
|
||||||
status.content = Html.simplify ( obj.get_string_member ("content"));
|
);
|
||||||
status.sensitive = obj.get_boolean_member ("sensitive");
|
|
||||||
status.visibility = StatusVisibility.from_string (obj.get_string_member ("visibility"));
|
|
||||||
|
|
||||||
if (obj.has_member ("url"))
|
if (obj.has_member ("url"))
|
||||||
status.url = obj.get_string_member ("url");
|
url = obj.get_string_member ("url");
|
||||||
else
|
else
|
||||||
status.url = obj.get_string_member ("uri").replace ("/activity", "");
|
url = obj.get_string_member ("uri").replace ("/activity", "");
|
||||||
|
|
||||||
var spoiler = obj.get_string_member ("spoiler_text");
|
var spoiler = obj.get_string_member ("spoiler_text");
|
||||||
if (spoiler != "")
|
if (spoiler != "")
|
||||||
status.spoiler_text = Html.simplify (spoiler);
|
spoiler_text = Html.simplify (spoiler);
|
||||||
|
|
||||||
if (obj.has_member ("reblogged"))
|
if (obj.has_member ("reblogged"))
|
||||||
status.reblogged = obj.get_boolean_member ("reblogged");
|
reblogged = obj.get_boolean_member ("reblogged");
|
||||||
if (obj.has_member ("favourited"))
|
if (obj.has_member ("favourited"))
|
||||||
status.favorited = obj.get_boolean_member ("favourited");
|
favorited = obj.get_boolean_member ("favourited");
|
||||||
if (obj.has_member ("muted"))
|
if (obj.has_member ("muted"))
|
||||||
status.muted = obj.get_boolean_member ("muted");
|
muted = obj.get_boolean_member ("muted");
|
||||||
if (obj.has_member ("pinned"))
|
if (obj.has_member ("pinned"))
|
||||||
status.pinned = obj.get_boolean_member ("pinned");
|
pinned = obj.get_boolean_member ("pinned");
|
||||||
|
|
||||||
if (obj.has_member ("reblog") && obj.get_null_member("reblog") != true)
|
if (obj.has_member ("reblog") && obj.get_null_member("reblog") != true)
|
||||||
status.reblog = Status.parse (obj.get_object_member ("reblog"));
|
reblog = new Status (obj.get_object_member ("reblog"));
|
||||||
|
|
||||||
API.Mention[]? _mentions = {};
|
|
||||||
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
|
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
|
||||||
var object = node.get_object ();
|
var entity = node.get_object ();
|
||||||
if (object != null)
|
if (entity != null) {
|
||||||
_mentions += API.Mention.parse (object);
|
if (mentions == null)
|
||||||
|
mentions = new ArrayList<API.Mention> ();
|
||||||
|
mentions.add (new API.Mention (entity));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (_mentions.length > 0)
|
|
||||||
status.mentions = _mentions;
|
|
||||||
|
|
||||||
API.Attachment[]? _attachments = {};
|
|
||||||
obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => {
|
obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => {
|
||||||
var object = node.get_object ();
|
var entity = node.get_object ();
|
||||||
if (object != null)
|
if (entity != null) {
|
||||||
_attachments += API.Attachment.parse (object);
|
if (attachments == null)
|
||||||
|
attachments = new ArrayList<API.Attachment> ();
|
||||||
|
attachments.add (new API.Attachment (entity));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (_attachments.length > 0)
|
|
||||||
status.attachments = _attachments;
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Status.empty () {
|
||||||
|
Object (id: -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status.from_account (API.Account account) {
|
||||||
|
Object (
|
||||||
|
id: 0,
|
||||||
|
account: account,
|
||||||
|
created_at: account.created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
if (account.note == "")
|
||||||
|
content = "";
|
||||||
|
else if ("\n" in account.note)
|
||||||
|
content = Html.remove_tags (account.note.split ("\n")[0]);
|
||||||
|
else
|
||||||
|
content = Html.remove_tags (account.note);
|
||||||
|
}
|
||||||
|
|
||||||
public Json.Node? serialize () {
|
public Json.Node? serialize () {
|
||||||
var builder = new Json.Builder ();
|
var builder = new Json.Builder ();
|
||||||
builder.begin_object ();
|
builder.begin_object ();
|
||||||
|
@ -142,21 +168,17 @@ public class Tootle.API.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_owned (){
|
public bool is_owned (){
|
||||||
return get_formal ().account.id == accounts.current.id;
|
return formal.account.id == accounts.active.id;
|
||||||
}
|
|
||||||
|
|
||||||
public bool has_spoiler () {
|
|
||||||
return get_formal ().spoiler_text != null || get_formal ().sensitive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_reply_mentions () {
|
public string get_reply_mentions () {
|
||||||
var result = "";
|
var result = "";
|
||||||
if (account.acct != accounts.current.acct)
|
if (account.acct != accounts.active.acct)
|
||||||
result = "@%s ".printf (account.acct);
|
result = "@%s ".printf (account.acct);
|
||||||
|
|
||||||
if (mentions != null) {
|
if (mentions != null) {
|
||||||
foreach (var mention in mentions) {
|
foreach (var mention in mentions) {
|
||||||
var equals_current = mention.acct == accounts.current.acct;
|
var equals_current = mention.acct == accounts.active.acct;
|
||||||
var already_mentioned = mention.acct in result;
|
var already_mentioned = mention.acct in result;
|
||||||
|
|
||||||
if (!equals_current && ! already_mentioned)
|
if (!equals_current && ! already_mentioned)
|
||||||
|
@ -167,69 +189,29 @@ public class Tootle.API.Status {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_reblogged (bool rebl, Network.ErrorCallback? err = network.on_error) {
|
public void action (string action, owned Network.ErrorCallback? err = network.on_error) {
|
||||||
var action = rebl ? "reblog" : "unreblog";
|
new Request.POST (@"/api/v1/statuses/$(formal.id)/$action")
|
||||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.then_parse_obj (obj => {
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
var status = new API.Status (obj).formal;
|
||||||
network.queue (msg, (sess, message) => {
|
formal.reblogged = status.reblogged;
|
||||||
reblogged = rebl;
|
formal.favorited = status.favorited;
|
||||||
updated ();
|
formal.muted = status.muted;
|
||||||
}, (status, reason) => {
|
formal.pinned = status.pinned;
|
||||||
err (status, reason);
|
})
|
||||||
});
|
.on_error ((status, reason) => err (status, reason))
|
||||||
|
.exec ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_favorited (bool fav, Network.ErrorCallback? err = network.on_error) {
|
public void poof (owned Soup.SessionCallback? cb = null, owned Network.ErrorCallback? err = network.on_error) {
|
||||||
var action = fav ? "favourite" : "unfavourite";
|
new Request.DELETE (@"/api/v1/statuses/$id")
|
||||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
.with_account (accounts.active)
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
.then ((sess, msg) => {
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
streams.status_removed (id);
|
||||||
network.queue (msg, (sess, message) => {
|
cb (sess, msg);
|
||||||
favorited = fav;
|
})
|
||||||
updated ();
|
.on_error ((status, reason) => err (status, reason))
|
||||||
}, (status, reason) => {
|
.exec ();
|
||||||
err (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_muted (bool mute, Network.ErrorCallback? err = network.on_error) {
|
|
||||||
var action = mute ? "mute" : "unmute";
|
|
||||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, message) => {
|
|
||||||
muted = mute;
|
|
||||||
updated ();
|
|
||||||
}, (status, reason) => {
|
|
||||||
err (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_pinned (bool pin, Network.ErrorCallback? err = network.on_error) {
|
|
||||||
var action = pin ? "pin" : "unpin";
|
|
||||||
var msg = new Soup.Message ("POST", "%s/api/v1/statuses/%lld/%s".printf (accounts.formal.instance, id, action));
|
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, message) => {
|
|
||||||
pinned = pin;
|
|
||||||
updated ();
|
|
||||||
}, (status, reason) => {
|
|
||||||
err (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void poof (Soup.SessionCallback? cb = null, Network.ErrorCallback? err = network.on_error) {
|
|
||||||
var msg = new Soup.Message ("DELETE", "%s/api/v1/statuses/%lld".printf (accounts.formal.instance, id));
|
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, message) => {
|
|
||||||
network.status_removed (id);
|
|
||||||
if (cb != null)
|
|
||||||
cb (sess, message);
|
|
||||||
}, (status, reason) => {
|
|
||||||
err (status, reason);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
public class Tootle.API.Tag{
|
public class Tootle.API.Tag : GLib.Object {
|
||||||
|
|
||||||
public string name;
|
public string name { get; construct set; }
|
||||||
public string url;
|
public string url { get; construct set; }
|
||||||
|
|
||||||
public Tag (string _name, string _url) {
|
public Tag (Json.Object obj) {
|
||||||
name = _name;
|
Object (
|
||||||
url = _url;
|
name: obj.get_string_member ("name"),
|
||||||
}
|
url: obj.get_string_member ("url")
|
||||||
|
);
|
||||||
public static Tag parse (Json.Object obj) {
|
|
||||||
var name = obj.get_string_member ("name");
|
|
||||||
var url = obj.get_string_member ("url");
|
|
||||||
return new Tag (name, url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
public enum Tootle.API.StatusVisibility {
|
public enum Tootle.API.Visibility {
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
UNLISTED,
|
UNLISTED,
|
||||||
PRIVATE,
|
PRIVATE,
|
||||||
|
@ -6,8 +6,6 @@ public enum Tootle.API.StatusVisibility {
|
||||||
|
|
||||||
public string to_string () {
|
public string to_string () {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case PUBLIC:
|
|
||||||
return "public";
|
|
||||||
case UNLISTED:
|
case UNLISTED:
|
||||||
return "unlisted";
|
return "unlisted";
|
||||||
case PRIVATE:
|
case PRIVATE:
|
||||||
|
@ -15,29 +13,38 @@ public enum Tootle.API.StatusVisibility {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
return "direct";
|
return "direct";
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
return "public";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StatusVisibility from_string (string str) {
|
public static Visibility from_string (string str) {
|
||||||
switch (str) {
|
switch (str) {
|
||||||
case "public":
|
|
||||||
return StatusVisibility.PUBLIC;
|
|
||||||
case "unlisted":
|
case "unlisted":
|
||||||
return StatusVisibility.UNLISTED;
|
return Visibility.UNLISTED;
|
||||||
case "private":
|
case "private":
|
||||||
return StatusVisibility.PRIVATE;
|
return Visibility.PRIVATE;
|
||||||
case "direct":
|
case "direct":
|
||||||
return StatusVisibility.DIRECT;
|
return Visibility.DIRECT;
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
return Visibility.PUBLIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string get_name () {
|
||||||
|
switch (this) {
|
||||||
|
case UNLISTED:
|
||||||
|
return _("Unlisted");
|
||||||
|
case PRIVATE:
|
||||||
|
return _("Followers-only");
|
||||||
|
case DIRECT:
|
||||||
|
return _("Direct");
|
||||||
|
default:
|
||||||
|
return _("Public");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_desc () {
|
public string get_desc () {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case PUBLIC:
|
|
||||||
return _("Post to public timelines");
|
|
||||||
case UNLISTED:
|
case UNLISTED:
|
||||||
return _("Don\'t post to public timelines");
|
return _("Don\'t post to public timelines");
|
||||||
case PRIVATE:
|
case PRIVATE:
|
||||||
|
@ -45,27 +52,25 @@ public enum Tootle.API.StatusVisibility {
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
return _("Post to mentioned users only");
|
return _("Post to mentioned users only");
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
return _("Post to public timelines");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_icon () {
|
public string get_icon () {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case PUBLIC:
|
|
||||||
return "network-workgroup-symbolic";
|
|
||||||
case UNLISTED:
|
case UNLISTED:
|
||||||
return "view-private-symbolic";
|
return "changes-allow-symbolic";
|
||||||
case PRIVATE:
|
case PRIVATE:
|
||||||
return "security-medium-symbolic";
|
return "changes-prevent-symbolic";
|
||||||
case DIRECT:
|
case DIRECT:
|
||||||
return "user-available-symbolic";
|
return "user-available-symbolic";
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
return "network-workgroup-symbolic";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StatusVisibility[] get_all () {
|
public static Visibility[] all () {
|
||||||
return {StatusVisibility.PUBLIC, StatusVisibility.UNLISTED, StatusVisibility.PRIVATE, StatusVisibility.DIRECT};
|
return {Visibility.PUBLIC, Visibility.UNLISTED, Visibility.PRIVATE, Visibility.DIRECT};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,144 +0,0 @@
|
||||||
using GLib;
|
|
||||||
|
|
||||||
public class Tootle.Accounts : Object {
|
|
||||||
|
|
||||||
private string dir_path;
|
|
||||||
private string file_path;
|
|
||||||
|
|
||||||
public signal void switched (API.Account? account);
|
|
||||||
public signal void updated (GenericArray<InstanceAccount> accounts);
|
|
||||||
|
|
||||||
public GenericArray<InstanceAccount> saved_accounts = new GenericArray<InstanceAccount> ();
|
|
||||||
public InstanceAccount? formal {get; set;}
|
|
||||||
public API.Account? current {get; set;}
|
|
||||||
|
|
||||||
public Accounts () {
|
|
||||||
dir_path = "%s/%s".printf (GLib.Environment.get_user_config_dir (), app.application_id);
|
|
||||||
file_path = "%s/%s".printf (dir_path, "accounts.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void switch_account (int id) {
|
|
||||||
info ("Switching to #%i", id);
|
|
||||||
settings.current_account = id;
|
|
||||||
formal = saved_accounts.@get (id);
|
|
||||||
var msg = new Soup.Message ("GET", "%s/api/v1/accounts/verify_credentials".printf (accounts.formal.instance));
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, mess) => {
|
|
||||||
var root = network.parse (mess);
|
|
||||||
current = API.Account.parse (root);
|
|
||||||
switched (current);
|
|
||||||
updated (saved_accounts);
|
|
||||||
},
|
|
||||||
network.on_show_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add (InstanceAccount account) {
|
|
||||||
info ("Adding account for %s at %s", account.username, account.instance);
|
|
||||||
saved_accounts.add (account);
|
|
||||||
save ();
|
|
||||||
updated (saved_accounts);
|
|
||||||
switch_account (saved_accounts.length - 1);
|
|
||||||
account.start_notificator ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove (int i) {
|
|
||||||
var account = saved_accounts.@get (i);
|
|
||||||
account.close_notificator ();
|
|
||||||
|
|
||||||
saved_accounts.remove_index (i);
|
|
||||||
if (saved_accounts.length < 1)
|
|
||||||
switched (null);
|
|
||||||
else {
|
|
||||||
var id = settings.current_account - 1;
|
|
||||||
if (id > saved_accounts.length - 1)
|
|
||||||
id = saved_accounts.length - 1;
|
|
||||||
else if (id < saved_accounts.length - 1)
|
|
||||||
id = 0;
|
|
||||||
switch_account (id);
|
|
||||||
}
|
|
||||||
save ();
|
|
||||||
updated (saved_accounts);
|
|
||||||
|
|
||||||
if (is_empty ()) {
|
|
||||||
window.destroy ();
|
|
||||||
Dialogs.NewAccount.open ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool is_empty () {
|
|
||||||
return saved_accounts.length == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init () {
|
|
||||||
save (false);
|
|
||||||
load ();
|
|
||||||
|
|
||||||
if (saved_accounts.length < 1)
|
|
||||||
Dialogs.NewAccount.open ();
|
|
||||||
else
|
|
||||||
switch_account (settings.current_account);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save (bool overwrite = true) {
|
|
||||||
try {
|
|
||||||
var dir = File.new_for_path (dir_path);
|
|
||||||
if (!dir.query_exists ())
|
|
||||||
dir.make_directory ();
|
|
||||||
|
|
||||||
var file = File.new_for_path (file_path);
|
|
||||||
if (file.query_exists () && !overwrite)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var builder = new Json.Builder ();
|
|
||||||
builder.begin_array ();
|
|
||||||
saved_accounts.foreach ((acc) => {
|
|
||||||
var node = acc.serialize ();
|
|
||||||
builder.add_value (node);
|
|
||||||
});
|
|
||||||
builder.end_array ();
|
|
||||||
|
|
||||||
var generator = new Json.Generator ();
|
|
||||||
generator.set_root (builder.get_root ());
|
|
||||||
var data = generator.to_data (null);
|
|
||||||
|
|
||||||
if (file.query_exists ())
|
|
||||||
file.@delete ();
|
|
||||||
|
|
||||||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
|
||||||
stream.write (data.data);
|
|
||||||
}
|
|
||||||
catch (GLib.Error e){
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load () {
|
|
||||||
try {
|
|
||||||
uint8[] data;
|
|
||||||
string etag;
|
|
||||||
var file = File.new_for_path (file_path);
|
|
||||||
file.load_contents (null, out data, out etag);
|
|
||||||
var contents = (string) data;
|
|
||||||
|
|
||||||
var parser = new Json.Parser ();
|
|
||||||
parser.load_from_data (contents, -1);
|
|
||||||
var array = parser.get_root ().get_array ();
|
|
||||||
|
|
||||||
saved_accounts = new GenericArray<InstanceAccount> ();
|
|
||||||
array.foreach_element ((_arr, _i, node) => {
|
|
||||||
var obj = node.get_object ();
|
|
||||||
var account = InstanceAccount.parse (obj);
|
|
||||||
if (account != null) {
|
|
||||||
saved_accounts.add (account);
|
|
||||||
account.start_notificator ();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
debug ("Loaded %i saved accounts", saved_accounts.length);
|
|
||||||
updated (saved_accounts);
|
|
||||||
}
|
|
||||||
catch (GLib.Error e){
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,6 +3,12 @@ using Granite;
|
||||||
|
|
||||||
namespace Tootle {
|
namespace Tootle {
|
||||||
|
|
||||||
|
public errordomain Oopsie {
|
||||||
|
USER,
|
||||||
|
PARSING,
|
||||||
|
INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
public static Application app;
|
public static Application app;
|
||||||
public static Dialogs.MainWindow? window;
|
public static Dialogs.MainWindow? window;
|
||||||
public static Window window_dummy;
|
public static Window window_dummy;
|
||||||
|
@ -10,16 +16,23 @@ namespace Tootle {
|
||||||
public static Settings settings;
|
public static Settings settings;
|
||||||
public static Accounts accounts;
|
public static Accounts accounts;
|
||||||
public static Network network;
|
public static Network network;
|
||||||
public static ImageCache image_cache;
|
public static Cache cache;
|
||||||
public static Watchlist watchlist;
|
public static Streams streams;
|
||||||
|
|
||||||
public static bool start_hidden = false;
|
public static bool start_hidden = false;
|
||||||
|
|
||||||
public class Application : Granite.Application {
|
public class Application : Granite.Application {
|
||||||
|
|
||||||
public abstract signal void refresh ();
|
// These are used for the GTK Inspector
|
||||||
public abstract signal void toast (string title);
|
public Settings app_settings { get {return Tootle.settings; } }
|
||||||
public abstract signal void error (string title, string text);
|
public Accounts app_accounts { get {return Tootle.accounts; } }
|
||||||
|
public Network app_network { get {return Tootle.network; } }
|
||||||
|
public Cache app_cache { get {return Tootle.cache; } }
|
||||||
|
public Streams app_streams { get {return Tootle.streams; } }
|
||||||
|
|
||||||
|
public signal void refresh ();
|
||||||
|
public signal void toast (string title);
|
||||||
|
public signal void error (string title, string text);
|
||||||
|
|
||||||
public const GLib.OptionEntry[] app_options = {
|
public const GLib.OptionEntry[] app_options = {
|
||||||
{ "hidden", 0, 0, OptionArg.NONE, ref start_hidden, "Do not show main window on start", null },
|
{ "hidden", 0, 0, OptionArg.NONE, ref start_hidden, "Do not show main window on start", null },
|
||||||
|
@ -27,22 +40,20 @@ namespace Tootle {
|
||||||
};
|
};
|
||||||
|
|
||||||
public const GLib.ActionEntry[] app_entries = {
|
public const GLib.ActionEntry[] app_entries = {
|
||||||
{"compose-toot", compose_toot_activated },
|
{"compose", compose_activated },
|
||||||
{"toggle-reveal", on_sensitive_toggled },
|
|
||||||
{"back", back_activated },
|
{"back", back_activated },
|
||||||
{"refresh", refresh_activated },
|
{"refresh", refresh_activated },
|
||||||
{"switch-timeline", switch_timeline_activated, "i" }
|
{"switch-timeline", switch_timeline_activated, "i" }
|
||||||
};
|
};
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
application_id = "com.github.bleakgrey.tootle";
|
application_id = Build.DOMAIN;
|
||||||
flags = ApplicationFlags.FLAGS_NONE;
|
flags = ApplicationFlags.FLAGS_NONE;
|
||||||
program_name = "Tootle";
|
program_name = Build.NAME;
|
||||||
build_version = "0.2.0";
|
build_version = Build.VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] ACCEL_NEW_POST = {"<Ctrl>T"};
|
public string[] ACCEL_NEW_POST = {"<Ctrl>T"};
|
||||||
public string[] ACCEL_TOGGLE_REVEAL = {"<Ctrl>S"};
|
|
||||||
public string[] ACCEL_BACK = {"<Alt>BackSpace", "<Alt>Left"};
|
public string[] ACCEL_BACK = {"<Alt>BackSpace", "<Alt>Left"};
|
||||||
public string[] ACCEL_REFRESH = {"<Ctrl>R", "F5"};
|
public string[] ACCEL_REFRESH = {"<Ctrl>R", "F5"};
|
||||||
public string[] ACCEL_TIMELINE_0 = {"<Alt>1"};
|
public string[] ACCEL_TIMELINE_0 = {"<Alt>1"};
|
||||||
|
@ -52,6 +63,9 @@ namespace Tootle {
|
||||||
|
|
||||||
public static int main (string[] args) {
|
public static int main (string[] args) {
|
||||||
Gtk.init (ref args);
|
Gtk.init (ref args);
|
||||||
|
|
||||||
|
Stacktrace.register_handlers ();
|
||||||
|
//assert (true == false); // I'm not crazy. It's for stacktrace testing.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var opt_context = new OptionContext ("- Options");
|
var opt_context = new OptionContext ("- Options");
|
||||||
|
@ -71,10 +85,10 @@ namespace Tootle {
|
||||||
Granite.Services.Logger.DisplayLevel = Granite.Services.LogLevel.INFO;
|
Granite.Services.Logger.DisplayLevel = Granite.Services.LogLevel.INFO;
|
||||||
|
|
||||||
settings = new Settings ();
|
settings = new Settings ();
|
||||||
|
streams = new Streams ();
|
||||||
accounts = new Accounts ();
|
accounts = new Accounts ();
|
||||||
network = new Network ();
|
network = new Network ();
|
||||||
image_cache = new ImageCache ();
|
cache = new Cache ();
|
||||||
watchlist = new Watchlist ();
|
|
||||||
accounts.init ();
|
accounts.init ();
|
||||||
|
|
||||||
app.error.connect (app.on_error);
|
app.error.connect (app.on_error);
|
||||||
|
@ -82,8 +96,7 @@ namespace Tootle {
|
||||||
window_dummy = new Window ();
|
window_dummy = new Window ();
|
||||||
add_window (window_dummy);
|
add_window (window_dummy);
|
||||||
|
|
||||||
set_accels_for_action ("app.compose-toot", ACCEL_NEW_POST);
|
set_accels_for_action ("app.compose", ACCEL_NEW_POST);
|
||||||
set_accels_for_action ("app.toggle-reveal", ACCEL_TOGGLE_REVEAL);
|
|
||||||
set_accels_for_action ("app.back", ACCEL_BACK);
|
set_accels_for_action ("app.back", ACCEL_BACK);
|
||||||
set_accels_for_action ("app.refresh", ACCEL_REFRESH);
|
set_accels_for_action ("app.refresh", ACCEL_REFRESH);
|
||||||
set_accels_for_action ("app.switch-timeline(0)", ACCEL_TIMELINE_0);
|
set_accels_for_action ("app.switch-timeline(0)", ACCEL_TIMELINE_0);
|
||||||
|
@ -104,13 +117,9 @@ namespace Tootle {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug ("Creating new window");
|
info ("Creating new window");
|
||||||
if (accounts.is_empty ())
|
window = new Dialogs.MainWindow (this);
|
||||||
Dialogs.NewAccount.open ();
|
window.present ();
|
||||||
else {
|
|
||||||
window = new Dialogs.MainWindow (this);
|
|
||||||
window.present ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void on_error (string title, string msg){
|
protected void on_error (string title, string msg){
|
||||||
|
@ -120,12 +129,8 @@ namespace Tootle {
|
||||||
message_dialog.destroy ();
|
message_dialog.destroy ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_sensitive_toggled () {
|
private void compose_activated () {
|
||||||
window.button_reveal.clicked ();
|
new Dialogs.Compose ();
|
||||||
}
|
|
||||||
|
|
||||||
private void compose_toot_activated () {
|
|
||||||
Dialogs.Compose.open ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void back_activated () {
|
private void back_activated () {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
public class Build {
|
||||||
|
|
||||||
|
public const string NAME = "Tootle";
|
||||||
|
public const string WEBSITE = "https://github.com/bleakgrey/tootle";
|
||||||
|
public const string DOMAIN = "com.github.bleakgrey.tootle";
|
||||||
|
public const string RESOURCES = "/com/github/bleakgrey/tootle/";
|
||||||
|
public const string VERSION = "1.0.0";
|
||||||
|
|
||||||
|
}
|
|
@ -30,37 +30,42 @@ public class Tootle.Desktop {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download a file from the web to a user's configured Downloads folder
|
// Download a file from the web to a user's configured Downloads folder
|
||||||
public static void download_file (string url) {
|
public delegate void DownloadCallback (string path);
|
||||||
debug ("Downloading file: %s", url);
|
public static void download (string url, DownloadCallback? cb = null, Network.ErrorCallback? ecb = null) {
|
||||||
|
info (@"Downloading file: $url...");
|
||||||
|
|
||||||
var i = url.last_index_of ("/");
|
var i = url.last_index_of ("/");
|
||||||
var name = url.substring (i + 1, url.length - i - 1);
|
var name = url.substring (i + 1, url.length - i - 1);
|
||||||
if (name == null)
|
if (name == null)
|
||||||
name = "unknown";
|
name = _("Unknown Attachment");
|
||||||
|
|
||||||
var dir_path = "%s/%s".printf (GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD), app.program_name);
|
var downloads = GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD);
|
||||||
var file_path = "%s/%s".printf (dir_path, name);
|
var dir_path = @"$downloads/$(Build.NAME)";
|
||||||
|
var file_path = @"$dir_path/$name";
|
||||||
|
|
||||||
var msg = new Soup.Message("GET", url);
|
new Request.GET (url)
|
||||||
msg.finished.connect(() => {
|
.then ((sess, msg) => {
|
||||||
try {
|
try {
|
||||||
var dir = File.new_for_path (dir_path);
|
var dir = File.new_for_path (dir_path);
|
||||||
if (!dir.query_exists ())
|
if (!dir.query_exists ())
|
||||||
dir.make_directory ();
|
dir.make_directory ();
|
||||||
|
|
||||||
var file = File.new_for_path (file_path);
|
var file = File.new_for_path (file_path);
|
||||||
if (!file.query_exists ()) {
|
if (!file.query_exists ()) {
|
||||||
var data = msg.response_body.data;
|
var data = msg.response_body.data;
|
||||||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||||
stream.write (data);
|
stream.write (data);
|
||||||
|
}
|
||||||
|
info ("OK");
|
||||||
|
cb (file_path);
|
||||||
|
|
||||||
|
} catch (Error e) {
|
||||||
|
warning ("Error: %s\n", e.message);
|
||||||
|
ecb (0, e.message);
|
||||||
}
|
}
|
||||||
app.toast (_("Media downloaded"));
|
})
|
||||||
} catch (Error e) {
|
.on_error ((code, reason) => ecb)
|
||||||
app.toast (e.message);
|
.exec ();
|
||||||
warning ("Error: %s\n", e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
network.queue (msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string fallback_icon (string normal, string fallback) {
|
public static string fallback_icon (string normal, string fallback) {
|
||||||
|
|
|
@ -1,241 +1,145 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
public class Tootle.Dialogs.Compose : Dialog {
|
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/dialogs/compose.ui")]
|
||||||
|
public class Tootle.Dialogs.Compose : Window {
|
||||||
|
|
||||||
private static Compose dialog;
|
public API.Status? status { get; construct set; }
|
||||||
|
public string style_class { get; construct set; }
|
||||||
|
public string label { get; construct set; }
|
||||||
|
public int char_limit {
|
||||||
|
get {
|
||||||
|
return 250;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected TextView text;
|
[GtkChild]
|
||||||
private ScrolledWindow scroll;
|
protected Box box;
|
||||||
private Label counter;
|
|
||||||
private Widgets.ImageToggleButton spoiler;
|
|
||||||
private MenuButton visibility;
|
|
||||||
private Button attach;
|
|
||||||
private Button cancel;
|
|
||||||
private Button publish;
|
|
||||||
protected Widgets.AttachmentGrid attachments;
|
|
||||||
private Revealer spoiler_revealer;
|
|
||||||
private Entry spoiler_text;
|
|
||||||
|
|
||||||
protected API.Status? replying_to;
|
[GtkChild]
|
||||||
protected API.Status? redrafting;
|
protected Revealer cw_revealer;
|
||||||
protected API.StatusVisibility visibility_opt = API.StatusVisibility.PUBLIC;
|
[GtkChild]
|
||||||
protected int char_limit;
|
protected ToggleButton cw_button;
|
||||||
|
[GtkChild]
|
||||||
|
protected Entry cw;
|
||||||
|
[GtkChild]
|
||||||
|
protected Label counter;
|
||||||
|
|
||||||
public Compose (API.Status? _replying_to = null, API.Status? _redrafting = null) {
|
[GtkChild]
|
||||||
border_width = 6;
|
protected MenuButton visibility_button;
|
||||||
deletable = false;
|
[GtkChild]
|
||||||
resizable = true;
|
protected Image visibility_icon;
|
||||||
title = _("Toot");
|
protected Widgets.VisibilityPopover visibility_popover;
|
||||||
|
[GtkChild]
|
||||||
|
protected Button post_button;
|
||||||
|
|
||||||
|
[GtkChild]
|
||||||
|
protected TextView content;
|
||||||
|
|
||||||
|
construct {
|
||||||
transient_for = window;
|
transient_for = window;
|
||||||
char_limit = settings.char_limit;
|
|
||||||
replying_to = _replying_to;
|
|
||||||
redrafting = _redrafting;
|
|
||||||
|
|
||||||
if (replying_to != null)
|
post_button.label = label;
|
||||||
visibility_opt = replying_to.visibility;
|
foreach (Widget w in new Widget[] { visibility_button, post_button })
|
||||||
if (redrafting != null)
|
w.get_style_context ().add_class (style_class);
|
||||||
visibility_opt = redrafting.visibility;
|
|
||||||
|
|
||||||
var actions = get_action_area ().get_parent () as Box;
|
visibility_popover = new Widgets.VisibilityPopover.with_button (visibility_button);
|
||||||
var content = get_content_area ();
|
visibility_popover.bind_property ("selected", visibility_icon, "icon-name", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
get_action_area ().hexpand = false;
|
target.set_string (((API.Visibility)src).get_icon ());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
visibility = get_visibility_btn ();
|
cw_button.bind_property ("active", cw_revealer, "reveal_child", BindingFlags.SYNC_CREATE);
|
||||||
visibility.tooltip_text = _("Post Visibility");
|
|
||||||
visibility.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
|
||||||
visibility.get_style_context ().remove_class ("image-button");
|
|
||||||
visibility.can_default = false;
|
|
||||||
(visibility as Widget).set_focus_on_click (false);
|
|
||||||
|
|
||||||
attach = new Button.from_icon_name ("mail-attachment-symbolic");
|
cw_button.toggled.connect (validate);
|
||||||
attach.tooltip_text = _("Add Media");
|
cw.buffer.deleted_text.connect (() => validate ());
|
||||||
attach.valign = Align.CENTER;
|
cw.buffer.inserted_text.connect (() => validate ());
|
||||||
attach.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
content.buffer.changed.connect (validate);
|
||||||
attach.get_style_context ().remove_class ("image-button");
|
post_button.clicked.connect (on_post_button_clicked);
|
||||||
attach.can_default = false;
|
|
||||||
(attach as Widget).set_focus_on_click (false);
|
|
||||||
attach.clicked.connect (() => attachments.select ());
|
|
||||||
|
|
||||||
spoiler = new Widgets.ImageToggleButton ("image-red-eye-symbolic");
|
if (status.spoiler_text != null) {
|
||||||
spoiler.tooltip_text = _("Spoiler Warning");
|
cw.text = status.spoiler_text;
|
||||||
spoiler.set_action ();
|
cw_button.active = true;
|
||||||
spoiler.toggled.connect (() => {
|
|
||||||
spoiler_revealer.reveal_child = spoiler.active;
|
|
||||||
validate ();
|
|
||||||
});
|
|
||||||
|
|
||||||
cancel = add_button (_("Cancel"), 5) as Button;
|
|
||||||
cancel.clicked.connect(() => destroy ());
|
|
||||||
|
|
||||||
if (redrafting != null) {
|
|
||||||
publish = add_button (_("Redraft"), 5) as Button;
|
|
||||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
|
|
||||||
publish.clicked.connect (redraft_post);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
publish = add_button (_("Toot!"), 5) as Button;
|
|
||||||
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
|
|
||||||
publish.clicked.connect (publish_post);
|
|
||||||
}
|
}
|
||||||
|
content.buffer.text = Html.remove_tags (status.content);
|
||||||
|
|
||||||
spoiler_text = new Entry ();
|
show ();
|
||||||
spoiler_text.margin_start = 6;
|
}
|
||||||
spoiler_text.margin_end = 6;
|
|
||||||
spoiler_text.placeholder_text = _("Write your warning here");
|
|
||||||
spoiler_text.changed.connect (validate);
|
|
||||||
|
|
||||||
spoiler_revealer = new Revealer ();
|
public Compose () {
|
||||||
spoiler_revealer.add (spoiler_text);
|
Object (status: new API.Status.empty (), style_class: STYLE_CLASS_SUGGESTED_ACTION, label: _("Post"));
|
||||||
|
}
|
||||||
|
|
||||||
text = new TextView ();
|
public Compose.redraft (API.Status status) {
|
||||||
text.get_style_context ().add_class ("toot-text");
|
Object (status: status, style_class: STYLE_CLASS_DESTRUCTIVE_ACTION, label: _("Redraft"));
|
||||||
text.wrap_mode = WrapMode.WORD;
|
}
|
||||||
text.accepts_tab = false;
|
|
||||||
text.vexpand = true;
|
|
||||||
text.buffer.changed.connect (validate);
|
|
||||||
|
|
||||||
scroll = new ScrolledWindow (null, null);
|
public Compose.reply (API.Status status) {
|
||||||
scroll.hscrollbar_policy = PolicyType.NEVER;
|
var template = new API.Status.empty ();
|
||||||
scroll.min_content_height = 120;
|
template.in_reply_to_id = status.in_reply_to_id;
|
||||||
scroll.vexpand = true;
|
template.in_reply_to_account_id = status.in_reply_to_account_id;
|
||||||
scroll.propagate_natural_height = true;
|
template.content = status.formal.get_reply_mentions ();
|
||||||
scroll.margin_start = 6;
|
Object (status: template, style_class: STYLE_CLASS_SUGGESTED_ACTION, label: _("Reply"));
|
||||||
scroll.margin_end = 6;
|
visibility_popover.selected = status.visibility;
|
||||||
scroll.add (text);
|
}
|
||||||
scroll.show_all ();
|
|
||||||
|
|
||||||
attachments = new Widgets.AttachmentGrid (true);
|
protected void validate () {
|
||||||
counter = new Label ("");
|
var remain = char_limit - content.buffer.get_char_count ();
|
||||||
|
if (cw_button.active)
|
||||||
|
remain -= (int)cw.buffer.length;
|
||||||
|
|
||||||
actions.pack_start (counter, false, false, 6);
|
counter.label = remain.to_string ();
|
||||||
actions.pack_end (spoiler, false, false, 6);
|
post_button.sensitive = remain >= 0;
|
||||||
actions.pack_end (visibility, false, false, 0);
|
visibility_button.sensitive = true;
|
||||||
actions.pack_end (attach, false, false, 6);
|
box.sensitive = true;
|
||||||
content.pack_start (spoiler_revealer, false, false, 6);
|
}
|
||||||
content.pack_start (scroll, false, false, 6);
|
|
||||||
content.pack_start (attachments, false, false, 6);
|
|
||||||
content.set_size_request (350, 120);
|
|
||||||
|
|
||||||
if (replying_to != null) {
|
protected void on_error (int32 code, string reason) { //TODO: display errors
|
||||||
spoiler.active = replying_to.sensitive;
|
warning (reason);
|
||||||
var status_spoiler_text = replying_to.spoiler_text != null ? replying_to.spoiler_text : "";
|
|
||||||
spoiler_text.set_text (status_spoiler_text);
|
|
||||||
}
|
|
||||||
if (redrafting != null) {
|
|
||||||
spoiler.active = redrafting.sensitive;
|
|
||||||
var status_spoiler_text = redrafting.spoiler_text != null ? redrafting.spoiler_text : "";
|
|
||||||
spoiler_text.set_text (status_spoiler_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy.connect (() => dialog = null);
|
|
||||||
|
|
||||||
show_all ();
|
|
||||||
attachments.hide ();
|
|
||||||
text.grab_focus ();
|
|
||||||
validate ();
|
validate ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuButton get_visibility_btn () {
|
protected void on_post_button_clicked () {
|
||||||
var button = new MenuButton ();
|
post_button.sensitive = false;
|
||||||
var menu = new Popover (null);
|
visibility_button.sensitive = false;
|
||||||
var box = new Box (Orientation.VERTICAL, 6);
|
box.sensitive = false;
|
||||||
box.margin = 12;
|
|
||||||
menu.add (box);
|
|
||||||
button.direction = ArrowType.DOWN;
|
|
||||||
button.image = new Image.from_icon_name (visibility_opt.get_icon (), IconSize.BUTTON);
|
|
||||||
|
|
||||||
RadioButton? first = null;
|
if (status.id >= 0) {
|
||||||
foreach (API.StatusVisibility opt in API.StatusVisibility.get_all ()){
|
info ("Removing old status...");
|
||||||
var item = new RadioButton.with_label_from_widget (first, opt.get_desc ());
|
status.poof (publish, on_error);
|
||||||
if (first == null)
|
}
|
||||||
first = item;
|
else {
|
||||||
|
publish ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item.toggled.connect (() => {
|
protected void publish () {
|
||||||
visibility_opt = opt;
|
info ("Publishing new status...");
|
||||||
(button.image as Image).icon_name = visibility_opt.get_icon ();
|
status.content = content.buffer.text;
|
||||||
});
|
status.spoiler_text = cw.text;
|
||||||
item.active = visibility_opt == opt;
|
|
||||||
box.pack_start (item, false, false, 0);
|
var req = new Request.POST ("/api/v1/statuses")
|
||||||
|
.with_account ()
|
||||||
|
.with_param ("visibility", visibility_popover.selected.to_string ())
|
||||||
|
.with_param ("status", Html.uri_encode (status.content));
|
||||||
|
|
||||||
|
if (cw_button.active) {
|
||||||
|
req.with_param ("sensitive", "true");
|
||||||
|
req.with_param ("spoiler_text", Html.uri_encode (cw.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
box.show_all ();
|
if (status.in_reply_to_id != null)
|
||||||
button.use_popover = true;
|
req.with_param ("in_reply_to_id", status.in_reply_to_id);
|
||||||
button.popover = menu;
|
if (status.in_reply_to_account_id != null)
|
||||||
button.valign = Align.CENTER;
|
req.with_param ("in_reply_to_account_id", status.in_reply_to_account_id);
|
||||||
button.show ();
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validate () {
|
req.then ((sess, mess) => {
|
||||||
var remain = char_limit - text.buffer.get_char_count ();
|
|
||||||
if (spoiler.active)
|
|
||||||
remain -= (int)spoiler_text.buffer.length;
|
|
||||||
|
|
||||||
counter.label = remain.to_string ();
|
|
||||||
publish.sensitive = remain >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void open (string? text = null, API.Status? reply_to = null) {
|
|
||||||
if (dialog == null){
|
|
||||||
dialog = new Compose (reply_to);
|
|
||||||
|
|
||||||
if (text != null)
|
|
||||||
dialog.text.buffer.text = text;
|
|
||||||
}
|
|
||||||
else if (text != null)
|
|
||||||
dialog.text.buffer.text += text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reply (API.Status status) {
|
|
||||||
if (dialog != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
open (null, status);
|
|
||||||
dialog.text.buffer.text = status.get_reply_mentions ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void redraft (API.Status status) {
|
|
||||||
if (dialog != null)
|
|
||||||
return;
|
|
||||||
dialog = new Compose (null, status);
|
|
||||||
|
|
||||||
if (status.attachments != null) {
|
|
||||||
foreach (API.Attachment attachment in status.attachments)
|
|
||||||
dialog.attachments.append (attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = Html.simplify (status.content);
|
|
||||||
content = Html.remove_tags (content);
|
|
||||||
content = Widgets.RichLabel.restore_entities (content);
|
|
||||||
dialog.text.buffer.text = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publish_post () {
|
|
||||||
var pars = "?status=%s&visibility=%s".printf (Html.uri_encode (text.buffer.text), visibility_opt.to_string ());
|
|
||||||
pars += attachments.get_uri_array ();
|
|
||||||
if (replying_to != null)
|
|
||||||
pars += "&in_reply_to_id=%s".printf (replying_to.id.to_string ());
|
|
||||||
|
|
||||||
if (spoiler.active) {
|
|
||||||
pars += "&sensitive=true";
|
|
||||||
pars += "&spoiler_text=" + Html.uri_encode (spoiler_text.buffer.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "%s/api/v1/statuses%s".printf (accounts.formal.instance, pars);
|
|
||||||
var msg = new Soup.Message ("POST", url);
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, mess) => {
|
|
||||||
var root = network.parse (mess);
|
var root = network.parse (mess);
|
||||||
var status = API.Status.parse (root);
|
var status = new API.Status (root);
|
||||||
debug ("Posted: %s", status.id.to_string ()); //TODO: Live updates
|
info ("OK: status id is %s", status.id.to_string ());
|
||||||
destroy ();
|
destroy ();
|
||||||
});
|
})
|
||||||
}
|
.on_error (on_error)
|
||||||
|
.exec ();
|
||||||
private void redraft_post () {
|
|
||||||
redrafting.poof ((sess, msg) => {
|
|
||||||
publish_post ();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,122 +1,55 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Gdk;
|
using Gdk;
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/dialogs/main.ui")]
|
||||||
public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
||||||
|
|
||||||
private Overlay overlay;
|
[GtkChild]
|
||||||
public Granite.Widgets.Toast toast;
|
protected Stack view_stack;
|
||||||
private Grid grid;
|
[GtkChild]
|
||||||
private Stack view_stack;
|
protected Stack timeline_stack;
|
||||||
private Stack timeline_stack;
|
|
||||||
|
|
||||||
public HeaderBar header;
|
[GtkChild]
|
||||||
public Granite.Widgets.ModeButton button_mode;
|
protected HeaderBar header;
|
||||||
private Widgets.AccountsButton button_accounts;
|
[GtkChild]
|
||||||
private Spinner spinner;
|
protected Button back_button;
|
||||||
private Button button_toot;
|
[GtkChild]
|
||||||
private Button button_back;
|
protected Button compose_button;
|
||||||
public Button button_reveal;
|
[GtkChild]
|
||||||
|
protected Granite.Widgets.ModeButton timeline_switcher;
|
||||||
public Views.Home home = new Views.Home ();
|
[GtkChild]
|
||||||
public Views.Notifications notifications = new Views.Notifications ();
|
protected Widgets.AccountsButton accounts_button;
|
||||||
public Views.Local local = new Views.Local ();
|
|
||||||
public Views.Federated federated = new Views.Federated ();
|
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
|
|
||||||
var provider = new Gtk.CssProvider ();
|
var provider = new Gtk.CssProvider ();
|
||||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/app.css");
|
provider.load_from_resource (@"$(Build.RESOURCES)app.css");
|
||||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
StyleContext.add_provider_for_screen (Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
|
||||||
|
back_button.clicked.connect (() => back ());
|
||||||
|
Desktop.set_hotkey_tooltip (back_button, _("Back"), app.ACCEL_BACK);
|
||||||
|
|
||||||
|
compose_button.clicked.connect (() => new Dialogs.Compose ());
|
||||||
|
Desktop.set_hotkey_tooltip (compose_button, _("Compose"), app.ACCEL_NEW_POST);
|
||||||
|
|
||||||
|
timeline_switcher.mode_changed.connect (on_mode_changed);
|
||||||
|
|
||||||
|
add_header_view (new Views.Home (), app.ACCEL_TIMELINE_0, 0);
|
||||||
|
add_header_view (new Views.Notifications (), app.ACCEL_TIMELINE_1, 1);
|
||||||
|
add_header_view (new Views.Local (), app.ACCEL_TIMELINE_2, 2);
|
||||||
|
add_header_view (new Views.Federated (), app.ACCEL_TIMELINE_3, 3);
|
||||||
|
timeline_switcher.set_active (0);
|
||||||
|
|
||||||
|
button_press_event.connect (on_button_press);
|
||||||
settings.changed.connect (update_theme);
|
settings.changed.connect (update_theme);
|
||||||
update_theme ();
|
update_theme ();
|
||||||
|
update_header ();
|
||||||
timeline_stack = new Stack();
|
|
||||||
timeline_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
|
|
||||||
timeline_stack.show ();
|
|
||||||
view_stack = new Stack();
|
|
||||||
view_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
|
|
||||||
view_stack.show ();
|
|
||||||
view_stack.add_named (timeline_stack, "0");
|
|
||||||
view_stack.hexpand = view_stack.vexpand = true;
|
|
||||||
|
|
||||||
spinner = new Spinner ();
|
|
||||||
spinner.active = true;
|
|
||||||
|
|
||||||
button_accounts = new Widgets.AccountsButton ();
|
|
||||||
|
|
||||||
button_back = new Button ();
|
|
||||||
button_back.valign = Align.CENTER;
|
|
||||||
button_back.label = _("Back");
|
|
||||||
button_back.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
|
|
||||||
button_back.clicked.connect (() => back ());
|
|
||||||
Desktop.set_hotkey_tooltip (button_back, null, app.ACCEL_BACK);
|
|
||||||
|
|
||||||
button_toot = new Button ();
|
|
||||||
button_toot.valign = Align.CENTER;
|
|
||||||
button_toot.image = new Image.from_icon_name ("document-edit-symbolic", IconSize.LARGE_TOOLBAR);
|
|
||||||
button_toot.clicked.connect (() => Dialogs.Compose.open ());
|
|
||||||
Desktop.set_hotkey_tooltip (button_toot, _("Toot"), app.ACCEL_NEW_POST);
|
|
||||||
|
|
||||||
button_reveal = new Button ();
|
|
||||||
button_reveal.valign = Align.CENTER;
|
|
||||||
button_reveal.image = new Image.from_icon_name ("image-red-eye-symbolic", IconSize.LARGE_TOOLBAR);
|
|
||||||
Desktop.set_hotkey_tooltip (button_reveal, _("Toggle content"), app.ACCEL_TOGGLE_REVEAL);
|
|
||||||
|
|
||||||
button_mode = new Granite.Widgets.ModeButton ();
|
|
||||||
button_mode.get_style_context ().add_class ("mode");
|
|
||||||
button_mode.vexpand = true;
|
|
||||||
button_mode.valign = Align.FILL;
|
|
||||||
button_mode.mode_changed.connect (on_mode_changed);
|
|
||||||
button_mode.show ();
|
|
||||||
|
|
||||||
header = new HeaderBar ();
|
|
||||||
header.get_style_context ().add_class ("compact");
|
|
||||||
header.show_close_button = true;
|
|
||||||
header.title = _("Tootle");
|
|
||||||
header.custom_title = button_mode;
|
|
||||||
header.pack_start (button_back);
|
|
||||||
header.pack_start (button_toot);
|
|
||||||
header.pack_end (button_accounts);
|
|
||||||
header.pack_end (button_reveal);
|
|
||||||
header.pack_end (spinner);
|
|
||||||
header.show_all ();
|
|
||||||
|
|
||||||
grid = new Grid ();
|
|
||||||
grid.attach (view_stack, 0, 0, 1, 1);
|
|
||||||
|
|
||||||
add_header_view (home, app.ACCEL_TIMELINE_0, 0);
|
|
||||||
add_header_view (notifications, app.ACCEL_TIMELINE_1, 1);
|
|
||||||
add_header_view (local, app.ACCEL_TIMELINE_2, 2);
|
|
||||||
add_header_view (federated, app.ACCEL_TIMELINE_3, 3);
|
|
||||||
button_mode.set_active (0);
|
|
||||||
|
|
||||||
toast = new Granite.Widgets.Toast ("");
|
|
||||||
overlay = new Overlay ();
|
|
||||||
overlay.add_overlay (grid);
|
|
||||||
overlay.add_overlay (toast);
|
|
||||||
overlay.set_size_request (450, 600);
|
|
||||||
add (overlay);
|
|
||||||
|
|
||||||
restore_state ();
|
restore_state ();
|
||||||
show_all ();
|
|
||||||
|
|
||||||
button_reveal.hide ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainWindow (Gtk.Application _app) {
|
public MainWindow (Gtk.Application app) {
|
||||||
application = _app;
|
Object (application: app, icon_name: Build.DOMAIN, resizable: true, window_position: WindowPosition.CENTER);
|
||||||
icon_name = "com.github.bleakgrey.tootle";
|
if (accounts.is_empty ())
|
||||||
resizable = true;
|
open_view (new Views.NewAccount (false));
|
||||||
window_position = WindowPosition.CENTER;
|
|
||||||
set_titlebar (header);
|
|
||||||
update_header ();
|
|
||||||
|
|
||||||
app.toast.connect (on_toast);
|
|
||||||
network.started.connect (() => spinner.show ());
|
|
||||||
network.finished.connect (() => spinner.hide ());
|
|
||||||
accounts.updated (accounts.saved_accounts);
|
|
||||||
button_press_event.connect (on_button_press);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_button_press (EventButton ev) {
|
private bool on_button_press (EventButton ev) {
|
||||||
|
@ -125,22 +58,19 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add_header_view (Views.Abstract view, string[] accelerators, int32 num) {
|
private void add_header_view (Views.Base view, string[] accelerators, int32 num) {
|
||||||
var img = new Image.from_icon_name (view.get_icon (), IconSize.LARGE_TOOLBAR);
|
var img = new Image.from_icon_name (view.get_icon (), IconSize.LARGE_TOOLBAR);
|
||||||
Desktop.set_hotkey_tooltip (img, view.get_name (), accelerators);
|
Desktop.set_hotkey_tooltip (img, view.get_name (), accelerators);
|
||||||
button_mode.append (img);
|
timeline_switcher.append (img);
|
||||||
view.image = img;
|
view.image = img;
|
||||||
timeline_stack.add_named (view, num.to_string ());
|
timeline_stack.add_named (view, num.to_string ());
|
||||||
|
|
||||||
if (view is Views.Notifications)
|
|
||||||
img.pixel_size = 20; // For some reason Notifications icon is too small without this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int get_visible_id () {
|
public int get_visible_id () {
|
||||||
return int.parse (view_stack.get_visible_child_name ());
|
return int.parse (view_stack.get_visible_child_name ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool open_view (Views.Abstract widget) {
|
public bool open_view (Views.Base widget) {
|
||||||
var i = get_visible_id ();
|
var i = get_visible_id ();
|
||||||
i++;
|
i++;
|
||||||
widget.stack_pos = i;
|
widget.stack_pos = i;
|
||||||
|
@ -171,7 +101,7 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool delete_event (Gdk.EventAny event) {
|
public override bool delete_event (EventAny event) {
|
||||||
destroy.connect (() => {
|
destroy.connect (() => {
|
||||||
if (!settings.always_online || accounts.is_empty ())
|
if (!settings.always_online || accounts.is_empty ())
|
||||||
app.remove_window (window_dummy);
|
app.remove_window (window_dummy);
|
||||||
|
@ -181,39 +111,28 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switch_timeline (int32 timeline_no) {
|
public void switch_timeline (int32 timeline_no) {
|
||||||
button_mode.set_active (timeline_no);
|
timeline_switcher.set_active (timeline_no);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_theme () {
|
private void update_theme () {
|
||||||
var provider = new Gtk.CssProvider ();
|
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = settings.dark_theme;
|
||||||
var is_dark = settings.dark_theme;
|
|
||||||
var theme = is_dark ? "dark" : "light";
|
|
||||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/%s.css".printf (theme));
|
|
||||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
||||||
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = is_dark;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_header () {
|
private void update_header () {
|
||||||
bool primary_mode = get_visible_id () == 0;
|
bool primary_mode = get_visible_id () == 0;
|
||||||
button_mode.sensitive = primary_mode;
|
timeline_switcher.sensitive = primary_mode;
|
||||||
button_mode.opacity = primary_mode ? 1 : 0; //Prevent HeaderBar height jitter
|
timeline_switcher.opacity = primary_mode ? 1 : 0; //Prevent HeaderBar height jitter
|
||||||
button_toot.set_visible (primary_mode);
|
compose_button.visible = primary_mode;
|
||||||
button_back.set_visible (!primary_mode);
|
back_button.visible = !primary_mode;
|
||||||
button_accounts.set_visible (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_toast (string msg){
|
|
||||||
toast.title = msg;
|
|
||||||
toast.send_notification ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_mode_changed (Widget widget) {
|
private void on_mode_changed (Widget widget) {
|
||||||
var visible = timeline_stack.get_visible_child () as Views.Abstract;
|
var visible = timeline_stack.get_visible_child () as Views.Base;
|
||||||
visible.current = false;
|
visible.current = false;
|
||||||
|
|
||||||
timeline_stack.set_visible_child_name (button_mode.selected.to_string ());
|
timeline_stack.set_visible_child_name (timeline_switcher.selected.to_string ());
|
||||||
|
|
||||||
visible = timeline_stack.get_visible_child () as Views.Abstract;
|
visible = timeline_stack.get_visible_child () as Views.Base;
|
||||||
visible.current = true;
|
visible.current = true;
|
||||||
visible.on_set_current ();
|
visible.on_set_current ();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
|
|
||||||
public class Tootle.Dialogs.NewAccount : Dialog {
|
|
||||||
|
|
||||||
private static NewAccount dialog;
|
|
||||||
|
|
||||||
private Grid grid;
|
|
||||||
private Button button_done;
|
|
||||||
private Image logo;
|
|
||||||
private Entry instance_entry;
|
|
||||||
private Label instance_register;
|
|
||||||
private Label code_name;
|
|
||||||
private Entry code_entry;
|
|
||||||
|
|
||||||
private string? instance;
|
|
||||||
private string? client_id;
|
|
||||||
private string? client_secret;
|
|
||||||
private string? code;
|
|
||||||
private string? token;
|
|
||||||
private string? username;
|
|
||||||
|
|
||||||
public NewAccount () {
|
|
||||||
border_width = 6;
|
|
||||||
deletable = true;
|
|
||||||
resizable = false;
|
|
||||||
title = _("New Account");
|
|
||||||
transient_for = window;
|
|
||||||
|
|
||||||
logo = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128");
|
|
||||||
logo.halign = Align.CENTER;
|
|
||||||
logo.hexpand = true;
|
|
||||||
logo.margin_bottom = 24;
|
|
||||||
|
|
||||||
instance_entry = new Entry ();
|
|
||||||
instance_entry.width_chars = 30;
|
|
||||||
|
|
||||||
instance_register = new Label ("<a href=\"https://joinmastodon.org/\">%s</a>".printf (_("What's an instance?")));
|
|
||||||
instance_register.halign = Align.END;
|
|
||||||
instance_register.set_use_markup (true);
|
|
||||||
|
|
||||||
code_name = new Widgets.AlignedLabel (_("Code:"));
|
|
||||||
|
|
||||||
code_entry = new Entry ();
|
|
||||||
code_entry.secondary_icon_name = "dialog-question-symbolic";
|
|
||||||
code_entry.secondary_icon_tooltip_text = _("Paste your instance authorization code here");
|
|
||||||
code_entry.secondary_icon_activatable = false;
|
|
||||||
|
|
||||||
button_done = new Button.with_label (_("Add Account"));
|
|
||||||
button_done.clicked.connect (on_done_clicked);
|
|
||||||
button_done.halign = Align.END;
|
|
||||||
button_done.margin_top = 24;
|
|
||||||
|
|
||||||
grid = new Grid ();
|
|
||||||
grid.column_spacing = 12;
|
|
||||||
grid.row_spacing = 6;
|
|
||||||
grid.hexpand = true;
|
|
||||||
grid.halign = Align.CENTER;
|
|
||||||
grid.attach (logo, 0, 0, 2, 1);
|
|
||||||
grid.attach (new Widgets.AlignedLabel (_("Instance:")), 0, 1);
|
|
||||||
grid.attach (instance_entry, 1, 1);
|
|
||||||
grid.attach (code_name, 0, 3);
|
|
||||||
grid.attach (code_entry, 1, 3);
|
|
||||||
grid.attach (instance_register, 1, 5);
|
|
||||||
grid.attach (button_done, 1, 10);
|
|
||||||
|
|
||||||
var content = get_content_area () as Box;
|
|
||||||
content.pack_start (grid, false, false, 0);
|
|
||||||
|
|
||||||
destroy.connect (() => {
|
|
||||||
dialog = null;
|
|
||||||
|
|
||||||
if (accounts.is_empty ())
|
|
||||||
app.remove_window (window_dummy);
|
|
||||||
});
|
|
||||||
|
|
||||||
show_all ();
|
|
||||||
clear ();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear () {
|
|
||||||
code_name.hide ();
|
|
||||||
code_entry.hide ();
|
|
||||||
code_entry.text = "";
|
|
||||||
client_id = client_secret = code = token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_done_clicked () {
|
|
||||||
instance = "https://" + instance_entry.text
|
|
||||||
.replace ("/", "")
|
|
||||||
.replace (":", "")
|
|
||||||
.replace ("https", "")
|
|
||||||
.replace ("http", "");
|
|
||||||
code = code_entry.text;
|
|
||||||
|
|
||||||
if (client_id == null || client_secret == null) {
|
|
||||||
request_client_tokens ();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code == "")
|
|
||||||
app.error (_("Error"), _("Please paste valid instance authorization code"));
|
|
||||||
else
|
|
||||||
try_auth (code);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void request_client_tokens (){
|
|
||||||
var pars = "?client_name=Tootle";
|
|
||||||
pars += "&redirect_uris=urn:ietf:wg:oauth:2.0:oob";
|
|
||||||
pars += "&website=https://github.com/bleakgrey/tootle";
|
|
||||||
pars += "&scopes=read%20write%20follow";
|
|
||||||
|
|
||||||
grid.sensitive = false;
|
|
||||||
var message = new Soup.Message ("POST", "%s/api/v1/apps%s".printf (instance, pars));
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
grid.sensitive = true;
|
|
||||||
|
|
||||||
var root = network.parse (msg);
|
|
||||||
var id = root.get_string_member ("client_id");
|
|
||||||
var secret = root.get_string_member ("client_secret");
|
|
||||||
client_id = id;
|
|
||||||
client_secret = secret;
|
|
||||||
|
|
||||||
info ("Received tokens from %s", instance);
|
|
||||||
request_auth_code ();
|
|
||||||
code_name.show ();
|
|
||||||
code_entry.show ();
|
|
||||||
}, (status, reason) => {
|
|
||||||
network.on_show_error (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void request_auth_code (){
|
|
||||||
var pars = "?scope=read%20write%20follow";
|
|
||||||
pars += "&response_type=code";
|
|
||||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
|
||||||
pars += "&client_id=" + client_id;
|
|
||||||
|
|
||||||
info ("Requesting auth token");
|
|
||||||
Desktop.open_uri ("%s/oauth/authorize%s".printf (instance, pars));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void try_auth (string code){
|
|
||||||
var pars = "?client_id=" + client_id;
|
|
||||||
pars += "&client_secret=" + client_secret;
|
|
||||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
|
||||||
pars += "&grant_type=authorization_code";
|
|
||||||
pars += "&code=" + code;
|
|
||||||
|
|
||||||
var message = new Soup.Message ("POST", "%s/oauth/token%s".printf (instance, pars));
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
var root = network.parse (msg);
|
|
||||||
token = root.get_string_member ("access_token");
|
|
||||||
|
|
||||||
info ("Got access token");
|
|
||||||
get_username ();
|
|
||||||
}, (status, reason) => {
|
|
||||||
network.on_show_error (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void get_username () {
|
|
||||||
var message = new Soup.Message("GET", "%s/api/v1/accounts/verify_credentials".printf (instance));
|
|
||||||
message.request_headers.append ("Authorization", "Bearer " + token);
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
var root = network.parse (msg);
|
|
||||||
username = root.get_string_member ("username");
|
|
||||||
add_account ();
|
|
||||||
window.show ();
|
|
||||||
window.present ();
|
|
||||||
destroy ();
|
|
||||||
}, (status, reason) => {
|
|
||||||
network.on_show_error (status, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add_account () {
|
|
||||||
var account = new InstanceAccount ();
|
|
||||||
account.username = username;
|
|
||||||
account.instance = instance;
|
|
||||||
account.client_id = client_id;
|
|
||||||
account.client_secret = client_secret;
|
|
||||||
account.token = token;
|
|
||||||
accounts.add (account);
|
|
||||||
app.activate ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void open () {
|
|
||||||
if (dialog == null)
|
|
||||||
dialog = new NewAccount ();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -88,7 +88,7 @@ public class Tootle.Dialogs.Preferences : Dialog {
|
||||||
halign = Align.START;
|
halign = Align.START;
|
||||||
valign = Align.CENTER;
|
valign = Align.CENTER;
|
||||||
margin_bottom = 6;
|
margin_bottom = 6;
|
||||||
settings.schema.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
settings.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,206 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
using Gee;
|
|
||||||
|
|
||||||
public class Tootle.Dialogs.WatchlistEditor : Dialog {
|
|
||||||
|
|
||||||
private static WatchlistEditor dialog;
|
|
||||||
|
|
||||||
private StackSwitcher switcher;
|
|
||||||
private MenuButton button_add;
|
|
||||||
private Button button_remove;
|
|
||||||
private Stack stack;
|
|
||||||
private ListStack users;
|
|
||||||
private ListStack hashtags;
|
|
||||||
private ActionBar actionbar;
|
|
||||||
private Popover popover;
|
|
||||||
private Grid popover_grid;
|
|
||||||
private Entry popover_entry;
|
|
||||||
private Button popover_button;
|
|
||||||
|
|
||||||
private const string TIP_USERS = _("You'll be notified when toots from this user appear in your Home timeline.");
|
|
||||||
private const string TIP_HASHTAGS = _("You'll be notified when toots with this hashtag appear in any public timelines.");
|
|
||||||
|
|
||||||
private class ModelItem : GLib.Object {
|
|
||||||
public string name;
|
|
||||||
|
|
||||||
public ModelItem (string name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ModelView : ListBoxRow {
|
|
||||||
public Label label;
|
|
||||||
public ModelView (ModelItem item) {
|
|
||||||
label = new Label (item.name);
|
|
||||||
label.margin = 6;
|
|
||||||
label.halign = Align.START;
|
|
||||||
label.justify = Justification.LEFT;
|
|
||||||
add (label);
|
|
||||||
show_all ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Model : GLib.ListModel, GLib.Object {
|
|
||||||
private GenericArray<ModelItem> items = new GenericArray<ModelItem> ();
|
|
||||||
|
|
||||||
public GLib.Type get_item_type () {
|
|
||||||
return typeof (ModelItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint get_n_items () {
|
|
||||||
return items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GLib.Object? get_item (uint position) {
|
|
||||||
return items.@get ((int)position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append (ModelItem item) {
|
|
||||||
this.items.add (item);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Widget create_row (GLib.Object obj) {
|
|
||||||
var item = (ModelItem) obj;
|
|
||||||
return new ModelView (item);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ListStack : ScrolledWindow {
|
|
||||||
public Model model;
|
|
||||||
public ListBox list;
|
|
||||||
|
|
||||||
public void update (ArrayList<string> array) {
|
|
||||||
array.@foreach (item => {
|
|
||||||
model.append (new ModelItem (item));
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
list.bind_model (model, create_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListStack (ArrayList<string> array) {
|
|
||||||
model = new Model ();
|
|
||||||
list = new ListBox ();
|
|
||||||
add (list);
|
|
||||||
update (array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_tip () {
|
|
||||||
var is_user = stack.visible_child_name == "users";
|
|
||||||
popover_entry.secondary_icon_tooltip_text = is_user ? TIP_USERS : TIP_HASHTAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WatchlistEditor () {
|
|
||||||
border_width = 6;
|
|
||||||
deletable = false;
|
|
||||||
resizable = false;
|
|
||||||
transient_for = window;
|
|
||||||
title = _("Watchlist");
|
|
||||||
|
|
||||||
users = new ListStack (watchlist.users);
|
|
||||||
hashtags = new ListStack (watchlist.hashtags);
|
|
||||||
|
|
||||||
stack = new Stack ();
|
|
||||||
stack.transition_type = StackTransitionType.SLIDE_LEFT_RIGHT;
|
|
||||||
stack.hexpand = true;
|
|
||||||
stack.vexpand = true;
|
|
||||||
stack.add_titled (users, "users", _("Users"));
|
|
||||||
stack.add_titled (hashtags, "hashtags", _("Hashtags"));
|
|
||||||
|
|
||||||
switcher = new StackSwitcher ();
|
|
||||||
switcher.stack = stack;
|
|
||||||
switcher.halign = Align.CENTER;
|
|
||||||
switcher.margin_bottom = 12;
|
|
||||||
|
|
||||||
popover_entry = new Entry ();
|
|
||||||
popover_entry.hexpand = true;
|
|
||||||
popover_entry.secondary_icon_name = "dialog-information-symbolic";
|
|
||||||
popover_entry.secondary_icon_activatable = false;
|
|
||||||
popover_entry.activate.connect (() => submit ());
|
|
||||||
|
|
||||||
popover_button = new Button.with_label (_("Add"));
|
|
||||||
popover_button.halign = Align.END;
|
|
||||||
popover_button.margin_start = 6;
|
|
||||||
popover_button.clicked.connect (() => submit ());
|
|
||||||
|
|
||||||
popover_grid = new Grid ();
|
|
||||||
popover_grid.margin = 6;
|
|
||||||
popover_grid.attach (popover_entry, 0, 0);
|
|
||||||
popover_grid.attach (popover_button, 1, 0);
|
|
||||||
popover_grid.show_all ();
|
|
||||||
|
|
||||||
popover = new Popover (null);
|
|
||||||
popover.add (popover_grid);
|
|
||||||
|
|
||||||
button_add = new MenuButton ();
|
|
||||||
button_add.image = new Image.from_icon_name ("list-add-symbolic", IconSize.BUTTON);
|
|
||||||
button_add.popover = popover;
|
|
||||||
button_add.clicked.connect (() => set_tip ());
|
|
||||||
|
|
||||||
button_remove = new Button ();
|
|
||||||
button_remove.image = new Image.from_icon_name ("list-remove-symbolic", IconSize.BUTTON);
|
|
||||||
button_remove.clicked.connect (on_remove);
|
|
||||||
|
|
||||||
actionbar = new ActionBar ();
|
|
||||||
actionbar.add (button_add);
|
|
||||||
actionbar.add (button_remove);
|
|
||||||
|
|
||||||
var grid = new Grid ();
|
|
||||||
grid.attach (stack, 0, 1);
|
|
||||||
grid.attach (actionbar, 0, 2);
|
|
||||||
|
|
||||||
var frame = new Frame (null);
|
|
||||||
frame.margin_bottom = 6;
|
|
||||||
frame.add (grid);
|
|
||||||
frame.set_size_request (350, 350);
|
|
||||||
|
|
||||||
var content = get_content_area ();
|
|
||||||
content.pack_start (switcher, true, true, 0);
|
|
||||||
content.pack_start (frame, true, true, 0);
|
|
||||||
|
|
||||||
add_button (_("_Close"), ResponseType.DELETE_EVENT);
|
|
||||||
show_all ();
|
|
||||||
|
|
||||||
response.connect (on_response);
|
|
||||||
destroy.connect (() => dialog = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_response (int i) {
|
|
||||||
destroy ();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_remove () {
|
|
||||||
var is_hashtag = stack.visible_child_name == "hashtags";
|
|
||||||
ListStack stack = is_hashtag ? hashtags : users;
|
|
||||||
stack.list.get_selected_rows ().@foreach (_row => {
|
|
||||||
var row = _row as ModelView;
|
|
||||||
watchlist.remove (row.label.label, is_hashtag);
|
|
||||||
watchlist.save ();
|
|
||||||
row.destroy ();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void submit () {
|
|
||||||
if (popover_entry.text_length < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var is_hashtag = stack.visible_child_name == "hashtags";
|
|
||||||
var entity = popover_entry.text
|
|
||||||
.replace ("#", "")
|
|
||||||
.replace (" ", "");
|
|
||||||
|
|
||||||
watchlist.add (entity, is_hashtag);
|
|
||||||
watchlist.save ();
|
|
||||||
button_add.active = false;
|
|
||||||
|
|
||||||
var stack = is_hashtag ? hashtags : users;
|
|
||||||
stack.list.insert (create_row (new ModelItem (entity)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void open () {
|
|
||||||
if (dialog == null)
|
|
||||||
dialog = new WatchlistEditor ();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -13,23 +13,24 @@ public class Tootle.Drawing {
|
||||||
ctx.close_path ();
|
ctx.close_path ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pixbuf make_pixbuf_thumbnail (Pixbuf pixbuf, int view_w, int view_h, bool fill_parent = false) {
|
public static void center (Cairo.Context ctx, int w, int h, int tw, int th) {
|
||||||
// Don't resize if parent view is bigger than actual image
|
var cx = w/2 - tw/2;
|
||||||
if (view_w >= pixbuf.width && view_h >= pixbuf.height)
|
var cy = h/2 - th/2;
|
||||||
return pixbuf;
|
ctx.translate (cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
//Otherwise fit the image into the parent view
|
public static Pixbuf make_thumbnail (Pixbuf pb, int view_w, int view_h) {
|
||||||
var resized_w = view_w;
|
if (view_w >= pb.width && view_h >= pb.height)
|
||||||
var resized_h = view_h;
|
return pb;
|
||||||
//resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
|
||||||
//resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
|
||||||
|
|
||||||
if (fill_parent)
|
double ratio_x = (double) view_w / (double) pb.width;
|
||||||
resized_h = (pixbuf.height * view_w) / pixbuf.width;
|
double ratio_y = (double) view_h / (double) pb.height;
|
||||||
else
|
double ratio = ratio_x < ratio_y ? ratio_x : ratio_y;
|
||||||
resized_w = (pixbuf.width * view_h) / pixbuf.height;
|
|
||||||
|
|
||||||
return pixbuf.scale_simple (resized_w, resized_h, InterpType.BILINEAR);
|
return pb.scale_simple (
|
||||||
|
(int) (pb.width * ratio),
|
||||||
|
(int) (pb.height * ratio),
|
||||||
|
InterpType.BILINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,46 @@
|
||||||
public class Tootle.Html {
|
public class Tootle.Html {
|
||||||
|
|
||||||
public static string remove_tags (string content) {
|
public static string remove_tags (string content) {
|
||||||
var all_tags = new Regex("<(.|\n)*?>", RegexCompileFlags.CASELESS);
|
var all_tags = new Regex ("<(.|\n)*?>", RegexCompileFlags.CASELESS);
|
||||||
return all_tags.replace(content, -1, 0, "");
|
return GLib.Markup.escape_text (all_tags.replace (content, -1, 0, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string simplify (string content) {
|
public static string escape_pango_entities (string str) {
|
||||||
var divided = content
|
return str
|
||||||
|
.replace (" ", " ")
|
||||||
|
.replace ("'", "'")
|
||||||
|
.replace ("& ", "&");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string restore_entities (string str) {
|
||||||
|
return str
|
||||||
|
.replace ("&", "&")
|
||||||
|
.replace ("<", "<")
|
||||||
|
.replace (">", ">")
|
||||||
|
.replace ("'", "'")
|
||||||
|
.replace (""", "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string simplify (string str) {
|
||||||
|
var divided = str
|
||||||
.replace("<br>", "\n")
|
.replace("<br>", "\n")
|
||||||
.replace("</br>", "")
|
.replace("</br>", "")
|
||||||
.replace("<br />", "\n")
|
.replace("<br />", "\n")
|
||||||
.replace("<p>", "")
|
.replace("<p>", "")
|
||||||
.replace("</p>", "\n\n");
|
.replace("</p>", "\n\n");
|
||||||
|
|
||||||
var html_params = new Regex("(class|target|rel)=\"(.|\n)*?\"", RegexCompileFlags.CASELESS);
|
var html_params = new Regex ("(class|target|rel)=\"(.|\n)*?\"", RegexCompileFlags.CASELESS);
|
||||||
var simplified = html_params.replace(divided, -1, 0, "");
|
var simplified = html_params.replace (divided, -1, 0, "");
|
||||||
|
|
||||||
while (simplified.has_suffix ("\n"))
|
while (simplified.has_suffix ("\n"))
|
||||||
simplified = simplified.slice (0, simplified.last_index_of ("\n"));
|
simplified = simplified.slice (0, simplified.last_index_of ("\n"));
|
||||||
|
|
||||||
return simplified;
|
return escape_pango_entities (simplified);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string uri_encode (string content) {
|
public static string uri_encode (string str) {
|
||||||
var to_escape = ";&+";
|
var restored = restore_entities (str);
|
||||||
return Soup.URI.encode (content, to_escape);
|
return Soup.URI.encode (restored, ";&+");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
using Soup;
|
|
||||||
using GLib;
|
|
||||||
using Gdk;
|
|
||||||
using Json;
|
|
||||||
|
|
||||||
private struct CachedImage {
|
|
||||||
|
|
||||||
public string uri;
|
|
||||||
public int size;
|
|
||||||
|
|
||||||
public CachedImage (string _uri, int _size) {
|
|
||||||
uri = _uri;
|
|
||||||
size = _size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint hash(CachedImage? c) {
|
|
||||||
assert (c != null);
|
|
||||||
assert (c.uri != null);
|
|
||||||
return GLib.int64_hash (c.size) ^ c.uri.hash ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool equal (CachedImage? a, CachedImage? b) {
|
|
||||||
if (a == null || b == null)
|
|
||||||
return false;
|
|
||||||
return a.size == b.size && a.uri == b.uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate void PixbufCallback (Gdk.Pixbuf pb);
|
|
||||||
|
|
||||||
public class Tootle.ImageCache : GLib.Object {
|
|
||||||
|
|
||||||
private GLib.HashTable<CachedImage?, Soup.Message> in_progress;
|
|
||||||
private GLib.HashTable<CachedImage?, Gdk.Pixbuf> pixbufs;
|
|
||||||
private uint total_size_est;
|
|
||||||
private uint size_limit;
|
|
||||||
private string cache_path;
|
|
||||||
|
|
||||||
construct {
|
|
||||||
pixbufs = new GLib.HashTable<CachedImage?, Gdk.Pixbuf> (CachedImage.hash, CachedImage.equal);
|
|
||||||
in_progress = new GLib.HashTable<CachedImage?, Soup.Message> (CachedImage.hash, CachedImage.equal);
|
|
||||||
total_size_est = 0;
|
|
||||||
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), app.application_id);
|
|
||||||
|
|
||||||
settings.changed.connect (on_settings_changed);
|
|
||||||
on_settings_changed ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageCache() {}
|
|
||||||
|
|
||||||
private void on_settings_changed () {
|
|
||||||
// assume 32BPP (divide bytes by 4 to get # pixels) and raw, overhead-free storage
|
|
||||||
// cache_size setting is number of megabytes
|
|
||||||
size_limit = (1024 * 1024 * settings.cache_size) / 4;
|
|
||||||
if (settings.cache)
|
|
||||||
enforce_size_limit ();
|
|
||||||
else
|
|
||||||
remove_all ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove_all () {
|
|
||||||
debug("Image cache cleared");
|
|
||||||
pixbufs.remove_all ();
|
|
||||||
total_size_est = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove_one (string uri, int size) {
|
|
||||||
CachedImage ci = CachedImage (uri, size);
|
|
||||||
bool removed = pixbufs.remove(ci);
|
|
||||||
if (removed) {
|
|
||||||
assert (total_size_est >= size * size);
|
|
||||||
total_size_est -= size * size;
|
|
||||||
debug("Cache usage: %zd", total_size_est);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: fix me
|
|
||||||
// remove least used image
|
|
||||||
private void remove_least_used () {
|
|
||||||
var keys = pixbufs.get_keys();
|
|
||||||
if (keys.first() != null) {
|
|
||||||
var ci = keys.first().data;
|
|
||||||
remove_one(ci.uri, ci.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enforce_size_limit () {
|
|
||||||
debug("Updating size limit (%zd/%zd)", total_size_est, size_limit);
|
|
||||||
while (total_size_est > size_limit && pixbufs.size() > 0)
|
|
||||||
remove_least_used ();
|
|
||||||
|
|
||||||
assert (total_size_est <= size_limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void store_pixbuf (CachedImage ci, Gdk.Pixbuf pixbuf) {
|
|
||||||
assert (!pixbufs.contains (ci));
|
|
||||||
pixbufs.insert (ci, pixbuf);
|
|
||||||
in_progress.remove (ci);
|
|
||||||
total_size_est += ci.size * ci.size;
|
|
||||||
enforce_size_limit ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void get_image (string uri, int size, owned PixbufCallback? cb = null) {
|
|
||||||
CachedImage ci = CachedImage (uri, size);
|
|
||||||
Gdk.Pixbuf? pb = pixbufs.get(ci);
|
|
||||||
if (pb != null) {
|
|
||||||
cb (pb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Soup.Message? msg = in_progress.get(ci);
|
|
||||||
if (msg == null) {
|
|
||||||
msg = new Soup.Message("GET", uri);
|
|
||||||
ulong id = 0;
|
|
||||||
id = msg.finished.connect(() => {
|
|
||||||
debug("Caching %s@%d", uri, size);
|
|
||||||
var data = msg.response_body.data;
|
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
|
||||||
store_pixbuf(ci, pixbuf);
|
|
||||||
cb(pixbuf);
|
|
||||||
msg.disconnect(id);
|
|
||||||
});
|
|
||||||
in_progress[ci] = msg;
|
|
||||||
network.queue (msg);
|
|
||||||
} else {
|
|
||||||
ulong id = 0;
|
|
||||||
id = msg.finished.connect(() => {
|
|
||||||
cb(pixbufs[ci]);
|
|
||||||
msg.disconnect(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load_avatar (string uri, Granite.Widgets.Avatar avatar, int size) {
|
|
||||||
get_image.begin (uri, size, (pixbuf) => avatar.pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load_image (string uri, Gtk.Image image) {
|
|
||||||
load_scaled_image (uri, image, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load_scaled_image (string uri, Gtk.Image image, int size) {
|
|
||||||
get_image.begin (uri, size, image.set_from_pixbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +1,89 @@
|
||||||
using GLib;
|
using GLib;
|
||||||
using Gee;
|
using Gee;
|
||||||
|
|
||||||
public class Tootle.InstanceAccount : Object {
|
public class Tootle.InstanceAccount : API.Account, IStreamListener {
|
||||||
|
|
||||||
public string username {get; set;}
|
public string instance { get; set; }
|
||||||
public string instance {get; set;}
|
public string client_id { get; set; }
|
||||||
public string client_id {get; set;}
|
public string client_secret { get; set; }
|
||||||
public string client_secret {get; set;}
|
public string token { get; set; }
|
||||||
public string token {get; set;}
|
|
||||||
|
|
||||||
public int64 last_seen_notification {get; set; default = 0;}
|
public int64 last_seen_notification { get; set; default = 0; }
|
||||||
public bool has_unread_notifications {get; set; default = false;}
|
public bool has_unread_notifications { get; set; default = false; }
|
||||||
public ArrayList<API.Notification> cached_notifications {get; set;}
|
public ArrayList<API.Notification> cached_notifications { get; set; default = new ArrayList<API.Notification> (); }
|
||||||
|
|
||||||
private Notificator? notificator;
|
protected string? stream;
|
||||||
|
|
||||||
public InstanceAccount () {
|
public string handle {
|
||||||
cached_notifications = new ArrayList<API.Notification> ();
|
owned get { return @"@$username@$short_instance"; }
|
||||||
|
}
|
||||||
|
public string short_instance {
|
||||||
|
owned get {
|
||||||
|
return instance
|
||||||
|
.replace ("https://", "")
|
||||||
|
.replace ("/","");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_pretty_instance () {
|
public InstanceAccount (Json.Object obj) {
|
||||||
return instance
|
Object (
|
||||||
.replace ("https://", "")
|
username: obj.get_string_member ("username"),
|
||||||
.replace ("/","");
|
instance: obj.get_string_member ("instance"),
|
||||||
|
client_id: obj.get_string_member ("id"),
|
||||||
|
client_secret: obj.get_string_member ("secret"),
|
||||||
|
token: obj.get_string_member ("access_token"),
|
||||||
|
last_seen_notification: obj.get_int_member ("last_seen_notification"),
|
||||||
|
has_unread_notifications: obj.get_boolean_member ("has_unread_notifications")
|
||||||
|
);
|
||||||
|
|
||||||
|
var cached = obj.get_object_member ("cached_profile");
|
||||||
|
var account = new API.Account (cached);
|
||||||
|
patch (account);
|
||||||
|
|
||||||
|
var notifications = obj.get_array_member ("cached_notifications");
|
||||||
|
notifications.foreach_element ((arr, i, node) => {
|
||||||
|
var notification = new API.Notification (node.get_object ());
|
||||||
|
cached_notifications.add (notification);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
~InstanceAccount () {
|
||||||
|
unsubscribe ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstanceAccount.empty (string instance){
|
||||||
|
Object (id: 0, instance: instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start_notificator () {
|
public InstanceAccount.from_account (API.Account account) {
|
||||||
if (notificator != null)
|
Object (id: account.id);
|
||||||
notificator.close ();
|
patch (account);
|
||||||
|
|
||||||
notificator = new Notificator (get_stream ());
|
|
||||||
notificator.status_added.connect (status_added);
|
|
||||||
notificator.status_removed.connect (status_removed);
|
|
||||||
notificator.notification.connect (notification);
|
|
||||||
notificator.start ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InstanceAccount patch (API.Account account) {
|
||||||
|
Utils.merge (this, account);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public bool is_current () {
|
public bool is_current () {
|
||||||
return accounts.formal.token == token;
|
return accounts.active.token == token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message get_stream () {
|
public string get_stream_url () {
|
||||||
var url = "%s/api/v1/streaming/?stream=user&access_token=%s".printf (instance, token);
|
return @"$instance/api/v1/streaming/?stream=user&access_token=$token";
|
||||||
return new Soup.Message ("GET", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close_notificator () {
|
public void subscribe () {
|
||||||
if (notificator != null)
|
streams.subscribe (get_stream_url (), this, out stream);
|
||||||
notificator.close ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Json.Node serialize () {
|
public void unsubscribe () {
|
||||||
|
streams.unsubscribe (stream, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Json.Node? serialize () {
|
||||||
var builder = new Json.Builder ();
|
var builder = new Json.Builder ();
|
||||||
builder.begin_object ();
|
builder.begin_object ();
|
||||||
|
|
||||||
builder.set_member_name ("hash");
|
builder.set_member_name ("hash");
|
||||||
builder.add_string_value ("test");
|
builder.add_string_value ("test");
|
||||||
builder.set_member_name ("username");
|
builder.set_member_name ("username");
|
||||||
|
@ -63,13 +94,17 @@ public class Tootle.InstanceAccount : Object {
|
||||||
builder.add_string_value (client_id);
|
builder.add_string_value (client_id);
|
||||||
builder.set_member_name ("secret");
|
builder.set_member_name ("secret");
|
||||||
builder.add_string_value (client_secret);
|
builder.add_string_value (client_secret);
|
||||||
builder.set_member_name ("token");
|
builder.set_member_name ("access_token");
|
||||||
builder.add_string_value (token);
|
builder.add_string_value (token);
|
||||||
builder.set_member_name ("last_seen_notification");
|
builder.set_member_name ("last_seen_notification");
|
||||||
builder.add_int_value (last_seen_notification);
|
builder.add_int_value (last_seen_notification);
|
||||||
builder.set_member_name ("has_unread_notifications");
|
builder.set_member_name ("has_unread_notifications");
|
||||||
builder.add_boolean_value (has_unread_notifications);
|
builder.add_boolean_value (has_unread_notifications);
|
||||||
|
|
||||||
|
var cached_profile = base.serialize ();
|
||||||
|
builder.set_member_name ("cached_profile");
|
||||||
|
builder.add_value (cached_profile);
|
||||||
|
|
||||||
builder.set_member_name ("cached_notifications");
|
builder.set_member_name ("cached_notifications");
|
||||||
builder.begin_array ();
|
builder.begin_array ();
|
||||||
cached_notifications.@foreach (notification => {
|
cached_notifications.@foreach (notification => {
|
||||||
|
@ -84,31 +119,12 @@ public class Tootle.InstanceAccount : Object {
|
||||||
return builder.get_root ();
|
return builder.get_root ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InstanceAccount parse (Json.Object obj) {
|
public override void on_notification (API.Notification obj) {
|
||||||
var acc = new InstanceAccount ();
|
var title = Html.remove_tags (obj.kind.get_desc (obj.account));
|
||||||
acc.username = obj.get_string_member ("username");
|
|
||||||
acc.instance = obj.get_string_member ("instance");
|
|
||||||
acc.client_id = obj.get_string_member ("id");
|
|
||||||
acc.client_secret = obj.get_string_member ("secret");
|
|
||||||
acc.token = obj.get_string_member ("token");
|
|
||||||
acc.last_seen_notification = obj.get_int_member ("last_seen_notification");
|
|
||||||
acc.has_unread_notifications = obj.get_boolean_member ("has_unread_notifications");
|
|
||||||
|
|
||||||
var notifications = obj.get_array_member ("cached_notifications");
|
|
||||||
notifications.foreach_element ((arr, i, node) => {
|
|
||||||
var notification = API.Notification.parse (node.get_object ());
|
|
||||||
acc.cached_notifications.add (notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void notification (API.Notification obj) {
|
|
||||||
var title = Html.remove_tags (obj.type.get_desc (obj.account));
|
|
||||||
var notification = new GLib.Notification (title);
|
var notification = new GLib.Notification (title);
|
||||||
if (obj.status != null) {
|
if (obj.status != null) {
|
||||||
var body = "";
|
var body = "";
|
||||||
body += get_pretty_instance ();
|
body += short_instance;
|
||||||
body += "\n";
|
body += "\n";
|
||||||
body += Html.remove_tags (obj.status.content);
|
body += Html.remove_tags (obj.status.content);
|
||||||
notification.set_body (body);
|
notification.set_body (body);
|
||||||
|
@ -118,34 +134,34 @@ public class Tootle.InstanceAccount : Object {
|
||||||
app.send_notification (app.application_id + ":" + obj.id.to_string (), notification);
|
app.send_notification (app.application_id + ":" + obj.id.to_string (), notification);
|
||||||
|
|
||||||
if (is_current ())
|
if (is_current ())
|
||||||
network.notification (obj);
|
streams.notification (obj);
|
||||||
|
|
||||||
if (obj.type == API.NotificationType.WATCHLIST) {
|
if (obj.kind == API.NotificationType.WATCHLIST) {
|
||||||
cached_notifications.add (obj);
|
cached_notifications.add (obj);
|
||||||
accounts.save ();
|
accounts.save ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void status_removed (int64 id) {
|
public override void on_status_removed (int64 id) {
|
||||||
if (is_current ())
|
if (is_current ())
|
||||||
network.status_removed (id);
|
streams.status_removed (id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void status_added (API.Status status) {
|
public override void on_status_added (API.Status status) {
|
||||||
if (!is_current ())
|
if (!is_current ())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
watchlist.users.@foreach (item => {
|
// watchlist.users.@foreach (item => {
|
||||||
var acct = status.account.acct;
|
// var acct = status.account.acct;
|
||||||
if (item == acct || item == "@" + acct) {
|
// if (item == acct || item == "@" + acct) {
|
||||||
var obj = new API.Notification (-1);
|
// var obj = new API.Notification (-1);
|
||||||
obj.type = API.NotificationType.WATCHLIST;
|
// obj.kind = API.NotificationType.WATCHLIST;
|
||||||
obj.account = status.account;
|
// obj.account = status.account;
|
||||||
obj.status = status;
|
// obj.status = status;
|
||||||
notification (obj);
|
// on_notification (obj);
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
208
src/Network.vala
|
@ -1,208 +0,0 @@
|
||||||
using Soup;
|
|
||||||
using GLib;
|
|
||||||
using Gdk;
|
|
||||||
using Json;
|
|
||||||
|
|
||||||
public class Tootle.Network : GLib.Object {
|
|
||||||
|
|
||||||
public const string INJECT_TOKEN = "X-HeyMate-PlsInjectToken4MeThx";
|
|
||||||
|
|
||||||
public signal void started ();
|
|
||||||
public signal void finished ();
|
|
||||||
public signal void notification (API.Notification notification);
|
|
||||||
public signal void status_removed (int64 id);
|
|
||||||
|
|
||||||
public delegate void ErrorCallback (int32 code, string reason);
|
|
||||||
public delegate void SuccessCallback (Session session, Message msg) throws GLib.Error;
|
|
||||||
|
|
||||||
private int requests_processing = 0;
|
|
||||||
private Soup.Session session;
|
|
||||||
|
|
||||||
construct {
|
|
||||||
session = new Soup.Session ();
|
|
||||||
session.ssl_strict = true;
|
|
||||||
session.ssl_use_system_ca_file = true;
|
|
||||||
session.timeout = 15;
|
|
||||||
session.max_conns = 20;
|
|
||||||
session.request_unqueued.connect (msg => {
|
|
||||||
requests_processing--;
|
|
||||||
if (requests_processing <= 0)
|
|
||||||
finished ();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
|
|
||||||
// session.add_feature (logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Network () {}
|
|
||||||
|
|
||||||
public async WebsocketConnection stream (Soup.Message msg) throws GLib.Error {
|
|
||||||
return yield session.websocket_connect_async (msg, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel_request (Soup.Message? msg) {
|
|
||||||
if (msg == null)
|
|
||||||
return;
|
|
||||||
switch (msg.status_code) {
|
|
||||||
case Soup.Status.CANCELLED:
|
|
||||||
case Soup.Status.OK:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
session.cancel_message (msg, Soup.Status.CANCELLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void inject (Soup.Message msg, string header) {
|
|
||||||
msg.request_headers.append (header, "VeryPls");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void inject_headers (ref Soup.Message msg) {
|
|
||||||
var headers = msg.request_headers;
|
|
||||||
var formal = accounts.formal;
|
|
||||||
if (headers.get_one (INJECT_TOKEN) != null && formal != null) {
|
|
||||||
headers.remove (INJECT_TOKEN);
|
|
||||||
headers.append ("Authorization", "Bearer " + formal.token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void queue (owned Soup.Message message, owned SuccessCallback? cb = null, owned ErrorCallback? errcb = null) {
|
|
||||||
requests_processing++;
|
|
||||||
started ();
|
|
||||||
|
|
||||||
inject_headers (ref message);
|
|
||||||
|
|
||||||
session.queue_message (message, (sess, msg) => {
|
|
||||||
var status = msg.status_code;
|
|
||||||
if (status != Soup.Status.CANCELLED) {
|
|
||||||
if (status == Soup.Status.OK) {
|
|
||||||
if (cb != null) {
|
|
||||||
try {
|
|
||||||
cb (session, msg);
|
|
||||||
}
|
|
||||||
catch (Error e) {
|
|
||||||
warning ("Caught exception on network request:");
|
|
||||||
warning (e.message);
|
|
||||||
if (errcb != null)
|
|
||||||
errcb (Soup.Status.NONE, e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (errcb != null)
|
|
||||||
errcb ((int32)status, get_error_reason ((int32)status));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// msg.request_body.free ();
|
|
||||||
// msg.response_body.free ();
|
|
||||||
// msg.request_headers.free ();
|
|
||||||
// msg.response_headers.free ();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public string get_error_reason (int32 status) {
|
|
||||||
return "Error " + status.to_string () + ": " + Soup.Status.get_phrase (status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_error (int32 code, string message) {
|
|
||||||
warning (message);
|
|
||||||
app.toast (message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void on_show_error (int32 code, string message) {
|
|
||||||
warning (message);
|
|
||||||
app.error (_("Network Error"), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Json.Object parse (Soup.Message msg) throws GLib.Error {
|
|
||||||
// debug ("Status Code: %u", msg.status_code);
|
|
||||||
// debug ("Message length: %lld", msg.response_body.length);
|
|
||||||
// debug ("Object: %s", (string) msg.response_body.data);
|
|
||||||
|
|
||||||
var parser = new Json.Parser ();
|
|
||||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
|
||||||
return parser.get_root ().get_object ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Json.Array parse_array (Soup.Message msg) throws GLib.Error {
|
|
||||||
// debug ("Status Code: %u", msg.status_code);
|
|
||||||
// debug ("Message length: %lld", msg.response_body.length);
|
|
||||||
// debug ("Array: %s", (string) msg.response_body.data);
|
|
||||||
|
|
||||||
var parser = new Json.Parser ();
|
|
||||||
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
|
||||||
return parser.get_root ().get_array ();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Cache
|
|
||||||
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size){
|
|
||||||
var message = new Soup.Message("GET", url);
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
if (msg.status_code != Soup.Status.OK) {
|
|
||||||
avatar.show_default (size);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = msg.response_body.data;
|
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
|
||||||
|
|
||||||
avatar.pixbuf = pixbuf.scale_simple (size, size, Gdk.InterpType.BILINEAR);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Cache
|
|
||||||
public delegate void PixbufCallback (Gdk.Pixbuf pixbuf);
|
|
||||||
public Soup.Message load_pixbuf (string url, PixbufCallback cb) {
|
|
||||||
var message = new Soup.Message("GET", url);
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
Gdk.Pixbuf? pixbuf = null;
|
|
||||||
try {
|
|
||||||
var data = msg.response_body.flatten ().data;
|
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
|
||||||
pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
|
||||||
}
|
|
||||||
catch (Error e) {
|
|
||||||
warning ("Can't get image: %s".printf (url));
|
|
||||||
warning ("Reason: " + e.message);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (msg.status_code != Soup.Status.OK)
|
|
||||||
warning ("Invalid response code %s: %s".printf (msg.status_code.to_string (), url));
|
|
||||||
}
|
|
||||||
cb (pixbuf);
|
|
||||||
});
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Cache
|
|
||||||
public void load_image (string url, Gtk.Image image) {
|
|
||||||
var message = new Soup.Message("GET", url);
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
if (msg.status_code != Soup.Status.OK) {
|
|
||||||
image.set_from_icon_name ("image-missing", Gtk.IconSize.LARGE_TOOLBAR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = msg.response_body.data;
|
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
|
|
||||||
image.set_from_pixbuf (pixbuf);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Cache
|
|
||||||
public void load_scaled_image (string url, Gtk.Image image, int size) {
|
|
||||||
var message = new Soup.Message("GET", url);
|
|
||||||
network.queue (message, (sess, msg) => {
|
|
||||||
if (msg.status_code != Soup.Status.OK) {
|
|
||||||
image.set_from_icon_name ("image-missing", Gtk.IconSize.LARGE_TOOLBAR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = msg.response_body.data;
|
|
||||||
var stream = new MemoryInputStream.from_data (data);
|
|
||||||
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
|
|
||||||
image.set_from_pixbuf (pixbuf);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
using GLib;
|
|
||||||
using Soup;
|
|
||||||
|
|
||||||
public class Tootle.Notificator : GLib.Object {
|
|
||||||
|
|
||||||
private WebsocketConnection? connection;
|
|
||||||
private Soup.Message msg;
|
|
||||||
private bool closing = false;
|
|
||||||
private int timeout = 2;
|
|
||||||
|
|
||||||
public signal void notification (API.Notification notification);
|
|
||||||
public signal void status_added (API.Status status);
|
|
||||||
public signal void status_removed (int64 id);
|
|
||||||
|
|
||||||
public Notificator (Soup.Message _msg){
|
|
||||||
msg = _msg;
|
|
||||||
msg.priority = Soup.MessagePriority.VERY_HIGH;
|
|
||||||
msg.set_flags (Soup.MessageFlags.IGNORE_CONNECTION_LIMITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string get_url () {
|
|
||||||
return msg.get_uri ().to_string (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string get_name () {
|
|
||||||
var name = msg.get_uri ().to_string (true);
|
|
||||||
if ("&access_token" in name) {
|
|
||||||
var pos = name.last_index_of ("&access_token");
|
|
||||||
name = name.slice (0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void start () {
|
|
||||||
if (connection != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
info ("Starting: %s", get_name ());
|
|
||||||
connection = yield network.stream (msg);
|
|
||||||
connection.error.connect (on_error);
|
|
||||||
connection.message.connect (on_message);
|
|
||||||
connection.closed.connect (on_closed);
|
|
||||||
timeout = 2;
|
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
warning (e.message);
|
|
||||||
on_closed ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close () {
|
|
||||||
if (connection == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
info ("Closing: %s", get_name ());
|
|
||||||
closing = true;
|
|
||||||
connection.close (0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool reconnect () {
|
|
||||||
start ();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_closed () {
|
|
||||||
if (closing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
warning ("Aborted: %s. Reconnecting in %i seconds.", get_name (), timeout);
|
|
||||||
GLib.Timeout.add_seconds (timeout, reconnect);
|
|
||||||
timeout = int.min (timeout*2, 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_error (Error e) {
|
|
||||||
if (!closing)
|
|
||||||
warning ("Error in %s: %s", get_name (), e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_message (int i, Bytes bytes) {
|
|
||||||
var msg = (string) bytes.get_data ();
|
|
||||||
|
|
||||||
var parser = new Json.Parser ();
|
|
||||||
parser.load_from_data (msg, -1);
|
|
||||||
var root = parser.get_root ().get_object ();
|
|
||||||
|
|
||||||
var type = root.get_string_member ("event");
|
|
||||||
switch (type) {
|
|
||||||
case "update":
|
|
||||||
if (!settings.live_updates)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var status = API.Status.parse (sanitize (root));
|
|
||||||
status_added (status);
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
if (!settings.live_updates)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var id = int64.parse (root.get_string_member("payload"));
|
|
||||||
status_removed (id);
|
|
||||||
break;
|
|
||||||
case "notification":
|
|
||||||
var notif = API.Notification.parse (sanitize (root));
|
|
||||||
notification (notif);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
warning ("Unknown push event: %s", type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Json.Object sanitize (Json.Object root) {
|
|
||||||
var payload = root.get_string_member ("payload");
|
|
||||||
var sanitized = Soup.URI.decode (payload);
|
|
||||||
var parser = new Json.Parser ();
|
|
||||||
parser.load_from_data (sanitized, -1);
|
|
||||||
return parser.get_root ().get_object ();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
using Soup;
|
||||||
|
using Gee;
|
||||||
|
|
||||||
|
public class Tootle.Request : Soup.Message {
|
||||||
|
|
||||||
|
public string url { construct set; get; }
|
||||||
|
private Network.SuccessCallback? cb;
|
||||||
|
private Network.ErrorCallback? error_cb;
|
||||||
|
private HashMap<string, string>? pars;
|
||||||
|
private weak InstanceAccount? account;
|
||||||
|
private bool needs_token = false;
|
||||||
|
|
||||||
|
public Request.GET (string url) {
|
||||||
|
Object (method: "GET", url: url);
|
||||||
|
}
|
||||||
|
public Request.POST (string url) {
|
||||||
|
Object (method: "POST", url: url);
|
||||||
|
}
|
||||||
|
public Request.DELETE (string url) {
|
||||||
|
Object (method: "DELETE", url: url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request then (owned Network.SuccessCallback cb) {
|
||||||
|
this.cb = (owned) cb;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request then_parse_array (owned Network.NodeCallback _cb) {
|
||||||
|
this.cb = (sess, msg) => {
|
||||||
|
var parser = new Json.Parser ();
|
||||||
|
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||||
|
parser.get_root ().get_array ().foreach_element ((array, i, node) => _cb (node, msg));
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request then_parse_obj (owned Network.ObjectCallback _cb) {
|
||||||
|
this.cb = (sess, msg) => {
|
||||||
|
_cb (network.parse (msg));
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request on_error (owned Network.ErrorCallback cb) {
|
||||||
|
this.error_cb = (owned) cb;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request with_account (InstanceAccount? account = null) {
|
||||||
|
this.needs_token = true;
|
||||||
|
if (account != null)
|
||||||
|
this.account = account;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request with_param (string name, string val) {
|
||||||
|
if (pars == null)
|
||||||
|
pars = new HashMap<string, string> ();
|
||||||
|
pars[name] = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be used for requests with default priority
|
||||||
|
public Request queue () {
|
||||||
|
var parameters = "";
|
||||||
|
if (pars != null) {
|
||||||
|
parameters = "?";
|
||||||
|
var parameters_counter = 0;
|
||||||
|
pars.@foreach (entry => {
|
||||||
|
parameters_counter++;
|
||||||
|
var key = (string) entry.key;
|
||||||
|
var val = (string) entry.value;
|
||||||
|
parameters += @"$key=$val";
|
||||||
|
|
||||||
|
if (parameters_counter < pars.size)
|
||||||
|
parameters += "&";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_token) {
|
||||||
|
if (account == null) {
|
||||||
|
warning (@"No account found for: $method: $url$parameters");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
request_headers.append ("Authorization", @"Bearer $(account.token)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("://" in url)) {
|
||||||
|
url = account.instance + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uri = new URI (url + "" + parameters);
|
||||||
|
|
||||||
|
url = uri.to_string (false);
|
||||||
|
info (@"$method: $url");
|
||||||
|
|
||||||
|
network.queue (this, (owned) cb, (owned) error_cb);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be used for real-time user interactions (liking, removing and browsing posts)
|
||||||
|
public Request exec () {
|
||||||
|
this.priority = MessagePriority.HIGH;
|
||||||
|
return this.queue ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
using Gee;
|
||||||
|
|
||||||
|
public class Tootle.Accounts : GLib.Object {
|
||||||
|
|
||||||
|
private string dir_path;
|
||||||
|
private string file_path;
|
||||||
|
|
||||||
|
public ArrayList<InstanceAccount> saved { get; set; default = new ArrayList<InstanceAccount> (); }
|
||||||
|
public InstanceAccount? active { get; set; }
|
||||||
|
|
||||||
|
construct {
|
||||||
|
dir_path = @"$(GLib.Environment.get_user_config_dir ())/$(app.application_id)";
|
||||||
|
file_path = @"$dir_path/accounts.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switch_account (int id) {
|
||||||
|
var acc = saved.@get (id);
|
||||||
|
info (@"Switching to account: $(acc.handle)...");
|
||||||
|
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||||
|
.with_account (acc)
|
||||||
|
.then ((sess, mess) => {
|
||||||
|
var root = network.parse (mess);
|
||||||
|
var profile = new API.Account (root);
|
||||||
|
acc.patch (profile);
|
||||||
|
info ("OK: Token is valid");
|
||||||
|
active = acc;
|
||||||
|
settings.current_account = id;
|
||||||
|
})
|
||||||
|
.on_error ((code, reason) => {
|
||||||
|
warning ("Token invalid!");
|
||||||
|
network.on_show_error (code, _("This instance has invalidated this session. Please sign in again.\n\n%s").printf (reason));
|
||||||
|
})
|
||||||
|
.exec ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add (InstanceAccount account) {
|
||||||
|
info (@"Adding new account: $(account.handle)");
|
||||||
|
saved.add (account);
|
||||||
|
save ();
|
||||||
|
switch_account (saved.size - 1);
|
||||||
|
account.subscribe ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove (InstanceAccount account) {
|
||||||
|
account.unsubscribe ();
|
||||||
|
saved.remove (account);
|
||||||
|
saved.notify_property ("size");
|
||||||
|
|
||||||
|
if (saved.size < 1)
|
||||||
|
active = null;
|
||||||
|
else {
|
||||||
|
var id = settings.current_account - 1;
|
||||||
|
if (id > saved.size - 1)
|
||||||
|
id = saved.size - 1;
|
||||||
|
else if (id < saved.size - 1)
|
||||||
|
id = 0;
|
||||||
|
switch_account (id);
|
||||||
|
}
|
||||||
|
save ();
|
||||||
|
|
||||||
|
if (is_empty ())
|
||||||
|
window.open_view (new Views.NewAccount (false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool is_empty () {
|
||||||
|
return saved.size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init () {
|
||||||
|
save (false);
|
||||||
|
load ();
|
||||||
|
|
||||||
|
if (saved.size < 1)
|
||||||
|
window.open_view (new Views.NewAccount (false));
|
||||||
|
else
|
||||||
|
switch_account (settings.current_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save (bool overwrite = true) {
|
||||||
|
try {
|
||||||
|
var dir = File.new_for_path (dir_path);
|
||||||
|
if (!dir.query_exists ())
|
||||||
|
dir.make_directory ();
|
||||||
|
|
||||||
|
var file = File.new_for_path (file_path);
|
||||||
|
if (file.query_exists () && !overwrite)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var builder = new Json.Builder ();
|
||||||
|
builder.begin_array ();
|
||||||
|
saved.foreach ((acc) => {
|
||||||
|
var node = acc.serialize ();
|
||||||
|
builder.add_value (node);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
builder.end_array ();
|
||||||
|
|
||||||
|
var generator = new Json.Generator ();
|
||||||
|
generator.set_root (builder.get_root ());
|
||||||
|
var data = generator.to_data (null);
|
||||||
|
|
||||||
|
if (file.query_exists ())
|
||||||
|
file.@delete ();
|
||||||
|
|
||||||
|
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||||
|
stream.write (data.data);
|
||||||
|
info ("Saved accounts");
|
||||||
|
}
|
||||||
|
catch (Error e){
|
||||||
|
warning (e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load () {
|
||||||
|
try {
|
||||||
|
uint8[] data;
|
||||||
|
string etag;
|
||||||
|
var file = File.new_for_path (file_path);
|
||||||
|
file.load_contents (null, out data, out etag);
|
||||||
|
var contents = (string) data;
|
||||||
|
|
||||||
|
var parser = new Json.Parser ();
|
||||||
|
parser.load_from_data (contents, -1);
|
||||||
|
var array = parser.get_root ().get_array ();
|
||||||
|
|
||||||
|
array.foreach_element ((_arr, _i, node) => {
|
||||||
|
var obj = node.get_object ();
|
||||||
|
var account = new InstanceAccount (obj);
|
||||||
|
if (account != null) {
|
||||||
|
saved.add (account);
|
||||||
|
account.subscribe ();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
info (@"Loaded $(saved.size) accounts");
|
||||||
|
}
|
||||||
|
catch (Error e){
|
||||||
|
warning (e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
using Gee;
|
||||||
|
using Gdk;
|
||||||
|
|
||||||
|
public class Tootle.Cache : GLib.Object {
|
||||||
|
|
||||||
|
protected HashTable<string, Item> items { get; set; }
|
||||||
|
protected HashTable<string, Soup.Message> items_in_progress { get; set; }
|
||||||
|
protected uint size {
|
||||||
|
get {
|
||||||
|
return items.size ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
construct {
|
||||||
|
items = new HashTable<string, Item> (GLib.str_hash, GLib.str_equal);
|
||||||
|
items_in_progress = new HashTable<string, Soup.Message> (GLib.str_hash, GLib.str_equal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void CachedResultCallback (Reference? result);
|
||||||
|
|
||||||
|
public struct Reference {
|
||||||
|
public string key;
|
||||||
|
public weak Pixbuf? data;
|
||||||
|
public bool loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Item : GLib.Object {
|
||||||
|
public Pixbuf data { get; construct set; }
|
||||||
|
public int64 references { get; construct set; }
|
||||||
|
|
||||||
|
public Item (Pixbuf d, int64 r) {
|
||||||
|
Object (data: d, references: r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unload (Reference? r) {
|
||||||
|
if (r == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (r.data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = items[r.key];
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.references--;
|
||||||
|
//info (@"DEREF $(r.key) $(item.references)");
|
||||||
|
if (item.references <= 0) {
|
||||||
|
//info ("REMOVE %s", r.key);
|
||||||
|
items.remove (r.key);
|
||||||
|
items_in_progress.remove (r.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load (string? url, owned CachedResultCallback cb) {
|
||||||
|
if (url == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var key = url;
|
||||||
|
if (items.contains (key)) {
|
||||||
|
//info (@"LOAD $key");
|
||||||
|
var item = items.@get (key);
|
||||||
|
item.references++;
|
||||||
|
cb (Reference () {
|
||||||
|
data = item.data,
|
||||||
|
key = key,
|
||||||
|
loading = false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = items.@get (key);
|
||||||
|
|
||||||
|
var message = items_in_progress.@get (key);
|
||||||
|
if (message == null) {
|
||||||
|
message = new Soup.Message ("GET", url);
|
||||||
|
ulong id = 0;
|
||||||
|
id = message.finished.connect (() => {
|
||||||
|
Pixbuf? pixbuf = null;
|
||||||
|
|
||||||
|
var data = message.response_body.flatten ().data;
|
||||||
|
var stream = new MemoryInputStream.from_data (data);
|
||||||
|
pixbuf = new Pixbuf.from_stream (stream);
|
||||||
|
stream.close ();
|
||||||
|
|
||||||
|
//info (@"< STORE $key");
|
||||||
|
items[key] = new Item (pixbuf, 1);
|
||||||
|
items_in_progress.remove (key);
|
||||||
|
|
||||||
|
cb (Reference () {
|
||||||
|
data = items[key].data,
|
||||||
|
key = key,
|
||||||
|
loading = false
|
||||||
|
});
|
||||||
|
|
||||||
|
message.disconnect (id);
|
||||||
|
});
|
||||||
|
|
||||||
|
network.queue (message, (sess, msg) => {
|
||||||
|
// no one cares
|
||||||
|
},
|
||||||
|
(code, reason) => {
|
||||||
|
cb (null);
|
||||||
|
});
|
||||||
|
|
||||||
|
cb (Reference () {
|
||||||
|
data = null,
|
||||||
|
key = key,
|
||||||
|
loading = true
|
||||||
|
});
|
||||||
|
|
||||||
|
items_in_progress.insert (key, message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//info ("AWAIT: %s", key);
|
||||||
|
ulong id = 0;
|
||||||
|
id = message.finished.connect_after (() => {
|
||||||
|
var it = items.@get (key);
|
||||||
|
cb (Reference () {
|
||||||
|
data = it.data,
|
||||||
|
key = key,
|
||||||
|
loading = false
|
||||||
|
});
|
||||||
|
it.references++;
|
||||||
|
message.disconnect (id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear () {
|
||||||
|
info ("PURGE");
|
||||||
|
items.remove_all ();
|
||||||
|
items_in_progress.remove_all ();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
public interface Tootle.IAccountListener : GLib.Object {
|
||||||
|
|
||||||
|
protected void connect_account () {
|
||||||
|
accounts.notify["active"].connect (() => on_account_changed (accounts.active));
|
||||||
|
accounts.saved.notify["size"].connect (() => on_accounts_changed (accounts.saved));
|
||||||
|
on_account_changed (accounts.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void on_account_changed (InstanceAccount? account) {}
|
||||||
|
public virtual void on_accounts_changed (Gee.ArrayList<InstanceAccount> accounts) {}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
public interface Tootle.IStreamListener : GLib.Object {
|
||||||
|
|
||||||
|
public virtual void on_status_removed (int64 id) {}
|
||||||
|
public virtual void on_status_added (API.Status s) {}
|
||||||
|
public virtual void on_notification (API.Notification n) {}
|
||||||
|
|
||||||
|
public virtual bool accepts (ref string event) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
using Soup;
|
||||||
|
using GLib;
|
||||||
|
using Gdk;
|
||||||
|
using Json;
|
||||||
|
|
||||||
|
public class Tootle.Network : GLib.Object {
|
||||||
|
|
||||||
|
public signal void started ();
|
||||||
|
public signal void finished ();
|
||||||
|
|
||||||
|
public delegate void ErrorCallback (int32 code, string reason);
|
||||||
|
public delegate void SuccessCallback (Session session, Message msg) throws Error;
|
||||||
|
public delegate void NodeCallback (Json.Node node, Message msg) throws Error;
|
||||||
|
public delegate void ObjectCallback (Json.Object node) throws Error;
|
||||||
|
|
||||||
|
private int requests_processing = 0;
|
||||||
|
public Soup.Session session;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
session = new Soup.Session ();
|
||||||
|
session.ssl_strict = true;
|
||||||
|
session.ssl_use_system_ca_file = true;
|
||||||
|
session.timeout = 15;
|
||||||
|
session.max_conns = 30;
|
||||||
|
session.request_unqueued.connect (msg => {
|
||||||
|
requests_processing--;
|
||||||
|
if (requests_processing <= 0)
|
||||||
|
finished ();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void cancel_request (Soup.Message? msg) {
|
||||||
|
// if (msg == null)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// switch (msg.status_code) {
|
||||||
|
// case Soup.Status.CANCELLED:
|
||||||
|
// case Soup.Status.OK:
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// session.cancel_message (msg, Soup.Status.CANCELLED);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void queue (owned Soup.Message message, owned SuccessCallback? cb, owned ErrorCallback? errcb = null) {
|
||||||
|
requests_processing++;
|
||||||
|
started ();
|
||||||
|
|
||||||
|
session.queue_message (message, (sess, msg) => {
|
||||||
|
var status = msg.status_code;
|
||||||
|
if (status == Soup.Status.OK) {
|
||||||
|
try {
|
||||||
|
cb (session, msg);
|
||||||
|
}
|
||||||
|
catch (Error e) {
|
||||||
|
warning ("Exception on network request: %s", e.message);
|
||||||
|
if (errcb != null)
|
||||||
|
errcb (Soup.Status.NONE, e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (errcb != null)
|
||||||
|
errcb ((int32)status, describe_error ((int32)status));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public string describe_error (int32 code) {
|
||||||
|
var reason = Soup.Status.get_phrase (code);
|
||||||
|
return @"$code: $reason";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_error (int32 code, string message) {
|
||||||
|
warning (message);
|
||||||
|
app.toast (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_show_error (int32 code, string message) {
|
||||||
|
warning (message);
|
||||||
|
app.error (_("Network Error"), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Json.Object parse (Soup.Message msg) throws Error {
|
||||||
|
var parser = new Json.Parser ();
|
||||||
|
parser.load_from_data ((string) msg.response_body.flatten ().data, -1);
|
||||||
|
return parser.get_root ().get_object ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
using GLib;
|
||||||
|
|
||||||
|
public class Tootle.Settings : GLib.Settings {
|
||||||
|
|
||||||
|
public int current_account { get; set; }
|
||||||
|
public bool notifications { get; set; }
|
||||||
|
public bool always_online { get; set; }
|
||||||
|
public int char_limit { get; set; }
|
||||||
|
public bool live_updates { get; set; }
|
||||||
|
public bool live_updates_public { get; set; }
|
||||||
|
public bool dark_theme { get; set; }
|
||||||
|
|
||||||
|
public string watched_users { get; set; }
|
||||||
|
public string watched_hashtags { get; set; }
|
||||||
|
|
||||||
|
public int window_x { get; set; }
|
||||||
|
public int window_y { get; set; }
|
||||||
|
public int window_w { get; set; }
|
||||||
|
public int window_h { get; set; }
|
||||||
|
|
||||||
|
public Settings () {
|
||||||
|
Object (schema_id: Build.DOMAIN);
|
||||||
|
init ("current-account");
|
||||||
|
init ("notifications");
|
||||||
|
init ("always-online");
|
||||||
|
init ("char-limit");
|
||||||
|
init ("live-updates");
|
||||||
|
init ("live-updates-public");
|
||||||
|
init ("dark-theme");
|
||||||
|
|
||||||
|
init ("watched-users");
|
||||||
|
init ("watched-hashtags");
|
||||||
|
|
||||||
|
init ("window-x");
|
||||||
|
init ("window-y");
|
||||||
|
init ("window-w");
|
||||||
|
init ("window-h");
|
||||||
|
}
|
||||||
|
|
||||||
|
void init (string key) {
|
||||||
|
bind (key, this, key, SettingsBindFlags.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
using GLib;
|
||||||
|
using Soup;
|
||||||
|
using Gee;
|
||||||
|
|
||||||
|
public class Tootle.Streams : Object {
|
||||||
|
|
||||||
|
public signal void notification (API.Notification n);
|
||||||
|
public signal void status_removed (int64 id);
|
||||||
|
|
||||||
|
protected HashTable<string, Connection> connections {
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
default = new HashTable<string, Connection> (GLib.str_hash, GLib.str_equal);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Connection : Object {
|
||||||
|
public ArrayList<IStreamListener> subscribers;
|
||||||
|
protected WebsocketConnection socket;
|
||||||
|
protected Message msg;
|
||||||
|
|
||||||
|
protected bool closing = false;
|
||||||
|
protected int timeout = 2;
|
||||||
|
|
||||||
|
public string name {
|
||||||
|
owned get {
|
||||||
|
var url = msg.get_uri ().to_string (false);
|
||||||
|
return url.slice (0, url.last_index_of ("&access_token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection (string url) {
|
||||||
|
this.subscribers = new ArrayList<IStreamListener> ();
|
||||||
|
this.msg = new Message ("GET", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool start () {
|
||||||
|
//info (@"Opening stream: $name");
|
||||||
|
network.session.websocket_connect_async.begin (msg, null, null, null, (obj, res) => {
|
||||||
|
socket = network.session.websocket_connect_async.end (res);
|
||||||
|
socket.error.connect (on_error);
|
||||||
|
socket.closed.connect (on_closed);
|
||||||
|
socket.message.connect (on_message);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add (IStreamListener s) {
|
||||||
|
info ("%s > %s", get_subscriber_name (s), name);
|
||||||
|
subscribers.add (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove (IStreamListener s) {
|
||||||
|
if (subscribers.contains (s)) {
|
||||||
|
info ("%s X %s", get_subscriber_name (s), name);
|
||||||
|
subscribers.remove (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribers.size <= 0) {
|
||||||
|
info (@"Closing: $name");
|
||||||
|
closing = true;
|
||||||
|
socket.close (0, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_error (Error e) {
|
||||||
|
if (!closing)
|
||||||
|
warning (@"Error in $name: $(e.message)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_closed () {
|
||||||
|
if (!closing) {
|
||||||
|
warning (@"CLOSED: $name. Reconnecting in $timeout seconds.");
|
||||||
|
GLib.Timeout.add_seconds (timeout, start);
|
||||||
|
timeout = int.min (timeout*2, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_message (int i, Bytes bytes) {
|
||||||
|
try {
|
||||||
|
emit (bytes, this);
|
||||||
|
}
|
||||||
|
catch (Error e) {
|
||||||
|
warning (@"Couldn't handle websocket event. Reason: $(e.message)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subscribe (string? url, IStreamListener s, out string cookie) {
|
||||||
|
if (url == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (connections.contains (url)) {
|
||||||
|
connections[url].add (s);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var con = new Connection (url);
|
||||||
|
connections[url] = con;
|
||||||
|
con.add (s);
|
||||||
|
con.start ();
|
||||||
|
}
|
||||||
|
cookie = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe (string? cookie, IStreamListener s) {
|
||||||
|
var url = cookie;
|
||||||
|
if (url == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (connections.contains (url))
|
||||||
|
connections.@get (url).remove (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string get_subscriber_name (Object s) {
|
||||||
|
return s.get_type ().name ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode (Bytes bytes, out string event, out Json.Object root) throws Error {
|
||||||
|
var msg = (string) bytes.get_data ();
|
||||||
|
var parser = new Json.Parser ();
|
||||||
|
parser.load_from_data (msg, -1);
|
||||||
|
root = parser.get_root ().get_object ();
|
||||||
|
event = root.get_string_member ("event");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Json.Object sanitize (Json.Object root) {
|
||||||
|
var payload = root.get_string_member ("payload");
|
||||||
|
var sanitized = Soup.URI.decode (payload);
|
||||||
|
var parser = new Json.Parser ();
|
||||||
|
parser.load_from_data (sanitized, -1);
|
||||||
|
return parser.get_root ().get_object ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emit (Bytes bytes, Connection c) throws Error {
|
||||||
|
if (!settings.live_updates)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string event;
|
||||||
|
Json.Object root;
|
||||||
|
decode (bytes, out event, out root);
|
||||||
|
|
||||||
|
// c.subscribers.@foreach (s => {
|
||||||
|
// warning ("%s: %s for %s", c.name, event, get_subscriber_name (s));
|
||||||
|
// return false;
|
||||||
|
// });
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case "update":
|
||||||
|
var entity = new API.Status (sanitize (root));
|
||||||
|
c.subscribers.@foreach (s => {
|
||||||
|
if (s.accepts (ref event))
|
||||||
|
s.on_status_added (entity);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
var id = int64.parse (root.get_string_member ("payload"));
|
||||||
|
c.subscribers.@foreach (s => {
|
||||||
|
if (s.accepts (ref event))
|
||||||
|
s.on_status_removed (id);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "notification":
|
||||||
|
var entity = new API.Notification (sanitize (root));
|
||||||
|
c.subscribers.@foreach (s => {
|
||||||
|
if (s.accepts (ref event))
|
||||||
|
s.on_notification (entity);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
warning (@"Unknown websocket event: \"$event\". Ignoring.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
public class Tootle.Settings : Granite.Services.Settings {
|
|
||||||
|
|
||||||
public int current_account { get; set; }
|
|
||||||
public bool notifications { get; set; }
|
|
||||||
public bool always_online { get; set; }
|
|
||||||
public bool cache { get; set; }
|
|
||||||
public int cache_size { get; set; }
|
|
||||||
public int char_limit { get; set; }
|
|
||||||
public bool live_updates { get; set; }
|
|
||||||
public bool live_updates_public { get; set; }
|
|
||||||
public bool dark_theme { get; set; }
|
|
||||||
public string watched_users { get; set; }
|
|
||||||
public string watched_hashtags { get; set; }
|
|
||||||
|
|
||||||
public int window_x { get; set; }
|
|
||||||
public int window_y { get; set; }
|
|
||||||
public int window_w { get; set; }
|
|
||||||
public int window_h { get; set; }
|
|
||||||
|
|
||||||
public Settings () {
|
|
||||||
base ("com.github.bleakgrey.tootle");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,651 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 PerfectCarl - https://github.com/PerfectCarl/vala-stacktrace
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Stacktrace {
|
||||||
|
|
||||||
|
public class Frame {
|
||||||
|
// Address used by addr2line
|
||||||
|
public string address { get;private set;default = "";}
|
||||||
|
|
||||||
|
public string line { get;private set;default = "";}
|
||||||
|
|
||||||
|
public string line_number { get;private set;default = "";}
|
||||||
|
|
||||||
|
public string file_path { get;private set;default = "";}
|
||||||
|
|
||||||
|
public string file_short_path { get;private set;default = "";}
|
||||||
|
|
||||||
|
public string function { get;private set;default = "";}
|
||||||
|
|
||||||
|
public Frame (string address, string line, string function, string file_path, string file_short_path) {
|
||||||
|
this._address = address;
|
||||||
|
this._line = line;
|
||||||
|
|
||||||
|
this._file_path = file_path;
|
||||||
|
this._file_short_path = file_short_path;
|
||||||
|
this._function = function;
|
||||||
|
this.line_number = extract_line (line);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string to_string () {
|
||||||
|
var result = line;
|
||||||
|
if (result == "")
|
||||||
|
result = " C library at address [" + address + "]";
|
||||||
|
return result + " [" + address + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Style {
|
||||||
|
RESET = 0,
|
||||||
|
BRIGHT = 1,
|
||||||
|
DIM = 2,
|
||||||
|
UNDERLINE = 3,
|
||||||
|
BLINK = 4,
|
||||||
|
REVERSE = 7,
|
||||||
|
HIDDEN = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CriticalHandler {
|
||||||
|
IGNORE,
|
||||||
|
PRINT_STACKTRACE,
|
||||||
|
CRASH
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Color {
|
||||||
|
BLACK = 0,
|
||||||
|
RED = 1,
|
||||||
|
GREEN = 2,
|
||||||
|
YELLOW = 3,
|
||||||
|
BLUE = 4,
|
||||||
|
MAGENTA = 5,
|
||||||
|
CYAN = 6,
|
||||||
|
WHITE = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
public Gee.ArrayList<Frame> _frames = new Gee.ArrayList<Frame>();
|
||||||
|
|
||||||
|
private Frame first_vala = null;
|
||||||
|
|
||||||
|
private int max_file_name_length = 0;
|
||||||
|
|
||||||
|
private int max_line_number_length = 0;
|
||||||
|
|
||||||
|
private bool is_all_function_name_blank = true;
|
||||||
|
|
||||||
|
private bool is_all_file_name_blank = true;
|
||||||
|
|
||||||
|
private ProcessSignal sig;
|
||||||
|
|
||||||
|
public static bool enabled { get;set;default = true;}
|
||||||
|
|
||||||
|
public static bool hide_installed_libraries { get;set;default = false;}
|
||||||
|
|
||||||
|
public static Color default_highlight_color { get;set;default = Color.WHITE;}
|
||||||
|
|
||||||
|
public static Color default_error_background { get;set;default = Color.RED;}
|
||||||
|
|
||||||
|
public Color highlight_color { get;set;default = Color.WHITE;}
|
||||||
|
|
||||||
|
public Color error_background { get;set;default = Color.RED;}
|
||||||
|
|
||||||
|
public Gee.ArrayList<Frame> frames {
|
||||||
|
get {
|
||||||
|
return _frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stacktrace (GLib.ProcessSignal sig = GLib.ProcessSignal.TTOU) {
|
||||||
|
this.sig = sig;
|
||||||
|
error_background = default_error_background;
|
||||||
|
highlight_color = default_highlight_color;
|
||||||
|
//hide_installed_libraries = true;
|
||||||
|
create_stacktrace ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_module_name () {
|
||||||
|
var path = new char[1024];
|
||||||
|
Posix.readlink ("/proc/self/exe", path);
|
||||||
|
string result = (string) path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO CARL convert this piece of code to vala conventions
|
||||||
|
public static string get_relative_path (string p_fullDestinationPath, string p_startPath) {
|
||||||
|
|
||||||
|
string[] l_startPathParts = p_startPath.split ("/");
|
||||||
|
string[] l_destinationPathParts = p_fullDestinationPath.split ("/");
|
||||||
|
|
||||||
|
int l_sameCounter = 0;
|
||||||
|
while ((l_sameCounter < l_startPathParts.length) &&
|
||||||
|
(l_sameCounter < l_destinationPathParts.length) &&
|
||||||
|
l_startPathParts[l_sameCounter] == l_destinationPathParts[l_sameCounter]) {
|
||||||
|
l_sameCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l_sameCounter == 0) {
|
||||||
|
return p_fullDestinationPath; // There is no relative link.
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder l_builder = new StringBuilder ();
|
||||||
|
for (int i = l_sameCounter ; i < l_startPathParts.length ; i++) {
|
||||||
|
l_builder.append ("../");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = l_sameCounter ; i < l_destinationPathParts.length ; i++) {
|
||||||
|
l_builder.append (l_destinationPathParts[i] + "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CARL l_builder.Length--;
|
||||||
|
// Remove the last /
|
||||||
|
var result = l_builder.str;
|
||||||
|
result = result.substring (0, result.length - 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_short_file_path (string file_path) {
|
||||||
|
var path = Environment.get_current_dir ();
|
||||||
|
/*var i = file_path.index_of ( path );
|
||||||
|
if( i>=0 )
|
||||||
|
return file_path.substring ( path.length, file_path.length - path.length );
|
||||||
|
return file_path; */
|
||||||
|
var result = get_relative_path (file_path, path);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool is_custom {
|
||||||
|
get {
|
||||||
|
return sig == ProcessSignal.TTOU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// input : '/home/cran/Documents/Projects/elementary/noise/instant-beta/build/core/libnoise-core.so.0(noise_job_repository_create_job+0x309) [0x7ff60a021e69]'
|
||||||
|
// ouput: 0x309
|
||||||
|
private int extract_base_address (string line) {
|
||||||
|
int result = 0 ;
|
||||||
|
var start = line.last_index_of ("+");
|
||||||
|
if (start >= 0) {
|
||||||
|
var end = line.last_index_of (")");
|
||||||
|
if( end > start ) {
|
||||||
|
var text = line.substring (start+3,end-start-3) ;
|
||||||
|
text.scanf("%x", &result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process_info_for_file (string full_line, string str ) {
|
||||||
|
func = "" ;
|
||||||
|
file_path = "";
|
||||||
|
short_file_path = "";
|
||||||
|
l = "";
|
||||||
|
file_line = "";
|
||||||
|
func_line = "";
|
||||||
|
|
||||||
|
var lines = full_line.split ("\n");
|
||||||
|
|
||||||
|
if (lines.length > 0)
|
||||||
|
func_line = lines[0];
|
||||||
|
|
||||||
|
if (lines.length > 1)
|
||||||
|
file_line = lines[1];
|
||||||
|
if (file_line == "??:0" || file_line == "??:?")
|
||||||
|
file_line = "";
|
||||||
|
func = extract_function_name (str);
|
||||||
|
|
||||||
|
file_path = "";
|
||||||
|
short_file_path = "";
|
||||||
|
l = "";
|
||||||
|
if (file_line != "") {
|
||||||
|
if (func == "")
|
||||||
|
func = extract_function_name_from_line (func_line);
|
||||||
|
file_path = extract_file_path (file_line);
|
||||||
|
short_file_path = extract_short_file_path (file_path);
|
||||||
|
l = extract_line (file_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string func = "" ;
|
||||||
|
string file_path = "";
|
||||||
|
string short_file_path = "";
|
||||||
|
string l = "";
|
||||||
|
string file_line = "";
|
||||||
|
string func_line = "";
|
||||||
|
|
||||||
|
private void process_info_from_lib (string file_path, string str) {
|
||||||
|
var cmd2 = "nm %s".printf(file_path) ;
|
||||||
|
var addr1_s = execute_command_sync_get_output (cmd2) ;
|
||||||
|
MatchInfo info ;
|
||||||
|
try {
|
||||||
|
Regex regex = new Regex ("\\n[^ ]* T "+func);
|
||||||
|
|
||||||
|
if( regex.match (addr1_s, 0, out info) )
|
||||||
|
{
|
||||||
|
while( info.matches() ){
|
||||||
|
var lll = info.fetch(0) ;
|
||||||
|
//stdout.printf ( "lll '%s'\n", lll ) ;
|
||||||
|
addr1_s = lll.substring(0, lll.index_of(" ")) ;
|
||||||
|
info.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RegexError e)
|
||||||
|
{
|
||||||
|
critical( "Error while processing regex %s", e.message ) ;
|
||||||
|
}
|
||||||
|
//stdout.printf ("addr1_s %s\n", addr1_s) ;
|
||||||
|
int addr1 = 0 ;
|
||||||
|
addr1_s.scanf("%x", &addr1);
|
||||||
|
if( addr1 != 0 ) {
|
||||||
|
int addr2 = extract_base_address (str) ;
|
||||||
|
string addr3 = "%#08x".printf (addr1+addr2);
|
||||||
|
var new_full_line = process_line (file_path, addr3);
|
||||||
|
process_info_for_file (new_full_line, str ) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void create_stacktrace () {
|
||||||
|
int frame_count = 100;
|
||||||
|
int skipped_frames_count = 5;
|
||||||
|
// Stacktrace not due to a crash
|
||||||
|
if (is_custom)
|
||||||
|
skipped_frames_count = 3;
|
||||||
|
|
||||||
|
void *[] array = new void *[frame_count];
|
||||||
|
|
||||||
|
_frames.clear ();
|
||||||
|
first_vala = null;
|
||||||
|
max_file_name_length = 0;
|
||||||
|
is_all_function_name_blank = true;
|
||||||
|
is_all_file_name_blank = true;
|
||||||
|
|
||||||
|
#if VALA_0_26
|
||||||
|
var size = Linux.Backtrace.@get (array);
|
||||||
|
var strings = Linux.Backtrace.symbols (array);
|
||||||
|
#else
|
||||||
|
int size = Linux.backtrace (array, frame_count);
|
||||||
|
unowned string[] strings = Linux.backtrace_symbols (array, size);
|
||||||
|
// Needed because of some weird bug
|
||||||
|
strings.length = size;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int[] addresses = (int[])array;
|
||||||
|
string module = get_module_name ();
|
||||||
|
// First ones are the handler
|
||||||
|
for (int i = skipped_frames_count ; i < size ; i++) {
|
||||||
|
int address = addresses[i];
|
||||||
|
string str = strings[i];
|
||||||
|
var addr = extract_address (str);
|
||||||
|
|
||||||
|
var full_line = process_line (module, addr);
|
||||||
|
process_info_for_file( full_line, str) ;
|
||||||
|
if (file_line == "") {
|
||||||
|
file_path = extract_file_path_from (str);
|
||||||
|
|
||||||
|
}
|
||||||
|
if( file_path.has_suffix(".so.0")) {
|
||||||
|
process_info_from_lib (file_path, str) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
//stdout.printf ("Building %d \n . addr: [%s]\n . full_line: '%s'\n . file_line: '%s'\n . func_line: '%s'\n . str : '%s'\n . func: '%s'\n . file: '%s'\n . line: '%s'\n",
|
||||||
|
//i, addr, full_line, file_line, func_line, str, func, file_path, l);
|
||||||
|
|
||||||
|
if (func != "" && file_path.has_suffix (".vala") && is_all_function_name_blank)
|
||||||
|
is_all_function_name_blank = false;
|
||||||
|
|
||||||
|
if (short_file_path != "" && is_all_file_name_blank)
|
||||||
|
is_all_file_name_blank = false;
|
||||||
|
|
||||||
|
var frame = new Frame (addr, file_line, func, file_path, short_file_path);
|
||||||
|
|
||||||
|
if (first_vala == null && file_path.has_suffix (".vala"))
|
||||||
|
first_vala = frame;
|
||||||
|
|
||||||
|
if (short_file_path.length > max_file_name_length)
|
||||||
|
max_file_name_length = short_file_path.length;
|
||||||
|
if (l.length > max_line_number_length)
|
||||||
|
max_line_number_length = l.length;
|
||||||
|
_frames.add (frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_function_name (string line) {
|
||||||
|
if (line == "")
|
||||||
|
return "";
|
||||||
|
var start = line.index_of ("(");
|
||||||
|
if (start >= 0) {
|
||||||
|
var end = line.index_of ("+", start);
|
||||||
|
if (end >= 0) {
|
||||||
|
var result = line.substring (start + 1, end - start - 1);
|
||||||
|
return result.strip ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_function_name_from_line (string line) {
|
||||||
|
return line.strip ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_file_path_from (string str) {
|
||||||
|
if (str == "")
|
||||||
|
return "";
|
||||||
|
/*if( str.index_of("??") >= 0)
|
||||||
|
//result = result.substring (4, line.length - 4 );
|
||||||
|
stdout.printf ("ERR2?? : %s\n", str ) ; */
|
||||||
|
var start = str.index_of ("(");
|
||||||
|
if (start >= 0) {
|
||||||
|
return str.substring (0, start).strip ();
|
||||||
|
}
|
||||||
|
return str.strip ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_file_path (string line) {
|
||||||
|
var result = line;
|
||||||
|
if (result == "")
|
||||||
|
return "";
|
||||||
|
if (result == "??:0??:0")
|
||||||
|
return "";
|
||||||
|
// For some reason, the file name can starts with ??:0
|
||||||
|
if (result.has_prefix ("??:0"))
|
||||||
|
result = result.substring (4, line.length - 4);
|
||||||
|
// stdout.printf ("ERR1?? : %s\n", line ) ;
|
||||||
|
var start = result.index_of (":");
|
||||||
|
if (start >= 0) {
|
||||||
|
result = result.substring (0, start);
|
||||||
|
return result.strip ();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string extract_line (string line) {
|
||||||
|
var result = line;
|
||||||
|
if (result == "")
|
||||||
|
return "";
|
||||||
|
if (result.has_prefix ("??:0"))
|
||||||
|
result = result.substring (4, line.length - 4);
|
||||||
|
var start = result.index_of (":");
|
||||||
|
if (start >= 0) {
|
||||||
|
result = result.substring (start + 1, line.length - start - 1);
|
||||||
|
var end = result.index_of ("(");
|
||||||
|
if (end >= 0) {
|
||||||
|
result = result.substring (0, end);
|
||||||
|
}
|
||||||
|
return result.strip ();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string extract_address (string line) {
|
||||||
|
if (line == "")
|
||||||
|
return "";
|
||||||
|
var start = line.index_of ("[");
|
||||||
|
if (start >= 0) {
|
||||||
|
var end = line.index_of ("]", start);
|
||||||
|
if (end >= 0) {
|
||||||
|
var result = line.substring (start + 1, end - start - 1);
|
||||||
|
return result.strip ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string execute_command_sync_get_output (string cmd) {
|
||||||
|
try {
|
||||||
|
int exitCode;
|
||||||
|
string std_out;
|
||||||
|
string std_err;
|
||||||
|
Process.spawn_command_line_sync (cmd, out std_out, out std_err, out exitCode);
|
||||||
|
return std_out;
|
||||||
|
}
|
||||||
|
catch (Error e) {
|
||||||
|
warning (@"Error while executing '$cmd': $(e.message)");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poor's man demangler. libunwind is another dep
|
||||||
|
// TODO : Optimize this
|
||||||
|
// module : app
|
||||||
|
// address : 0x007f80
|
||||||
|
// output : /home/cran/Projects/noise/noise-perf-instant-search/tests/errors.vala:87
|
||||||
|
string process_line (string module, string address) {
|
||||||
|
var cmd = "addr2line -f -e %s %s".printf (module, address);
|
||||||
|
var result = execute_command_sync_get_output (cmd);
|
||||||
|
//stdout.printf( "CMD %s\n", cmd) ;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_reset_code () {
|
||||||
|
// return get_color_code (Style.RESET, Colors.WHITE, Colors.BLACK);
|
||||||
|
return "\x1b[0m";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_reset_style () {
|
||||||
|
return get_color_code (Style.DIM, highlight_color, background_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_color_code (Style attr, Color fg, Color bg = background_color) {
|
||||||
|
/* Command is the control command to the terminal */
|
||||||
|
if (bg == Color.BLACK)
|
||||||
|
return "%c[%d;%dm".printf (0x1B, (int) attr, (int) fg + 30);
|
||||||
|
else
|
||||||
|
return "%c[%d;%d;%dm".printf (0x1B, (int) attr, (int) fg + 30, (int) bg + 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_signal_name () {
|
||||||
|
return sig.to_string ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_highlight_code () {
|
||||||
|
return get_color_code (Style.BRIGHT, highlight_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_printable_function (Frame frame, int padding = 0) {
|
||||||
|
var result = "";
|
||||||
|
var is_unknown = false;
|
||||||
|
if (frame.function == "") {
|
||||||
|
result = "<unknown> " + frame.address;
|
||||||
|
is_unknown = true;
|
||||||
|
} else {
|
||||||
|
var s = "";
|
||||||
|
int count = padding - get_signal_name ().length;
|
||||||
|
if (padding != 0 && count > 0)
|
||||||
|
s = string.nfill (count, ' ');
|
||||||
|
result = "'" + frame.function + "'" + s;
|
||||||
|
}
|
||||||
|
if (is_unknown)
|
||||||
|
return result + get_reset_code ();
|
||||||
|
else
|
||||||
|
return get_highlight_code () + result + get_reset_code ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_printable_line_number (Frame frame, bool pad = true) {
|
||||||
|
var path = frame.line_number;
|
||||||
|
var result = "";
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
if (path.length >= max_line_number_length || !pad)
|
||||||
|
result = color + path + get_reset_style ();
|
||||||
|
else {
|
||||||
|
result = color + path + get_reset_style ();
|
||||||
|
result = string.nfill (max_line_number_length - path.length, ' ') + result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_printable_file_short_path (Frame frame, bool pad = true) {
|
||||||
|
var path = frame.file_short_path;
|
||||||
|
var result = "";
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
if (path.length >= max_file_name_length || !pad)
|
||||||
|
result = color + path + get_reset_style ();
|
||||||
|
else {
|
||||||
|
result = color + path + get_reset_style ();
|
||||||
|
result = result + string.nfill (max_file_name_length - path.length, ' ');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color background_color = Color.BLACK;
|
||||||
|
int title_length = 0;
|
||||||
|
|
||||||
|
private string get_printable_title () {
|
||||||
|
var c = get_color_code (Style.DIM, highlight_color, background_color);
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
|
||||||
|
var result = "" ;
|
||||||
|
|
||||||
|
if( is_custom)
|
||||||
|
result = "%sA function was called in %s".printf (
|
||||||
|
c,
|
||||||
|
get_reset_style ());
|
||||||
|
else
|
||||||
|
result = "%sAn error occured %s(%s)%s".printf (
|
||||||
|
c,
|
||||||
|
color,
|
||||||
|
get_signal_name (),
|
||||||
|
get_reset_style ());
|
||||||
|
|
||||||
|
title_length = get_signal_name ().length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_reason () {
|
||||||
|
// var c = get_reset_code();
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
if (sig == ProcessSignal.TRAP) {
|
||||||
|
return "The reason is likely %san uncaught error%s".printf (
|
||||||
|
color, get_reset_code ());
|
||||||
|
}
|
||||||
|
if (sig == ProcessSignal.ABRT) {
|
||||||
|
return "The reason is likely %sa failed assertion (assert...)%s".printf (
|
||||||
|
color, get_reset_code ());
|
||||||
|
}
|
||||||
|
if (sig == ProcessSignal.SEGV) {
|
||||||
|
return "The reason is likely %sa null reference being used%s".printf (
|
||||||
|
color, get_reset_code ());
|
||||||
|
}
|
||||||
|
return "Unknown reason";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void print () {
|
||||||
|
stdout.printf ("\n");
|
||||||
|
background_color = error_background;
|
||||||
|
var header = "%s%s\n".printf (get_printable_title (),
|
||||||
|
get_reset_code ());
|
||||||
|
|
||||||
|
if (first_vala != null) {
|
||||||
|
header = "%s in %s, line %s in %s\n".printf (
|
||||||
|
get_printable_title (),
|
||||||
|
get_printable_file_short_path (first_vala, false),
|
||||||
|
get_printable_line_number (first_vala, false),
|
||||||
|
get_printable_function (first_vala) + get_reset_code ());
|
||||||
|
title_length += first_vala.line_number.length +
|
||||||
|
first_vala.function.length +
|
||||||
|
first_vala.file_short_path.length;
|
||||||
|
}
|
||||||
|
stdout.printf (header);
|
||||||
|
background_color = Color.BLACK;
|
||||||
|
if( !is_custom) {
|
||||||
|
var reason = get_reason ();
|
||||||
|
stdout.printf ("%s\n", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has the user forgot to compile with -g -X -rdynamic flag ?
|
||||||
|
if (is_all_file_name_blank) {
|
||||||
|
//var advice = " %sNote%s: no file path and line numbers can be retrieved. Are you sure %syou added -g -X -rdynamic%s to valac command line?\n";
|
||||||
|
var advice = "%sNote%s: no vala function name can be retrieved.";
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has the user forgot to compile with rdynamic flag ?
|
||||||
|
if (is_all_function_name_blank && !is_all_file_name_blank) {
|
||||||
|
var advice = "%sNote%s: no vala function name can be retrieved.";
|
||||||
|
//var advice = " %sNote%s: no vala function name can be retrieved. Are you sure %syou added -X -rdynamic%s to valac command line?\n";
|
||||||
|
var color = get_highlight_code ();
|
||||||
|
stdout.printf (advice, color, get_reset_code (), color, get_reset_code ());
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.printf ("\n");
|
||||||
|
int i = 1;
|
||||||
|
bool has_displayed_first_vala = false;
|
||||||
|
foreach (var frame in _frames) {
|
||||||
|
var show_frame = frame.function != "" || frame.file_path.has_suffix (".vala") || frame.file_path.has_suffix (".c");
|
||||||
|
if (hide_installed_libraries && has_displayed_first_vala)
|
||||||
|
show_frame = show_frame && frame.file_short_path != "";
|
||||||
|
|
||||||
|
// Ignore glib tracing code if displayed before the first vala frame
|
||||||
|
if ((frame.function == "g_logv" || frame.function == "g_log") && !has_displayed_first_vala)
|
||||||
|
show_frame = false;
|
||||||
|
if (show_frame) {
|
||||||
|
// #2 ./OtherModule.c line 80 in 'other_module_do_it'
|
||||||
|
// at /home/cran/Projects/noise/noise-perf-instant-search/tests/errors/module/OtherModule.vala:10
|
||||||
|
var str = " %s #%d %s line %s in %s\n";
|
||||||
|
background_color = Color.BLACK;
|
||||||
|
var lead = " ";
|
||||||
|
var function_padding = 0;
|
||||||
|
if (frame == first_vala) {
|
||||||
|
has_displayed_first_vala = true;
|
||||||
|
lead = "*";
|
||||||
|
background_color = error_background;
|
||||||
|
function_padding = 22;
|
||||||
|
}
|
||||||
|
var l_number = "";
|
||||||
|
if (frame.line_number == "") {
|
||||||
|
str = " %s #%d <unknown> %s in %s\n";
|
||||||
|
var func_name = get_printable_function (frame);
|
||||||
|
var fill_len = int.max (max_file_name_length + max_line_number_length - 1, 0);
|
||||||
|
str = str.printf (
|
||||||
|
lead,
|
||||||
|
i,
|
||||||
|
string.nfill (fill_len, ' '),
|
||||||
|
func_name);
|
||||||
|
} else {
|
||||||
|
str = str.printf (
|
||||||
|
lead,
|
||||||
|
i,
|
||||||
|
get_printable_file_short_path (frame),
|
||||||
|
get_printable_line_number (frame),
|
||||||
|
get_printable_function (frame, function_padding));
|
||||||
|
l_number = ":" + frame.line_number;
|
||||||
|
}
|
||||||
|
stdout.printf (str);
|
||||||
|
str = " at %s%s\n".printf (
|
||||||
|
frame.file_path, l_number);
|
||||||
|
stdout.printf (str);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register_handlers () {
|
||||||
|
Process.@signal (ProcessSignal.SEGV, handler);
|
||||||
|
Process.@signal (ProcessSignal.ABRT, handler);
|
||||||
|
Process.@signal (ProcessSignal.TRAP, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CriticalHandler critical_handling { get;set;default = CriticalHandler.PRINT_STACKTRACE;}
|
||||||
|
|
||||||
|
public static void handler (int sig) {
|
||||||
|
Stacktrace stack = new Stacktrace ((ProcessSignal) sig);
|
||||||
|
stack.print ();
|
||||||
|
if (sig != ProcessSignal.TRAP ||
|
||||||
|
(sig == ProcessSignal.TRAP && critical_handling == CriticalHandler.CRASH))
|
||||||
|
Process.exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
public class Tootle.Utils {
|
||||||
|
|
||||||
|
public static void merge (GLib.Object what, GLib.Object with) {
|
||||||
|
var props = with.get_class ().list_properties ();
|
||||||
|
foreach (var prop in props) {
|
||||||
|
var name = prop.get_name ();
|
||||||
|
var defined = what.get_class ().find_property (name) != null;
|
||||||
|
if (defined) {
|
||||||
|
var val = Value (prop.value_type);
|
||||||
|
with.get_property (name, ref val);
|
||||||
|
what.set_property (name, val) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,73 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
|
|
||||||
public abstract class Tootle.Views.Abstract : ScrolledWindow {
|
|
||||||
|
|
||||||
public bool current = false;
|
|
||||||
public int stack_pos = -1;
|
|
||||||
public Image? image;
|
|
||||||
public Box view;
|
|
||||||
protected Box? empty;
|
|
||||||
protected Grid? header;
|
|
||||||
|
|
||||||
construct {
|
|
||||||
view = new Box (Orientation.VERTICAL, 0);
|
|
||||||
view.valign = Align.START;
|
|
||||||
add (view);
|
|
||||||
|
|
||||||
hscrollbar_policy = PolicyType.NEVER;
|
|
||||||
edge_reached.connect (pos => {
|
|
||||||
if (pos == PositionType.BOTTOM)
|
|
||||||
on_bottom_reached ();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Abstract () {
|
|
||||||
show_all ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual string get_icon () {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual string get_name () {
|
|
||||||
return "unnamed";
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void clear (){
|
|
||||||
view.forall (widget => {
|
|
||||||
if (widget != header)
|
|
||||||
widget.destroy ();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void on_bottom_reached () {}
|
|
||||||
public virtual void on_set_current () {}
|
|
||||||
|
|
||||||
public virtual bool is_empty () {
|
|
||||||
return view.get_children ().length () <= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool empty_state () {
|
|
||||||
if (empty != null)
|
|
||||||
empty.destroy ();
|
|
||||||
if (!is_empty ())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
empty = new Box (Orientation.VERTICAL, 0);
|
|
||||||
empty.margin = 64;
|
|
||||||
var image = new Image.from_resource ("/com/github/bleakgrey/tootle/empty_state");
|
|
||||||
var text = new Label (_("Nothing to see here"));
|
|
||||||
text.get_style_context ().add_class ("h2");
|
|
||||||
text.opacity = 0.5;
|
|
||||||
empty.hexpand = true;
|
|
||||||
empty.vexpand = true;
|
|
||||||
empty.valign = Align.FILL;
|
|
||||||
empty.pack_start (image, false, false, 0);
|
|
||||||
empty.pack_start (text, false, false, 12);
|
|
||||||
empty.show_all ();
|
|
||||||
view.pack_start (empty, false, false, 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/views/base.ui")]
|
||||||
|
public class Tootle.Views.Base : Box {
|
||||||
|
|
||||||
|
public static string STATUS_EMPTY = _("Nothing to see here");
|
||||||
|
public static string STATUS_LOADING = " ";
|
||||||
|
|
||||||
|
public bool current = false;
|
||||||
|
public int stack_pos = -1;
|
||||||
|
public Image? image;
|
||||||
|
|
||||||
|
[GtkChild]
|
||||||
|
protected ScrolledWindow scrolled;
|
||||||
|
[GtkChild]
|
||||||
|
protected Box view;
|
||||||
|
[GtkChild]
|
||||||
|
protected Stack states;
|
||||||
|
[GtkChild]
|
||||||
|
protected Box content;
|
||||||
|
[GtkChild]
|
||||||
|
private Label status_message_label;
|
||||||
|
[GtkChild]
|
||||||
|
protected Button status_button;
|
||||||
|
[GtkChild]
|
||||||
|
private Stack status_stack;
|
||||||
|
|
||||||
|
public string state { get; set; default = "status"; }
|
||||||
|
public string status_message { get; set; default = STATUS_EMPTY; }
|
||||||
|
public bool allow_closing { get; set; default = true; }
|
||||||
|
|
||||||
|
public bool empty {
|
||||||
|
get {
|
||||||
|
return content.get_children ().length () <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
construct {
|
||||||
|
status_button.label = _("Reload");
|
||||||
|
bind_property ("state", states, "visible-child-name", BindingFlags.SYNC_CREATE);
|
||||||
|
scrolled.edge_reached.connect (pos => {
|
||||||
|
if (pos == PositionType.BOTTOM)
|
||||||
|
on_bottom_reached ();
|
||||||
|
});
|
||||||
|
content.remove.connect (() => on_content_changed ());
|
||||||
|
|
||||||
|
notify["status-message"].connect (() => {
|
||||||
|
status_message_label.label = @"<span size='large'>$status_message</span>";
|
||||||
|
status_stack.visible_child_name = status_message == STATUS_LOADING ? "spinner" : "message";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string get_icon () {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string get_name () {
|
||||||
|
return "unnamed";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void clear (){
|
||||||
|
content.forall (widget => {
|
||||||
|
widget.destroy ();
|
||||||
|
});
|
||||||
|
state = "status";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void on_bottom_reached () {}
|
||||||
|
public virtual void on_set_current () {}
|
||||||
|
|
||||||
|
public virtual void on_content_changed () {
|
||||||
|
if (empty) {
|
||||||
|
status_message = STATUS_EMPTY;
|
||||||
|
state = "status";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = "content";
|
||||||
|
}
|
||||||
|
check_resize ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void on_error (int32 code, string reason) {
|
||||||
|
status_message = reason;
|
||||||
|
status_button.visible = true;
|
||||||
|
status_button.sensitive = true;
|
||||||
|
state = "status";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,20 +1,19 @@
|
||||||
public class Tootle.Views.Direct : Views.Timeline {
|
public class Tootle.Views.Direct : Views.Timeline {
|
||||||
|
|
||||||
public Direct () {
|
public Direct () {
|
||||||
base ("direct");
|
Object (timeline: "direct");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_icon () {
|
public override string get_icon () {
|
||||||
return "mail-send-symbolic";
|
return "mail-send-symbolic";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_name () {
|
public override string get_name () {
|
||||||
return _("Direct Messages");
|
return _("Direct Messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Soup.Message? get_stream () {
|
public override string? get_stream_url () {
|
||||||
var url = "%s/api/v1/streaming/?stream=direct&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
return @"/api/v1/streaming/?stream=direct&access_token=$(accounts.active.token)";
|
||||||
return new Soup.Message("GET", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,111 +1,91 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
public class Tootle.Views.ExpandedStatus : Views.Abstract {
|
public class Tootle.Views.ExpandedStatus : Views.Base, IAccountListener {
|
||||||
|
|
||||||
private API.Status root_status;
|
public API.Status root_status { get; construct set; }
|
||||||
private bool last_status_was_root = false;
|
protected InstanceAccount? account = null;
|
||||||
private bool sensitive_visible = false;
|
protected Widgets.Status root_widget;
|
||||||
|
|
||||||
public ExpandedStatus (API.Status status) {
|
public ExpandedStatus (API.Status status) {
|
||||||
base ();
|
Object (root_status: status, state: "content");
|
||||||
root_status = status;
|
|
||||||
|
root_widget = append (status);
|
||||||
|
root_widget.avatar.button_press_event.connect (root_widget.on_avatar_clicked);
|
||||||
|
root_widget.get_style_context ().add_class ("card");
|
||||||
|
root_widget.get_style_context ().add_class ("highlight");
|
||||||
|
|
||||||
|
connect_account ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void on_account_changed (InstanceAccount? acc) {
|
||||||
|
account = acc;
|
||||||
request ();
|
request ();
|
||||||
|
|
||||||
window.button_reveal.clicked.connect (on_reveal_toggle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~ExpandedStatus () {
|
private Widgets.Status prepend (API.Status status, bool to_end = false){
|
||||||
if (window != null) {
|
|
||||||
window.button_reveal.clicked.disconnect (on_reveal_toggle);
|
|
||||||
window.button_reveal.hide ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepend (API.Status status, bool is_root = false){
|
|
||||||
var separator = new Separator (Orientation.HORIZONTAL);
|
|
||||||
separator.show ();
|
|
||||||
|
|
||||||
var widget = new Widgets.Status (status);
|
var widget = new Widgets.Status (status);
|
||||||
widget.avatar.button_press_event.connect (widget.on_avatar_clicked);
|
widget.avatar.button_press_event.connect (widget.on_avatar_clicked);
|
||||||
if (!is_root)
|
widget.revealer.reveal_child = true;
|
||||||
widget.button_press_event.connect (widget.open);
|
|
||||||
else
|
|
||||||
widget.highlight ();
|
|
||||||
|
|
||||||
if (!last_status_was_root) {
|
content.pack_start (widget, false, false, 0);
|
||||||
widget.separator = separator;
|
if (!to_end)
|
||||||
view.pack_start (separator, false, false, 0);
|
content.reorder_child (widget, 0);
|
||||||
}
|
|
||||||
view.pack_start (widget, false, false, 0);
|
|
||||||
last_status_was_root = is_root;
|
|
||||||
|
|
||||||
if (status.has_spoiler ())
|
check_resize ();
|
||||||
window.button_reveal.show ();
|
return widget;
|
||||||
if (sensitive_visible)
|
}
|
||||||
reveal_sensitive (widget);
|
private Widgets.Status append (API.Status status) {
|
||||||
|
return prepend (status, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message request (){
|
public void request () {
|
||||||
var url = "%s/api/v1/statuses/%lld/context".printf (accounts.formal.instance, root_status.id);
|
new Request.GET (@"/api/v1/statuses/$(root_status.id)/context")
|
||||||
var msg = new Soup.Message ("GET", url);
|
.with_account (account)
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.then_parse_obj (root => {
|
||||||
network.queue (msg, (sess, mess) => {
|
if (scrolled == null) return;
|
||||||
var root = network.parse (mess);
|
|
||||||
var ancestors = root.get_array_member ("ancestors");
|
var ancestors = root.get_array_member ("ancestors");
|
||||||
ancestors.foreach_element ((array, i, node) => {
|
ancestors.foreach_element ((array, i, node) => {
|
||||||
var object = node.get_object ();
|
var object = node.get_object ();
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
var status = API.Status.parse (object);
|
var status = new API.Status (object);
|
||||||
prepend (status);
|
prepend (status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var descendants = root.get_array_member ("descendants");
|
||||||
|
descendants.foreach_element ((array, i, node) => {
|
||||||
|
var object = node.get_object ();
|
||||||
|
if (object != null) {
|
||||||
|
var status = new API.Status (object);
|
||||||
|
append (status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int x,y;
|
||||||
|
translate_coordinates (root_widget, 0, 0, out x, out y);
|
||||||
|
scrolled.vadjustment.value = (double)(y*-1); //TODO: Animate scrolling?
|
||||||
|
})
|
||||||
|
.exec ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open_from_link (string q) {
|
||||||
|
new Request.GET ("/api/v1/search")
|
||||||
|
.with_account ()
|
||||||
|
.with_param ("q", q)
|
||||||
|
.with_param ("resolve", "true")
|
||||||
|
.then ((sess, msg) => {
|
||||||
|
var root = network.parse (msg);
|
||||||
|
var statuses = root.get_array_member ("statuses");
|
||||||
|
var object = statuses.get_element (0).get_object ();
|
||||||
|
if (object != null){
|
||||||
|
var status = new API.Status (object);
|
||||||
|
window.open_view (new Views.ExpandedStatus (status));
|
||||||
}
|
}
|
||||||
});
|
else
|
||||||
|
Desktop.open_uri (q);
|
||||||
prepend (root_status, true);
|
})
|
||||||
|
.exec ();
|
||||||
var descendants = root.get_array_member ("descendants");
|
|
||||||
descendants.foreach_element ((array, i, node) => {
|
|
||||||
var object = node.get_object ();
|
|
||||||
if (object != null) {
|
|
||||||
var status = API.Status.parse (object);
|
|
||||||
prepend (status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void open_from_link (string q){
|
|
||||||
var url = "%s/api/v1/search?q=%s&resolve=true".printf (accounts.formal.instance, q);
|
|
||||||
var msg = new Soup.Message ("GET", url);
|
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, mess) => {
|
|
||||||
var root = network.parse (mess);
|
|
||||||
var statuses = root.get_array_member ("statuses");
|
|
||||||
var object = statuses.get_element (0).get_object ();
|
|
||||||
if (object != null){
|
|
||||||
var st = API.Status.parse (object);
|
|
||||||
window.open_view (new Views.ExpandedStatus (st));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Desktop.open_uri (q);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_reveal_toggle () {
|
|
||||||
sensitive_visible = !sensitive_visible;
|
|
||||||
view.forall (w => {
|
|
||||||
if (!(w is Widgets.Status))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var widget = w as Widgets.Status;
|
|
||||||
reveal_sensitive (widget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reveal_sensitive (Widgets.Status widget) {
|
|
||||||
if (widget.status.has_spoiler ())
|
|
||||||
widget.revealer.reveal_child = sensitive_visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
public class Tootle.Views.Favorites : Views.Timeline {
|
public class Tootle.Views.Favorites : Views.Timeline {
|
||||||
|
|
||||||
public Favorites () {
|
public Favorites () {
|
||||||
base ("favorites");
|
Object (timeline: "favorites");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_url (){
|
public override string get_url (){
|
||||||
if (page_next != null)
|
if (page_next != null)
|
||||||
return page_next;
|
return page_next;
|
||||||
|
|
||||||
var url = "%s/api/v1/favourites/?limit=%i".printf (accounts.formal.instance, this.limit);
|
return @"/api/v1/favourites";
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
public class Tootle.Views.Federated : Views.Timeline {
|
public class Tootle.Views.Federated : Views.Timeline {
|
||||||
|
|
||||||
public Federated () {
|
public Federated () {
|
||||||
base ("public");
|
Object (timeline: "public", is_public: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_icon () {
|
public override string get_icon () {
|
||||||
return "network-workgroup-symbolic";
|
return "network-workgroup-symbolic";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_name () {
|
public override string get_name () {
|
||||||
return _("Federated Timeline");
|
return _("Federated Timeline");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool is_public () {
|
public override string? get_stream_url () {
|
||||||
return true;
|
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public&access_token=$(account.token)" : null;
|
||||||
}
|
|
||||||
|
|
||||||
public override Soup.Message? get_stream () {
|
|
||||||
var url = "%s/api/v1/streaming/?stream=public&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
|
||||||
return new Soup.Message("GET", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
using Gtk;
|
|
||||||
|
|
||||||
public class Tootle.Views.Followers : Views.Timeline {
|
|
||||||
|
|
||||||
public Followers (API.Account account) {
|
|
||||||
base (account.id.to_string ());
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void append (API.Account account){
|
|
||||||
if (empty != null)
|
|
||||||
empty.destroy ();
|
|
||||||
|
|
||||||
var separator = new Separator (Orientation.HORIZONTAL);
|
|
||||||
separator.show ();
|
|
||||||
|
|
||||||
var widget = new Widgets.Account (account);
|
|
||||||
widget.separator = separator;
|
|
||||||
view.pack_start (separator, false, false, 0);
|
|
||||||
view.pack_start (widget, false, false, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string get_url (){
|
|
||||||
if (page_next != null)
|
|
||||||
return page_next;
|
|
||||||
|
|
||||||
var url = "%s/api/v1/accounts/%s/followers".printf (accounts.formal.instance, this.timeline);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void request (){
|
|
||||||
var msg = new Soup.Message("GET", get_url ());
|
|
||||||
msg.finished.connect (() => empty_state ());
|
|
||||||
network.queue (msg, (sess, mess) => {
|
|
||||||
try {
|
|
||||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
|
||||||
var object = node.get_object ();
|
|
||||||
if (object != null){
|
|
||||||
var status = API.Account.parse (object);
|
|
||||||
append (status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
get_pages (mess.response_headers.get_one ("Link"));
|
|
||||||
}
|
|
||||||
catch (GLib.Error e) {
|
|
||||||
warning ("Can't get account follow info:");
|
|
||||||
warning (e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
public class Tootle.Views.Following : Views.Followers {
|
|
||||||
|
|
||||||
public Following (API.Account account) {
|
|
||||||
base (account);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string get_url (){
|
|
||||||
if (page_next != null)
|
|
||||||
return page_next;
|
|
||||||
|
|
||||||
var url = "%s/api/v1/accounts/%s/following".printf (accounts.formal.instance, this.timeline);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,20 +1,12 @@
|
||||||
public class Tootle.Views.Hashtag : Views.Timeline {
|
public class Tootle.Views.Hashtag : Views.Timeline {
|
||||||
|
|
||||||
public Hashtag (string hashtag) {
|
public Hashtag (string tag) {
|
||||||
base ("tag/" + hashtag);
|
Object (timeline: @"tag/$tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_hashtag () {
|
public override string? get_stream_url () {
|
||||||
return this.timeline.substring (4);
|
var tag = timeline.substring (4);
|
||||||
}
|
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.token)" : null;
|
||||||
|
|
||||||
public override string get_name () {
|
|
||||||
return get_hashtag ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Soup.Message? get_stream () {
|
|
||||||
var url = "%s/api/v1/streaming/?stream=hashtag&tag=%s&access_token=%s".printf (accounts.formal.instance, get_hashtag (), accounts.formal.token);
|
|
||||||
return new Soup.Message("GET", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
public class Tootle.Views.Home : Views.Timeline {
|
public class Tootle.Views.Home : Views.Timeline {
|
||||||
|
|
||||||
public Home () {
|
public Home () {
|
||||||
base ("home");
|
Object (timeline: "home");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_icon () {
|
public override string get_icon () {
|
||||||
|
@ -12,8 +12,8 @@ public class Tootle.Views.Home : Views.Timeline {
|
||||||
return _("Home");
|
return _("Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Soup.Message? get_stream () {
|
public override string? get_stream_url () {
|
||||||
return accounts.formal.get_stream ();
|
return account.get_stream_url () ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
public class Tootle.Views.Local : Views.Timeline {
|
public class Tootle.Views.Local : Views.Federated {
|
||||||
|
|
||||||
public Local () {
|
|
||||||
base ("public");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string get_icon () {
|
public override string get_icon () {
|
||||||
return Desktop.fallback_icon ("system-users-symbolic", "document-open-recent-symbolic");
|
return Desktop.fallback_icon ("system-users-symbolic", "document-open-recent-symbolic");
|
||||||
|
@ -12,19 +8,14 @@ public class Tootle.Views.Local : Views.Timeline {
|
||||||
return _("Local Timeline");
|
return _("Local Timeline");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_url (){
|
public override Request append_params (Request req) {
|
||||||
var url = base.get_url ();
|
req.with_param ("local", "true");
|
||||||
url += "&local=true";
|
req.with_param ("limit", limit.to_string ());
|
||||||
return url;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool is_public () {
|
public override string? get_stream_url () {
|
||||||
return true;
|
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=public:local&access_token=$(account.token)" : null;
|
||||||
}
|
|
||||||
|
|
||||||
public override Soup.Message? get_stream () {
|
|
||||||
var url = "%s/api/v1/streaming/?stream=public:local&access_token=%s".printf (accounts.formal.instance, accounts.formal.token);
|
|
||||||
return new Soup.Message("GET", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
public class Tootle.Views.NewAccount : Views.Base {
|
||||||
|
|
||||||
|
private string? instance { get; set; }
|
||||||
|
private string? code { get; set; }
|
||||||
|
private string scopes = "read%20write%20follow";
|
||||||
|
|
||||||
|
private string? client_id { get; set; }
|
||||||
|
private string? client_secret { get; set; }
|
||||||
|
private string? access_token { get; set; }
|
||||||
|
private string redirect_uri { get; set; default = "urn:ietf:wg:oauth:2.0:oob"; } //TODO: Investigate URI handling for automatic token getting
|
||||||
|
private InstanceAccount account;
|
||||||
|
|
||||||
|
private Button next_button;
|
||||||
|
private Entry instance_entry;
|
||||||
|
private Entry code_entry;
|
||||||
|
private Label reset_label;
|
||||||
|
|
||||||
|
private Stack stack;
|
||||||
|
private Widget step1;
|
||||||
|
private Widget step2;
|
||||||
|
|
||||||
|
public NewAccount (bool allow_closing = true) {
|
||||||
|
Object (allow_closing: allow_closing);
|
||||||
|
|
||||||
|
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/new_account.ui");
|
||||||
|
content.pack_start (builder.get_object ("wizard") as Grid);
|
||||||
|
state = "content";
|
||||||
|
next_button = builder.get_object ("next") as Button;
|
||||||
|
reset_label = builder.get_object ("reset") as Label;
|
||||||
|
instance_entry = builder.get_object ("instance_entry") as Entry;
|
||||||
|
code_entry = builder.get_object ("code_entry") as Entry;
|
||||||
|
|
||||||
|
stack = builder.get_object ("stack") as Stack;
|
||||||
|
step1 = builder.get_object ("step1") as Widget;
|
||||||
|
step2 = builder.get_object ("step2") as Widget;
|
||||||
|
|
||||||
|
next_button.clicked.connect (on_next_clicked);
|
||||||
|
reset_label.activate_link.connect (reset);
|
||||||
|
instance_entry.text = "https://mastodon.social/"; //TODO: REMOVE ME
|
||||||
|
info ("New account view was requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool reset () {
|
||||||
|
info ("State invalidated");
|
||||||
|
instance = code = client_id = client_secret = access_token = null;
|
||||||
|
instance_entry.sensitive = true;
|
||||||
|
stack.visible_child = step1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void oopsie (string message) {
|
||||||
|
warning (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_next_clicked () {
|
||||||
|
try {
|
||||||
|
step ();
|
||||||
|
}
|
||||||
|
catch (Oopsie e) {
|
||||||
|
oopsie (e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void step () throws Error {
|
||||||
|
if (instance == null)
|
||||||
|
setup_instance ();
|
||||||
|
|
||||||
|
if (client_secret == null || client_id == null) {
|
||||||
|
register_client ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
code = code_entry.text;
|
||||||
|
request_token ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup_instance () throws Error {
|
||||||
|
info ("Checking instance URL");
|
||||||
|
|
||||||
|
var str = instance_entry.text
|
||||||
|
.replace ("/", "")
|
||||||
|
.replace (":", "")
|
||||||
|
.replace ("https", "")
|
||||||
|
.replace ("http", "");
|
||||||
|
instance = "https://"+str;
|
||||||
|
instance_entry.text = str;
|
||||||
|
|
||||||
|
if (str.char_count () <= 0 || !("." in instance))
|
||||||
|
throw new Oopsie.USER (_("Instance URL is invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register_client () throws Error {
|
||||||
|
info ("Registering client");
|
||||||
|
instance_entry.sensitive = false;
|
||||||
|
|
||||||
|
account = new InstanceAccount.empty (instance);
|
||||||
|
|
||||||
|
new Request.POST (@"/api/v1/apps")
|
||||||
|
.with_param ("client_name", Build.NAME)
|
||||||
|
.with_param ("website", Build.WEBSITE)
|
||||||
|
.with_param ("scopes", scopes)
|
||||||
|
.with_param ("redirect_uris", redirect_uri)
|
||||||
|
.with_account (account)
|
||||||
|
.then ((sess, msg) => {
|
||||||
|
var root = network.parse (msg);
|
||||||
|
client_id = root.get_string_member ("client_id");
|
||||||
|
client_secret = root.get_string_member ("client_secret");
|
||||||
|
info ("OK: instance registered client");
|
||||||
|
stack.visible_child = step2;
|
||||||
|
|
||||||
|
open_confirmation_page ();
|
||||||
|
})
|
||||||
|
.on_error ((status, reason) => {
|
||||||
|
oopsie (reason);
|
||||||
|
instance_entry.sensitive = true;
|
||||||
|
})
|
||||||
|
.exec ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void open_confirmation_page () {
|
||||||
|
info ("Opening permission request page");
|
||||||
|
|
||||||
|
var pars = @"scope=$scopes&response_type=code&redirect_uri=$redirect_uri&client_id=$client_id";
|
||||||
|
var url = @"$instance/oauth/authorize?$pars";
|
||||||
|
Desktop.open_uri (url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void request_token () throws Error {
|
||||||
|
if (code.char_count () <= 10)
|
||||||
|
throw new Oopsie.USER (_("Please paste a valid authorization code"));
|
||||||
|
|
||||||
|
info ("Requesting access token");
|
||||||
|
new Request.POST (@"/oauth/token")
|
||||||
|
.with_account (account)
|
||||||
|
.with_param ("client_id", client_id)
|
||||||
|
.with_param ("client_secret", client_secret)
|
||||||
|
.with_param ("redirect_uri", redirect_uri)
|
||||||
|
.with_param ("grant_type", "authorization_code")
|
||||||
|
.with_param ("code", code)
|
||||||
|
.then ((sess, msg) => {
|
||||||
|
var root = network.parse (msg);
|
||||||
|
access_token = root.get_string_member ("access_token");
|
||||||
|
account.token = access_token;
|
||||||
|
account.id = 0;
|
||||||
|
info ("OK: received access token");
|
||||||
|
request_profile ();
|
||||||
|
})
|
||||||
|
.on_error ((code, reason) => oopsie (reason))
|
||||||
|
.exec ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void request_profile () throws Error {
|
||||||
|
info ("Testing received access token");
|
||||||
|
new Request.GET ("/api/v1/accounts/verify_credentials")
|
||||||
|
.with_account (account)
|
||||||
|
.then ((sess, msg) => {
|
||||||
|
var root = network.parse (msg);
|
||||||
|
var account = new API.Account (root);
|
||||||
|
info ("OK: received user profile");
|
||||||
|
save (account);
|
||||||
|
})
|
||||||
|
.on_error ((status, reason) => {
|
||||||
|
reset ();
|
||||||
|
oopsie (reason);
|
||||||
|
})
|
||||||
|
.exec ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save (API.Account profile) {
|
||||||
|
info ("Account validated. Saving...");
|
||||||
|
account.patch (profile);
|
||||||
|
account.instance = instance;
|
||||||
|
account.client_id = client_id;
|
||||||
|
account.client_secret = client_secret;
|
||||||
|
account.token = access_token;
|
||||||
|
accounts.add (account);
|
||||||
|
|
||||||
|
destroy ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,23 +1,20 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Gdk;
|
using Gdk;
|
||||||
|
|
||||||
public class Tootle.Views.Notifications : Views.Abstract {
|
public class Tootle.Views.Notifications : Views.Base, IAccountListener {
|
||||||
|
|
||||||
private int64 last_id = 0;
|
protected InstanceAccount? account = null;
|
||||||
private bool force_dot = false;
|
protected int64 last_id = 0;
|
||||||
|
protected bool force_dot = false;
|
||||||
|
|
||||||
public Notifications () {
|
public Notifications () {
|
||||||
base ();
|
|
||||||
view.remove.connect (on_remove);
|
|
||||||
accounts.switched.connect (on_account_changed);
|
|
||||||
app.refresh.connect (on_refresh);
|
app.refresh.connect (on_refresh);
|
||||||
network.notification.connect (prepend);
|
status_button.clicked.connect (on_refresh);
|
||||||
|
streams.notification.connect (prepend);
|
||||||
request ();
|
connect_account ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool has_unread () {
|
private bool has_unread () {
|
||||||
var account = accounts.formal;
|
|
||||||
if (account == null)
|
if (account == null)
|
||||||
return false;
|
return false;
|
||||||
return last_id > account.last_seen_notification || force_dot;
|
return last_id > account.last_seen_notification || force_dot;
|
||||||
|
@ -39,38 +36,33 @@ public class Tootle.Views.Notifications : Views.Abstract {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void append (API.Notification notification, bool reverse = false) {
|
public void append (API.Notification notification, bool reverse = false) {
|
||||||
if (empty != null)
|
GLib.Idle.add (() => {
|
||||||
empty.destroy ();
|
var widget = new Widgets.Notification (notification);
|
||||||
|
content.pack_start (widget, false, false, 0);
|
||||||
|
|
||||||
var separator = new Separator (Orientation.HORIZONTAL);
|
if (reverse) {
|
||||||
separator.show ();
|
content.reorder_child (widget, 0);
|
||||||
|
|
||||||
var widget = new Widgets.Notification (notification);
|
if (!current) {
|
||||||
widget.separator = separator;
|
force_dot = true;
|
||||||
view.pack_start (separator, false, false, 0);
|
accounts.active.has_unread_notifications = force_dot;
|
||||||
view.pack_start (widget, false, false, 0);
|
}
|
||||||
|
|
||||||
if (reverse) {
|
|
||||||
view.reorder_child (widget, 0);
|
|
||||||
view.reorder_child (separator, 0);
|
|
||||||
|
|
||||||
if (!current) {
|
|
||||||
force_dot = true;
|
|
||||||
accounts.formal.has_unread_notifications = force_dot;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (notification.id > last_id)
|
on_content_changed ();
|
||||||
last_id = notification.id;
|
|
||||||
|
|
||||||
if (has_unread ()) {
|
if (notification.id > last_id)
|
||||||
accounts.save ();
|
last_id = notification.id;
|
||||||
image.icon_name = get_icon ();
|
|
||||||
}
|
if (has_unread ()) {
|
||||||
|
accounts.save ();
|
||||||
|
image.icon_name = get_icon ();
|
||||||
|
}
|
||||||
|
return GLib.Source.REMOVE;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void on_set_current () {
|
public override void on_set_current () {
|
||||||
var account = accounts.formal;
|
|
||||||
if (has_unread ()) {
|
if (has_unread ()) {
|
||||||
force_dot = false;
|
force_dot = false;
|
||||||
account.has_unread_notifications = force_dot;
|
account.has_unread_notifications = force_dot;
|
||||||
|
@ -80,73 +72,58 @@ public class Tootle.Views.Notifications : Views.Abstract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_remove (Widget widget) {
|
public override void on_content_changed () {
|
||||||
if (!(widget is Widgets.Notification))
|
base.on_content_changed ();
|
||||||
return;
|
if (image != null && empty)
|
||||||
|
|
||||||
empty_state ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool empty_state () {
|
|
||||||
var is_empty = base.empty_state ();
|
|
||||||
if (image != null && is_empty)
|
|
||||||
image.icon_name = get_icon ();
|
image.icon_name = get_icon ();
|
||||||
|
|
||||||
return is_empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_refresh () {
|
public virtual void on_refresh () {
|
||||||
clear ();
|
clear ();
|
||||||
request ();
|
GLib.Idle.add (request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_account_changed (API.Account? account) {
|
public virtual void on_account_changed (InstanceAccount? acc) {
|
||||||
if (account == null)
|
account = acc;
|
||||||
return;
|
if (account == null) {
|
||||||
|
last_id = 0;
|
||||||
last_id = accounts.formal.last_seen_notification;
|
force_dot = false;
|
||||||
force_dot = accounts.formal.has_unread_notifications;
|
}
|
||||||
on_refresh ();
|
else {
|
||||||
|
last_id = account.last_seen_notification;
|
||||||
|
force_dot = account.has_unread_notifications;
|
||||||
|
}
|
||||||
|
on_refresh ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void request () {
|
public bool request () {
|
||||||
if (accounts.current == null) {
|
if (account != null) {
|
||||||
empty_state ();
|
account.cached_notifications.@foreach (notification => {
|
||||||
return;
|
append (notification);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts.formal.cached_notifications.@foreach (notification => {
|
// new Request.GET ("/api/v1/follow_requests") //TODO: this
|
||||||
append (notification);
|
// .with_account ()
|
||||||
return true;
|
// .then_parse_array (node => {
|
||||||
});
|
// var notification = API.Notification.parse_follow_request (node.get_object ());
|
||||||
|
// append (notification);
|
||||||
|
// })
|
||||||
|
// .on_error (on_error)
|
||||||
|
// .exec ();
|
||||||
|
|
||||||
var url = "%s/api/v1/follow_requests".printf (accounts.formal.instance);
|
new Request.GET ("/api/v1/notifications")
|
||||||
var msg = new Soup.Message ("GET", url);
|
.with_account (account)
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.with_param ("limit", "30")
|
||||||
network.queue (msg, (sess, mess) => {
|
.then_parse_array (node => {
|
||||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
var notification = new API.Notification (node.get_object ());
|
||||||
var obj = node.get_object ();
|
append (notification);
|
||||||
if (obj != null){
|
})
|
||||||
var notification = API.Notification.parse_follow_request (obj);
|
.on_error (on_error)
|
||||||
append (notification);
|
.exec ();
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var url2 = "%s/api/v1/notifications?limit=30".printf (accounts.formal.instance);
|
return GLib.Source.REMOVE;
|
||||||
var msg2 = new Soup.Message ("GET", url2);
|
|
||||||
network.inject (msg2, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg2, (sess, mess) => {
|
|
||||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
|
||||||
var obj = node.get_object ();
|
|
||||||
if (obj != null){
|
|
||||||
var notification = API.Notification.parse (obj);
|
|
||||||
append (notification);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
empty_state ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,249 +1,190 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Granite;
|
|
||||||
|
|
||||||
public class Tootle.Views.Profile : Views.Timeline {
|
public class Tootle.Views.Profile : Views.Timeline {
|
||||||
|
|
||||||
const int AVATAR_SIZE = 128;
|
public API.Account profile { get; construct set; }
|
||||||
protected API.Account account;
|
|
||||||
|
protected RadioButton filter_all;
|
||||||
|
protected RadioButton filter_replies;
|
||||||
|
protected RadioButton filter_media;
|
||||||
|
|
||||||
protected Grid header_image;
|
|
||||||
protected Box header_info;
|
|
||||||
protected Granite.Widgets.Avatar avatar;
|
|
||||||
protected Widgets.RichLabel display_name;
|
|
||||||
protected Label username;
|
|
||||||
protected Label relationship;
|
protected Label relationship;
|
||||||
protected Widgets.RichLabel note;
|
|
||||||
protected Grid counters;
|
|
||||||
protected Box actions;
|
protected Box actions;
|
||||||
protected Button button_follow;
|
protected Button follow_button;
|
||||||
|
protected MenuButton options_button;
|
||||||
protected Gtk.Menu menu;
|
|
||||||
protected Gtk.MenuItem menu_edit;
|
|
||||||
protected Gtk.MenuItem menu_mention;
|
|
||||||
protected Gtk.MenuItem menu_mute;
|
|
||||||
protected Gtk.MenuItem menu_block;
|
|
||||||
protected Gtk.MenuItem menu_report;
|
|
||||||
protected Gtk.MenuButton button_menu;
|
|
||||||
|
|
||||||
|
protected Label posts_label;
|
||||||
|
protected Label following_label;
|
||||||
|
protected Label followers_label;
|
||||||
|
protected RadioButton posts_tab;
|
||||||
|
protected RadioButton following_tab;
|
||||||
|
protected RadioButton followers_tab;
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
header = new Grid ();
|
profile.notify["rs"].connect (on_rs_updated);
|
||||||
header_info = new Box (Orientation.VERTICAL, 0);
|
|
||||||
header_info.margin = 12;
|
|
||||||
actions = new Box (Orientation.HORIZONTAL, 0);
|
|
||||||
actions.hexpand = false;
|
|
||||||
actions.halign = Align.END;
|
|
||||||
actions.vexpand = false;
|
|
||||||
actions.valign = Align.START;
|
|
||||||
actions.margin = 12;
|
|
||||||
|
|
||||||
relationship = new Label ("");
|
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/profile_header.ui");
|
||||||
relationship.get_style_context ().add_class ("relationship");
|
view.pack_start (builder.get_object ("grid") as Grid, false, false, 0);
|
||||||
relationship.halign = Align.START;
|
|
||||||
relationship.valign = Align.START;
|
|
||||||
relationship.margin = 12;
|
|
||||||
header.attach (relationship, 0, 0, 1, 1);
|
|
||||||
|
|
||||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
var avatar = builder.get_object ("avatar") as Widgets.Avatar;
|
||||||
avatar.hexpand = true;
|
avatar.url = profile.avatar;
|
||||||
avatar.margin_bottom = 6;
|
|
||||||
header_info.pack_start (avatar, false, false, 0);
|
|
||||||
|
|
||||||
display_name = new Widgets.RichLabel ("");
|
var name = builder.get_object ("name") as Widgets.RichLabel;
|
||||||
display_name.get_style_context ().add_class (Granite.STYLE_CLASS_H2_LABEL);
|
profile.bind_property ("display-name", name, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
header_info.pack_start (display_name, false, false, 0);
|
var label = (string) src;
|
||||||
|
target.set_string (@"<span size='x-large' weight='bold'>$label</span>");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
username = new Label ("");
|
var handle = builder.get_object ("handle") as Widgets.RichLabel;
|
||||||
header_info.pack_start (username, false, false, 0);
|
profile.bind_property ("acct", handle, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
|
target.set_string ("@" + (string) src);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
note = new Widgets.RichLabel ("");
|
var note = builder.get_object ("note") as Widgets.RichLabel;
|
||||||
note.set_line_wrap (true);
|
profile.bind_property ("note", note, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
note.selectable = true;
|
target.set_string (Html.simplify ((string) src));
|
||||||
note.margin_top = 12;
|
return true;
|
||||||
note.can_focus = false;
|
});
|
||||||
note.justify = Justification.CENTER;
|
|
||||||
header_info.pack_start (note, false, false, 0);
|
|
||||||
header_info.show_all ();
|
|
||||||
header.attach (header_info, 0, 0, 1, 1);
|
|
||||||
|
|
||||||
counters = new Grid ();
|
actions = builder.get_object ("actions") as Box;
|
||||||
counters.column_homogeneous = true;
|
follow_button = builder.get_object ("follow_button") as Button;
|
||||||
counters.get_style_context ().add_class ("header-counters");
|
follow_button.clicked.connect (on_follow_button_clicked);
|
||||||
header.attach (counters, 0, 1, 1, 1);
|
options_button = builder.get_object ("options_button") as MenuButton;
|
||||||
|
relationship = builder.get_object ("relationship") as Label;
|
||||||
|
|
||||||
header_image = new Grid ();
|
posts_label = builder.get_object ("posts_label") as Label;
|
||||||
header_image.get_style_context ().add_class ("header");
|
profile.bind_property ("posts_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
header.attach (header_image, 0, 0, 2, 2);
|
var val = (int64) src;
|
||||||
|
target.set_string (_("%s Posts").printf (@"<b>$val</b>"));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
following_label = builder.get_object ("following_label") as Label;
|
||||||
|
profile.bind_property ("following_count", following_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
|
var val = (int64) src;
|
||||||
|
target.set_string (_("%s Follows").printf (@"<b>$val</b>"));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
followers_label = builder.get_object ("followers_label") as Label;
|
||||||
|
profile.bind_property ("followers_count", followers_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
|
||||||
|
var val = (int64) src;
|
||||||
|
target.set_string (_("%s Followers").printf (@"<b>$val</b>"));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
menu = new Gtk.Menu ();
|
filter_all = builder.get_object ("filter_all") as RadioButton;
|
||||||
menu_edit = new Gtk.MenuItem.with_label (_("Edit Profile"));
|
filter_all.toggled.connect (on_refresh);
|
||||||
menu_mention = new Gtk.MenuItem.with_label (_("Mention"));
|
filter_replies = builder.get_object ("filter_replies") as RadioButton;
|
||||||
menu_report = new Gtk.MenuItem.with_label (_("Report"));
|
filter_replies.toggled.connect (on_refresh);
|
||||||
menu_mute = new Gtk.MenuItem.with_label (_("Mute"));
|
filter_media = builder.get_object ("filter_media") as RadioButton;
|
||||||
menu_block = new Gtk.MenuItem.with_label (_("Block"));
|
filter_media.toggled.connect (on_refresh);
|
||||||
menu.add (menu_mention);
|
|
||||||
//menu.add (new Gtk.SeparatorMenuItem ());
|
|
||||||
menu.add (menu_mute);
|
|
||||||
menu.add (menu_block);
|
|
||||||
//menu.add (menu_report); //TODO: Report users
|
|
||||||
//menu.add (menu_edit); //TODO: Edit profile
|
|
||||||
menu.show_all ();
|
|
||||||
|
|
||||||
button_follow = add_counter ("contact-new-symbolic");
|
posts_tab = builder.get_object ("posts_tab") as RadioButton;
|
||||||
button_menu = new MenuButton ();
|
posts_tab.toggled.connect (() => {
|
||||||
button_menu.image = new Image.from_icon_name ("view-more-symbolic", IconSize.LARGE_TOOLBAR);
|
if (posts_tab.active) on_refresh ();
|
||||||
button_menu.tooltip_text = _("More Actions");
|
});
|
||||||
button_menu.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
following_tab = builder.get_object ("following_tab") as RadioButton;
|
||||||
(button_menu as Widget).set_focus_on_click (false);
|
following_tab.toggled.connect (() => {
|
||||||
button_menu.can_default = false;
|
if (following_tab.active) on_refresh ();
|
||||||
button_menu.can_focus = false;
|
});
|
||||||
button_menu.popup = menu;
|
followers_tab = builder.get_object ("followers_tab") as RadioButton;
|
||||||
actions.pack_end(button_menu, false, false, 0);
|
followers_tab.toggled.connect (() => {
|
||||||
actions.pack_end(button_follow, false, false, 0);
|
if (followers_tab.active) on_refresh ();
|
||||||
button_menu.hide ();
|
});
|
||||||
button_follow.hide ();
|
|
||||||
header.attach (actions, 0, 0, 2, 2);
|
|
||||||
|
|
||||||
view.pack_start (header, false, false, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile (API.Account acc) {
|
public Profile (API.Account acc) {
|
||||||
base ("");
|
Object (profile: acc);
|
||||||
account = acc;
|
profile.get_relationship ();
|
||||||
account.updated.connect (rebind);
|
|
||||||
|
|
||||||
add_counter (_("Toots"), 1, account.statuses_count);
|
|
||||||
add_counter (_("Follows"), 2, account.following_count).clicked.connect (() => {
|
|
||||||
var view = new Views.Following (account);
|
|
||||||
window.open_view (view);
|
|
||||||
});
|
|
||||||
add_counter (_("Followers"), 3, account.followers_count).clicked.connect (() => {
|
|
||||||
var view = new Views.Followers (account);
|
|
||||||
window.open_view (view);
|
|
||||||
});
|
|
||||||
|
|
||||||
show_all ();
|
|
||||||
|
|
||||||
//TODO: Has this thing always been synchronous???
|
|
||||||
//var stylesheet = ".header{background-image: url(\"%s\")}".printf (account.header);
|
|
||||||
//var css_provider = Granite.Widgets.Utils.get_css_provider (stylesheet);
|
|
||||||
//header_image.get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
||||||
|
|
||||||
menu_mention.activate.connect (() => Dialogs.Compose.open ("@%s ".printf (account.acct)));
|
|
||||||
menu_mute.activate.connect (() => account.set_muted (!account.rs.muting));
|
|
||||||
menu_block.activate.connect (() => account.set_blocked (!account.rs.blocking));
|
|
||||||
button_follow.clicked.connect (() => account.set_following (!account.rs.following));
|
|
||||||
|
|
||||||
rebind ();
|
|
||||||
account.get_relationship ();
|
|
||||||
request ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void on_follow_button_clicked () {
|
||||||
|
actions.sensitive = false;
|
||||||
|
profile.set_following (!profile.rs.following);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void on_rs_updated () {
|
||||||
|
var rs = profile.rs;
|
||||||
|
var label = "";
|
||||||
|
if (actions.sensitive = rs != null) {
|
||||||
|
if (rs.requested)
|
||||||
|
label = _("Sent follow request");
|
||||||
|
else if (rs.followed_by && rs.following)
|
||||||
|
label = _("Mutually follows you");
|
||||||
|
else if (rs.followed_by)
|
||||||
|
label = _("Follows you");
|
||||||
|
|
||||||
public void rebind (){
|
foreach (Widget w in new Widget[] { follow_button, options_button }) {
|
||||||
display_name.set_label ("<b>%s</b>".printf (account.display_name));
|
var ctx = w.get_style_context ();
|
||||||
username.label = "@" + account.acct;
|
ctx.remove_class (STYLE_CLASS_SUGGESTED_ACTION);
|
||||||
note.set_label (account.note);
|
ctx.remove_class (STYLE_CLASS_DESTRUCTIVE_ACTION);
|
||||||
button_follow.visible = !account.is_self ();
|
ctx.add_class (rs.following ? STYLE_CLASS_DESTRUCTIVE_ACTION : STYLE_CLASS_SUGGESTED_ACTION);
|
||||||
network.load_avatar (account.avatar, avatar, 128);
|
}
|
||||||
|
|
||||||
menu_edit.visible = account.is_self ();
|
var label2 = "";
|
||||||
|
if (rs.followed_by && !rs.following)
|
||||||
|
label2 = _("Follow back");
|
||||||
|
else if (rs.following)
|
||||||
|
label2 = _("Unfollow");
|
||||||
|
else
|
||||||
|
label2 = _("Follow");
|
||||||
|
|
||||||
if (account.rs != null && !account.is_self ()) {
|
follow_button.label = label2;
|
||||||
button_follow.show ();
|
}
|
||||||
if (account.rs.following) {
|
|
||||||
button_follow.tooltip_text = _("Unfollow");
|
|
||||||
(button_follow.get_image () as Image).icon_name = "close-symbolic";
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
button_follow.tooltip_text = _("Follow");
|
|
||||||
(button_follow.get_image () as Image).icon_name = "contact-new-symbolic";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.rs != null){
|
relationship.label = label;
|
||||||
button_menu.show ();
|
}
|
||||||
menu_block.label = account.rs.blocking ? _("Unblock") : _("Block");
|
|
||||||
menu_mute.label = account.rs.muting ? _("Unmute") : _("Mute");
|
|
||||||
menu_report.visible = menu_mute.visible = menu_block.visible = !account.is_self ();
|
|
||||||
|
|
||||||
var rs_label = get_relationship_label ();
|
|
||||||
if (rs_label != null) {
|
|
||||||
relationship.label = rs_label;
|
|
||||||
relationship.show ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
relationship.hide ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
relationship.hide ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool is_status_owned (API.Status status) {
|
|
||||||
return status.is_owned ();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Button add_counter (string name, int? i = null, int64? val = null) {
|
|
||||||
Button btn;
|
|
||||||
if (val != null){
|
|
||||||
btn = new Button ();
|
|
||||||
var label = new Label ("<b>%s</b>\n%s".printf (name.up (), val.to_string ()));
|
|
||||||
label.justify = Justification.CENTER;
|
|
||||||
label.use_markup = true;
|
|
||||||
label.margin = 8;
|
|
||||||
btn.add (label);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
btn = new Button.from_icon_name (name, IconSize.LARGE_TOOLBAR);
|
|
||||||
|
|
||||||
btn.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
|
||||||
(btn as Widget).set_focus_on_click (false);
|
|
||||||
btn.can_default = false;
|
|
||||||
btn.can_focus = false;
|
|
||||||
|
|
||||||
if (i != null)
|
|
||||||
counters.attach (btn, i, 1, 1, 1);
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool is_empty () {
|
|
||||||
return view.get_children ().length () <= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string get_url () {
|
public override string get_url () {
|
||||||
if (page_next != null)
|
if (page_next != null)
|
||||||
return page_next;
|
return page_next;
|
||||||
|
|
||||||
var url = "%s/api/v1/accounts/%lld/statuses?limit=%i".printf (accounts.formal.instance, account.id, this.limit);
|
if (following_tab.active)
|
||||||
return url;
|
return @"/api/v1/accounts/$(profile.id)/following";
|
||||||
|
else if (followers_tab.active)
|
||||||
|
return @"/api/v1/accounts/$(profile.id)/followers";
|
||||||
|
else
|
||||||
|
return @"/api/v1/accounts/$(profile.id)/statuses";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void request () {
|
public override Request append_params (Request req) {
|
||||||
if (account != null)
|
req.with_param ("exclude_replies", (!filter_replies.active).to_string ());
|
||||||
base.request ();
|
req.with_param ("only_media", filter_media.active.to_string ());
|
||||||
}
|
return base.append_params (req);
|
||||||
|
}
|
||||||
|
|
||||||
private string? get_relationship_label () {
|
public override bool request () {
|
||||||
if (account.rs.requested)
|
append_params (new Request.GET (get_url ()))
|
||||||
return _("Sent follow request");
|
.with_account (account)
|
||||||
else if (account.rs.blocking)
|
.then_parse_array ((node, msg) => {
|
||||||
return _("Blocked");
|
var obj = node.get_object ();
|
||||||
else if (account.rs.followed_by)
|
if (obj != null) {
|
||||||
return _("Follows you");
|
API.Status status;
|
||||||
else if (account.rs.domain_blocking)
|
if (posts_tab.active)
|
||||||
return _("Blocking this instance");
|
status = new API.Status (obj);
|
||||||
else
|
else {
|
||||||
return null;
|
var account = new API.Account (obj);
|
||||||
|
status = new API.Status.from_account (account);
|
||||||
|
}
|
||||||
|
|
||||||
|
append (status);
|
||||||
|
}
|
||||||
|
get_pages (msg.response_headers.get_one ("Link"));
|
||||||
|
})
|
||||||
|
.on_error (on_error)
|
||||||
|
.exec ();
|
||||||
|
|
||||||
|
return GLib.Source.REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open_from_id (int64 id){
|
public static void open_from_id (int64 id){
|
||||||
var url = "%s/api/v1/accounts/%lld".printf (accounts.formal.instance, id);
|
var url = "%s/api/v1/accounts/%lld".printf (accounts.active.instance, id);
|
||||||
var msg = new Soup.Message ("GET", url);
|
var msg = new Soup.Message ("GET", url);
|
||||||
msg.priority = Soup.MessagePriority.HIGH;
|
msg.priority = Soup.MessagePriority.HIGH;
|
||||||
network.queue (msg, (sess, mess) => {
|
network.queue (msg, (sess, mess) => {
|
||||||
var root = network.parse (mess);
|
var root = network.parse (mess);
|
||||||
var acc = API.Account.parse (root);
|
var acc = new API.Account (root);
|
||||||
window.open_view (new Views.Profile (acc));
|
window.open_view (new Views.Profile (acc));
|
||||||
}, (status, reason) => {
|
}, (status, reason) => {
|
||||||
network.on_error (status, reason);
|
network.on_error (status, reason);
|
||||||
|
|
|
@ -1,61 +1,64 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
public class Tootle.Views.Search : Views.Abstract {
|
public class Tootle.Views.Search : Views.Base {
|
||||||
|
|
||||||
private string query = "";
|
private string query = "";
|
||||||
private Entry entry;
|
private SearchBar bar;
|
||||||
|
private SearchEntry entry;
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
view.margin_bottom = 6;
|
bar = new SearchBar ();
|
||||||
|
bar.search_mode_enabled = true;
|
||||||
|
bar.show ();
|
||||||
|
pack_start (bar, false, false, 0);
|
||||||
|
|
||||||
entry = new Entry ();
|
entry = new SearchEntry ();
|
||||||
entry.placeholder_text = _("Search");
|
|
||||||
entry.secondary_icon_name = "system-search-symbolic";
|
|
||||||
entry.width_chars = 25;
|
entry.width_chars = 25;
|
||||||
entry.text = query;
|
entry.text = query;
|
||||||
entry.valign = Align.CENTER;
|
|
||||||
entry.show ();
|
entry.show ();
|
||||||
window.header.pack_start (entry);
|
bar.add (entry);
|
||||||
|
bar.connect_entry (entry);
|
||||||
|
|
||||||
destroy.connect (() => entry.destroy ());
|
|
||||||
entry.activate.connect (() => request ());
|
entry.activate.connect (() => request ());
|
||||||
entry.icon_press.connect (() => request ());
|
entry.icon_press.connect (() => request ());
|
||||||
}
|
|
||||||
|
|
||||||
public Search () {
|
|
||||||
entry.grab_focus_without_selecting ();
|
entry.grab_focus_without_selecting ();
|
||||||
|
status_button.clicked.connect (request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void append_account (API.Account acc) {
|
private void append_account (API.Account acc) {
|
||||||
var widget = new Widgets.Account (acc);
|
var status = new API.Status.from_account (acc);
|
||||||
view.pack_start (widget, false, false, 0);
|
var widget = new Widgets.Status (status);
|
||||||
|
widget.button_press_event.connect (widget.on_avatar_clicked);
|
||||||
|
content.pack_start (widget, false, false, 0);
|
||||||
|
on_content_changed ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void append_status (API.Status status) {
|
private void append_status (API.Status status) {
|
||||||
var widget = new Widgets.Status (status);
|
var widget = new Widgets.Status (status);
|
||||||
widget.button_press_event.connect (widget.on_avatar_clicked);
|
widget.button_press_event.connect (widget.on_avatar_clicked);
|
||||||
view.pack_start (widget, false, false, 0);
|
content.pack_start (widget, false, false, 0);
|
||||||
|
on_content_changed ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void append_header (string name) {
|
private void append_header (string name) {
|
||||||
var widget = new Label (name);
|
var widget = new Label (@"<span weight='bold' size='medium'>$name</span>");
|
||||||
widget.get_style_context ().add_class ("h4");
|
|
||||||
widget.halign = Align.START;
|
widget.halign = Align.START;
|
||||||
widget.margin = 6;
|
widget.margin = 8;
|
||||||
widget.margin_bottom = 0;
|
widget.use_markup = true;
|
||||||
widget.show ();
|
widget.show ();
|
||||||
view.pack_start (widget, false, false, 0);
|
content.pack_start (widget, false, false, 0);
|
||||||
|
on_content_changed ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void append_hashtag (string name) {
|
private void append_hashtag (string name) {
|
||||||
var text = "<a href=\"%s/tags/%s\">#%s</a>".printf (accounts.formal.instance, Soup.URI.encode (name, null), name);
|
var encoded = Soup.URI.encode (name, null);
|
||||||
var widget = new Widgets.RichLabel (text);
|
var widget = new Widgets.RichLabel (@"<a href=\"$(accounts.active.instance)/tags/$encoded\">#$name</a>");
|
||||||
widget.use_markup = true;
|
widget.use_markup = true;
|
||||||
widget.halign = Align.START;
|
widget.halign = Align.START;
|
||||||
widget.margin = 6;
|
widget.margin = 6;
|
||||||
widget.margin_bottom = 0;
|
widget.margin_bottom = 0;
|
||||||
widget.show ();
|
widget.show ();
|
||||||
view.pack_start (widget, false, false, 0);
|
content.pack_start (widget, false, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void request () {
|
private void request () {
|
||||||
|
@ -64,25 +67,31 @@ public class Tootle.Views.Search : Views.Abstract {
|
||||||
clear ();
|
clear ();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.reopen_view (this.stack_pos);
|
|
||||||
|
|
||||||
var query_encoded = Soup.URI.encode (query, null);
|
new Request.GET ("/api/v2/search")
|
||||||
var url = "%s/api/v1/search?q=%s&resolve=true".printf (accounts.formal.instance, query_encoded);
|
.with_account (accounts.active)
|
||||||
var msg = new Soup.Message("GET", url);
|
.with_param ("resolve", "true")
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
.with_param ("q", Soup.URI.encode (query, null))
|
||||||
network.queue (msg, (sess, mess) => {
|
.then ((sess, msg) => {
|
||||||
var root = network.parse (mess);
|
var root = network.parse (msg);
|
||||||
var accounts = root.get_array_member ("accounts");
|
var accounts = root.get_array_member ("accounts");
|
||||||
var statuses = root.get_array_member ("statuses");
|
var statuses = root.get_array_member ("statuses");
|
||||||
var hashtags = root.get_array_member ("hashtags");
|
var hashtags = root.get_array_member ("hashtags");
|
||||||
|
|
||||||
clear ();
|
clear ();
|
||||||
|
|
||||||
|
if (hashtags.get_length () > 0) {
|
||||||
|
append_header (_("Hashtags"));
|
||||||
|
hashtags.foreach_element ((array, i, node) => {
|
||||||
|
append_hashtag (node.get_object ().get_string_member ("name"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (accounts.get_length () > 0) {
|
if (accounts.get_length () > 0) {
|
||||||
append_header (_("Accounts"));
|
append_header (_("Accounts"));
|
||||||
accounts.foreach_element ((array, i, node) => {
|
accounts.foreach_element ((array, i, node) => {
|
||||||
var obj = node.get_object ();
|
var obj = node.get_object ();
|
||||||
var acc = API.Account.parse (obj);
|
var acc = new API.Account (obj);
|
||||||
append_account (acc);
|
append_account (acc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -91,20 +100,13 @@ public class Tootle.Views.Search : Views.Abstract {
|
||||||
append_header (_("Statuses"));
|
append_header (_("Statuses"));
|
||||||
statuses.foreach_element ((array, i, node) => {
|
statuses.foreach_element ((array, i, node) => {
|
||||||
var obj = node.get_object ();
|
var obj = node.get_object ();
|
||||||
var status = API.Status.parse (obj);
|
var status = new API.Status (obj);
|
||||||
append_status (status);
|
append_status (status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
})
|
||||||
if (hashtags.get_length () > 0) {
|
.on_error (on_error)
|
||||||
append_header (_("Hashtags"));
|
.exec ();
|
||||||
hashtags.foreach_element ((array, i, node) => {
|
|
||||||
append_hashtag (node.get_string ());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
empty_state ();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,25 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Gdk;
|
using Gdk;
|
||||||
|
|
||||||
public class Tootle.Views.Timeline : Views.Abstract {
|
public class Tootle.Views.Timeline : Views.Base, IAccountListener, IStreamListener {
|
||||||
|
|
||||||
protected string timeline;
|
public string timeline { get; construct set; }
|
||||||
protected string pars;
|
public bool is_public { get; construct set; default = false; }
|
||||||
|
|
||||||
|
protected InstanceAccount? account = null;
|
||||||
protected int limit = 25;
|
protected int limit = 25;
|
||||||
protected bool is_last_page = false;
|
protected bool is_last_page = false;
|
||||||
protected string? page_next;
|
protected string? page_next;
|
||||||
protected string? page_prev;
|
protected string? page_prev;
|
||||||
|
protected string? stream;
|
||||||
|
|
||||||
protected Notificator? notificator;
|
construct {
|
||||||
|
|
||||||
public Timeline (string timeline, string pars = "") {
|
|
||||||
base ();
|
|
||||||
this.timeline = timeline;
|
|
||||||
this.pars = pars;
|
|
||||||
|
|
||||||
accounts.switched.connect (on_account_changed);
|
|
||||||
app.refresh.connect (on_refresh);
|
app.refresh.connect (on_refresh);
|
||||||
destroy.connect (() => {
|
status_button.clicked.connect (on_refresh);
|
||||||
if (notificator != null)
|
connect_account ();
|
||||||
notificator.close ();
|
}
|
||||||
});
|
~Timeline () {
|
||||||
|
streams.unsubscribe (stream, this);
|
||||||
setup_notificator ();
|
|
||||||
request ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string get_icon () {
|
public override string get_icon () {
|
||||||
|
@ -36,38 +30,32 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
||||||
return _("Home");
|
return _("Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_status_added (API.Status status) {
|
public override void on_status_added (API.Status status) {
|
||||||
prepend (status);
|
prepend (status);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool is_status_owned (API.Status status) {
|
public virtual bool is_status_owned (API.Status status) {
|
||||||
return false;
|
return status.is_owned ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepend (API.Status status) {
|
public void prepend (API.Status status) {
|
||||||
append (status, true);
|
append (status, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void append (API.Status status, bool first = false){
|
public void append (API.Status status, bool first = false) {
|
||||||
if (empty != null)
|
GLib.Idle.add (() => {
|
||||||
empty.destroy ();
|
var w = new Widgets.Status (status);
|
||||||
|
w.button_press_event.connect (w.open);
|
||||||
|
if (!is_status_owned (status))
|
||||||
|
w.avatar.button_press_event.connect (w.on_avatar_clicked);
|
||||||
|
|
||||||
var separator = new Separator (Orientation.HORIZONTAL);
|
content.pack_start (w, false, false, 0);
|
||||||
separator.show ();
|
if (first || status.pinned)
|
||||||
|
content.reorder_child (w, 0);
|
||||||
|
|
||||||
var widget = new Widgets.Status (status);
|
on_content_changed ();
|
||||||
widget.separator = separator;
|
return GLib.Source.REMOVE;
|
||||||
widget.button_press_event.connect (widget.open);
|
});
|
||||||
if (!is_status_owned (status))
|
|
||||||
widget.avatar.button_press_event.connect (widget.on_avatar_clicked);
|
|
||||||
view.pack_start (separator, false, false, 0);
|
|
||||||
view.pack_start (widget, false, false, 0);
|
|
||||||
|
|
||||||
if (first || status.pinned) {
|
|
||||||
var new_index = header == null ? 1 : 0;
|
|
||||||
view.reorder_child (separator, new_index);
|
|
||||||
view.reorder_child (widget, new_index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void clear () {
|
public override void clear () {
|
||||||
|
@ -102,82 +90,51 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
||||||
if (page_next != null)
|
if (page_next != null)
|
||||||
return page_next;
|
return page_next;
|
||||||
|
|
||||||
var url = "%s/api/v1/timelines/%s?limit=%i".printf (accounts.formal.instance, this.timeline, this.limit);
|
return @"/api/v1/timelines/$timeline";
|
||||||
url += this.pars;
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void request (){
|
public virtual Request append_params (Request req) {
|
||||||
if (accounts.current == null) {
|
return req.with_param ("limit", limit.to_string ());
|
||||||
empty_state ();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg = new Soup.Message ("GET", get_url ());
|
|
||||||
network.inject (msg, Network.INJECT_TOKEN);
|
|
||||||
network.queue (msg, (sess, mess) => {
|
|
||||||
network.parse_array (mess).foreach_element ((array, i, node) => {
|
|
||||||
var object = node.get_object ();
|
|
||||||
if (object != null) {
|
|
||||||
var status = API.Status.parse (object);
|
|
||||||
append (status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
get_pages (mess.response_headers.get_one ("Link"));
|
|
||||||
empty_state ();
|
|
||||||
},
|
|
||||||
network.on_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_refresh (){
|
public virtual bool request () {
|
||||||
|
append_params (new Request.GET (get_url ()))
|
||||||
|
.with_account (account)
|
||||||
|
.then_parse_array ((node, msg) => {
|
||||||
|
var obj = node.get_object ();
|
||||||
|
if (obj != null) {
|
||||||
|
var status = new API.Status (obj);
|
||||||
|
append (status);
|
||||||
|
}
|
||||||
|
get_pages (msg.response_headers.get_one ("Link"));
|
||||||
|
})
|
||||||
|
.on_error (on_error)
|
||||||
|
.exec ();
|
||||||
|
|
||||||
|
return GLib.Source.REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void on_refresh () {
|
||||||
|
status_button.sensitive = false;
|
||||||
clear ();
|
clear ();
|
||||||
request ();
|
status_message = STATUS_LOADING;
|
||||||
|
GLib.Idle.add (request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Soup.Message? get_stream (){
|
public virtual string? get_stream_url () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void on_account_changed (API.Account? account){
|
public override void on_account_changed (InstanceAccount? acc) {
|
||||||
if(account == null)
|
account = acc;
|
||||||
return;
|
streams.unsubscribe (stream, this);
|
||||||
|
streams.subscribe (get_stream_url (), this, out stream);
|
||||||
var stream = get_stream ();
|
|
||||||
if (notificator != null && stream != null) {
|
|
||||||
var old_url = notificator.get_url ();
|
|
||||||
var new_url = stream.get_uri ().to_string (false);
|
|
||||||
if (old_url != new_url) {
|
|
||||||
info ("Updating notificator %s", notificator.get_name ());
|
|
||||||
setup_notificator ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
on_refresh ();
|
on_refresh ();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setup_notificator () {
|
protected override bool accepts (ref string event) {
|
||||||
if (notificator != null)
|
|
||||||
notificator.close ();
|
|
||||||
|
|
||||||
var stream = get_stream ();
|
|
||||||
if (stream == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
notificator = new Notificator (stream);
|
|
||||||
notificator.status_added.connect ((status) => {
|
|
||||||
if (can_stream ())
|
|
||||||
on_status_added (status);
|
|
||||||
});
|
|
||||||
notificator.start ();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual bool is_public () {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual bool can_stream () {
|
|
||||||
var allowed_public = true;
|
var allowed_public = true;
|
||||||
if (is_public ())
|
if (is_public)
|
||||||
allowed_public = settings.live_updates_public;
|
allowed_public = settings.live_updates_public;
|
||||||
|
|
||||||
return settings.live_updates && allowed_public;
|
return settings.live_updates && allowed_public;
|
||||||
|
@ -185,7 +142,7 @@ public class Tootle.Views.Timeline : Views.Abstract {
|
||||||
|
|
||||||
protected override void on_bottom_reached () {
|
protected override void on_bottom_reached () {
|
||||||
if (is_last_page) {
|
if (is_last_page) {
|
||||||
debug ("Last page reached");
|
info ("Last page reached");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
request ();
|
request ();
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
using GLib;
|
|
||||||
using Gee;
|
|
||||||
|
|
||||||
public class Tootle.Watchlist : Object {
|
|
||||||
|
|
||||||
public ArrayList<string> users = new ArrayList<string> ();
|
|
||||||
public ArrayList<string> hashtags = new ArrayList<string> ();
|
|
||||||
public ArrayList<Notificator> notificators = new ArrayList<Notificator> ();
|
|
||||||
|
|
||||||
construct {
|
|
||||||
accounts.switched.connect (on_account_changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Watchlist () {}
|
|
||||||
|
|
||||||
public virtual void on_account_changed (API.Account? account){
|
|
||||||
if (account != null)
|
|
||||||
reload ();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reload () {
|
|
||||||
info ("Reloading");
|
|
||||||
|
|
||||||
notificators.@foreach (notificator => {
|
|
||||||
notificator.close ();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
notificators.clear ();
|
|
||||||
users.clear ();
|
|
||||||
hashtags.clear ();
|
|
||||||
|
|
||||||
load ();
|
|
||||||
info ("Watching for %i users and %i hashtags", users.size, hashtags.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load () {
|
|
||||||
var users_array = settings.watched_users.split (",");
|
|
||||||
foreach (string item in users_array)
|
|
||||||
add (item, false);
|
|
||||||
|
|
||||||
var hashtags_array = settings.watched_hashtags.split (",");
|
|
||||||
foreach (string item in hashtags_array)
|
|
||||||
add (item, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save () {
|
|
||||||
var serialized_users = "";
|
|
||||||
users.@foreach (item => {
|
|
||||||
serialized_users += item + ",";
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
serialized_users = remove_last_delimiter (serialized_users);
|
|
||||||
settings.watched_users = serialized_users;
|
|
||||||
|
|
||||||
var serialized_hashtags = "";
|
|
||||||
hashtags.@foreach (item => {
|
|
||||||
serialized_hashtags += item + ",";
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
serialized_hashtags = remove_last_delimiter (serialized_hashtags);
|
|
||||||
settings.watched_hashtags = serialized_hashtags;
|
|
||||||
|
|
||||||
info ("Saved");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string remove_last_delimiter (string str) {
|
|
||||||
var i = str.last_index_of (",");
|
|
||||||
if (i > -1)
|
|
||||||
return str.substring (0, i);
|
|
||||||
else
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Notificator get_notificator (string hashtag) {
|
|
||||||
var url = "%s/api/v1/streaming/?stream=hashtag&tag=%s&access_token=%s".printf (accounts.formal.instance, hashtag, accounts.formal.token);
|
|
||||||
var msg = new Soup.Message ("GET", url);
|
|
||||||
var notificator = new Notificator (msg);
|
|
||||||
notificator.status_added.connect (on_status_added);
|
|
||||||
return notificator;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_status_added (API.Status status) {
|
|
||||||
var obj = new API.Notification (-1);
|
|
||||||
obj.type = API.NotificationType.WATCHLIST;
|
|
||||||
obj.account = status.account;
|
|
||||||
obj.status = status;
|
|
||||||
accounts.formal.notification (obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add (string entity, bool is_hashtag) {
|
|
||||||
if (entity == "")
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (is_hashtag) {
|
|
||||||
hashtags.add (entity);
|
|
||||||
var notificator = get_notificator (entity);
|
|
||||||
notificator.start ();
|
|
||||||
notificators.add (notificator);
|
|
||||||
info ("Added #%s", entity);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
users.add (entity);
|
|
||||||
info ("Added @%s", entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove (string entity, bool is_hashtag) {
|
|
||||||
if (entity == "")
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (is_hashtag) {
|
|
||||||
var i = hashtags.index_of (entity);
|
|
||||||
var notificator = notificators.@get(i);
|
|
||||||
notificator.close ();
|
|
||||||
notificators.remove_at (i);
|
|
||||||
hashtags.remove (entity);
|
|
||||||
info ("Removed #%s", entity);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
users.remove (entity);
|
|
||||||
info ("Removed @%s", entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,15 +5,15 @@ public class Tootle.Widgets.Account : Widgets.Status {
|
||||||
public Account (API.Account account) {
|
public Account (API.Account account) {
|
||||||
var status = new API.Status (-1);
|
var status = new API.Status (-1);
|
||||||
status.account = account;
|
status.account = account;
|
||||||
status.url = account.url;
|
//status.url = account.url;
|
||||||
status.content = "<a href=\"%s\">@%s</a>".printf (account.url, account.acct);
|
//status.content = "<a href=\"%s\">@%s</a>".printf (account.url, account.acct);
|
||||||
status.created_at = account.created_at;
|
//status.created_at = account.created_at;
|
||||||
|
|
||||||
base (status);
|
base (status);
|
||||||
|
|
||||||
counters.visible = false;
|
//counters.visible = false;
|
||||||
title_acct.visible = false;
|
//title_acct.visible = false;
|
||||||
content_label.margin_bottom = 12;
|
//content_label.margin_bottom = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool on_clicked (EventButton ev) {
|
protected override bool on_clicked (EventButton ev) {
|
||||||
|
|
|
@ -1,168 +1,149 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
public class Tootle.Widgets.AccountsButton : MenuButton {
|
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/accounts_button.ui")]
|
||||||
|
public class Tootle.Widgets.AccountsButton : Gtk.MenuButton, IAccountListener {
|
||||||
|
|
||||||
const int AVATAR_SIZE = 24;
|
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/accounts_button_item.ui")]
|
||||||
Granite.Widgets.Avatar avatar;
|
private class Item : Grid {
|
||||||
Grid grid;
|
[GtkChild]
|
||||||
Popover menu;
|
private Widgets.Avatar avatar;
|
||||||
ListBox list;
|
[GtkChild]
|
||||||
ModelButton item_settings;
|
private Label name;
|
||||||
ModelButton item_refresh;
|
[GtkChild]
|
||||||
ModelButton item_search;
|
private Label handle;
|
||||||
ModelButton item_favs;
|
[GtkChild]
|
||||||
ModelButton item_direct;
|
private Button profile;
|
||||||
ModelButton item_watchlist;
|
[GtkChild]
|
||||||
|
private Button remove;
|
||||||
|
|
||||||
private class AccountItemView : ListBoxRow {
|
public Item (InstanceAccount acc, AccountsButton _self) {
|
||||||
|
avatar.url = acc.avatar;
|
||||||
|
name.label = acc.display_name;
|
||||||
|
handle.label = acc.handle;
|
||||||
|
|
||||||
private Grid grid;
|
profile.clicked.connect (() => {
|
||||||
public Label display_name;
|
Views.Profile.open_from_id (acc.id);
|
||||||
public Label instance;
|
_self.active = false;
|
||||||
public Button button;
|
});
|
||||||
public int id = -1;
|
|
||||||
|
|
||||||
construct {
|
remove.clicked.connect (() => {
|
||||||
can_default = false;
|
_self.active = false;
|
||||||
|
accounts.remove (acc);
|
||||||
grid = new Grid ();
|
});
|
||||||
grid.margin = 6;
|
|
||||||
grid.margin_start = 14;
|
|
||||||
|
|
||||||
display_name = new Label ("");
|
|
||||||
display_name.hexpand = true;
|
|
||||||
display_name.halign = Align.START;
|
|
||||||
display_name.use_markup = true;
|
|
||||||
instance = new Label ("");
|
|
||||||
instance.halign = Align.START;
|
|
||||||
button = new Button.from_icon_name ("window-close-symbolic", IconSize.SMALL_TOOLBAR);
|
|
||||||
button.receives_default = false;
|
|
||||||
button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
|
||||||
|
|
||||||
grid.attach (display_name, 1, 0, 1, 1);
|
|
||||||
grid.attach (instance, 1, 1, 1, 1);
|
|
||||||
grid.attach (button, 2, 0, 2, 2);
|
|
||||||
add (grid);
|
|
||||||
show_all ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountItemView (){
|
public Item.add_new () {
|
||||||
button.clicked.connect (() => accounts.remove (id));
|
name.label = _("New Account");
|
||||||
|
handle.label = _("Click to add");
|
||||||
|
profile.destroy ();
|
||||||
|
remove.destroy ();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
construct{
|
private bool invalidated = true;
|
||||||
avatar = new Granite.Widgets.Avatar.with_default_icon (AVATAR_SIZE);
|
|
||||||
list = new ListBox ();
|
|
||||||
|
|
||||||
var item_separator = new Separator (Orientation.HORIZONTAL);
|
[GtkChild]
|
||||||
item_separator.hexpand = true;
|
private Widgets.Avatar avatar;
|
||||||
|
[GtkChild]
|
||||||
|
private Spinner spinner;
|
||||||
|
|
||||||
|
[GtkChild]
|
||||||
|
private ListBox account_list;
|
||||||
|
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_accounts;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_prefs;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_refresh;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_search;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_favs;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_direct;
|
||||||
|
[GtkChild]
|
||||||
|
private ModelButton item_watchlist;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
connect_account ();
|
||||||
|
|
||||||
item_refresh = new ModelButton ();
|
|
||||||
item_refresh.text = _("Refresh");
|
|
||||||
item_refresh.clicked.connect (() => app.refresh ());
|
item_refresh.clicked.connect (() => app.refresh ());
|
||||||
Desktop.set_hotkey_tooltip (item_refresh, null, app.ACCEL_REFRESH);
|
Desktop.set_hotkey_tooltip (item_refresh, null, app.ACCEL_REFRESH);
|
||||||
|
|
||||||
item_favs = new ModelButton ();
|
|
||||||
item_favs.text = _("Favorites");
|
|
||||||
item_favs.clicked.connect (() => window.open_view (new Views.Favorites ()));
|
item_favs.clicked.connect (() => window.open_view (new Views.Favorites ()));
|
||||||
|
|
||||||
item_direct = new ModelButton ();
|
|
||||||
item_direct.text = _("Direct Messages");
|
|
||||||
item_direct.clicked.connect (() => window.open_view (new Views.Direct ()));
|
item_direct.clicked.connect (() => window.open_view (new Views.Direct ()));
|
||||||
|
|
||||||
item_search = new ModelButton ();
|
|
||||||
item_search.text = _("Search");
|
|
||||||
item_search.clicked.connect (() => window.open_view (new Views.Search ()));
|
item_search.clicked.connect (() => window.open_view (new Views.Search ()));
|
||||||
|
//item_watchlist.clicked.connect (() => Dialogs.WatchlistEditor.open ());
|
||||||
|
item_prefs.clicked.connect (() => Dialogs.Preferences.open ());
|
||||||
|
|
||||||
item_watchlist = new ModelButton ();
|
// network.started.connect (() => spinner.show ());
|
||||||
item_watchlist.text = _("Watchlist");
|
// network.finished.connect (() => spinner.hide ());
|
||||||
item_watchlist.clicked.connect (() => Dialogs.WatchlistEditor.open ());
|
|
||||||
|
|
||||||
item_settings = new ModelButton ();
|
on_account_changed (null);
|
||||||
item_settings.text = _("Settings");
|
|
||||||
item_settings.clicked.connect (() => Dialogs.Preferences.open ());
|
|
||||||
|
|
||||||
grid = new Grid ();
|
notify["active"].connect (() => {
|
||||||
grid.orientation = Orientation.VERTICAL;
|
if (active && invalidated)
|
||||||
grid.width_request = 200;
|
rebuild ();
|
||||||
grid.attach (list, 0, 1, 1, 1);
|
|
||||||
grid.attach (item_separator, 0, 3, 1, 1);
|
|
||||||
grid.attach (item_favs, 0, 4, 1, 1);
|
|
||||||
grid.attach (item_direct, 0, 5, 1, 1);
|
|
||||||
grid.attach (new Separator (Orientation.HORIZONTAL), 0, 6, 1, 1);
|
|
||||||
grid.attach (item_refresh, 0, 7, 1, 1);
|
|
||||||
grid.attach (item_search, 0, 8, 1, 1);
|
|
||||||
grid.attach (item_watchlist, 0, 9, 1, 1);
|
|
||||||
grid.attach (item_settings, 0, 10, 1, 1);
|
|
||||||
grid.show_all ();
|
|
||||||
|
|
||||||
menu = new Popover (null);
|
|
||||||
menu.add (grid);
|
|
||||||
|
|
||||||
get_style_context ().add_class ("button_avatar");
|
|
||||||
popover = menu;
|
|
||||||
add (avatar);
|
|
||||||
show_all ();
|
|
||||||
|
|
||||||
accounts.updated.connect (accounts_updated);
|
|
||||||
accounts.switched.connect (account_switched);
|
|
||||||
list.row_activated.connect (row => {
|
|
||||||
var widget = row as AccountItemView;
|
|
||||||
if (widget.id == -1) {
|
|
||||||
Dialogs.NewAccount.open ();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (widget.id == settings.current_account)
|
|
||||||
Views.Profile.open_from_id (accounts.current.id);
|
|
||||||
else
|
|
||||||
accounts.switch_account (widget.id);
|
|
||||||
|
|
||||||
menu.popdown ();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void accounts_updated (GenericArray<InstanceAccount> accounts) {
|
|
||||||
list.forall (widget => widget.destroy ());
|
|
||||||
int i = -1;
|
|
||||||
accounts.foreach (account => {
|
|
||||||
i++;
|
|
||||||
var widget = new AccountItemView ();
|
|
||||||
widget.id = i;
|
|
||||||
widget.display_name.label = "<b>@"+account.username+"</b>";
|
|
||||||
widget.instance.label = account.get_pretty_instance ();
|
|
||||||
list.add (widget);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var add_account = new AccountItemView ();
|
account_list.row_activated.connect (on_selection_changed) ;
|
||||||
add_account.display_name.label = _("<b>New Account</b>");
|
|
||||||
add_account.instance.label = _("Click to add");
|
|
||||||
add_account.button.hide ();
|
|
||||||
list.add (add_account);
|
|
||||||
update_selection ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void account_switched (API.Account? account) {
|
protected void on_selection_changed (ListBoxRow r) {
|
||||||
if (account == null)
|
var i = r.get_index ();
|
||||||
avatar.show_default (AVATAR_SIZE);
|
if (i >= accounts.saved.size) {
|
||||||
else
|
active = false;
|
||||||
network.load_avatar (account.avatar, avatar, get_avatar_size ());
|
window.open_view (new Views.NewAccount (true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = accounts.saved.@get (i);
|
||||||
|
if (accounts.active == account)
|
||||||
|
return;
|
||||||
|
|
||||||
|
accounts.switch_account (i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_selection () {
|
public virtual void on_accounts_changed (Gee.ArrayList<InstanceAccount> accounts) {
|
||||||
var id = settings.current_account;
|
invalidated = true;
|
||||||
var row = list.get_row_at_index (id);
|
if (active)
|
||||||
if (row != null)
|
rebuild ();
|
||||||
list.select_row (row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int get_avatar_size () {
|
public virtual void on_account_changed (InstanceAccount? account) {
|
||||||
return AVATAR_SIZE * get_style_context ().get_scale ();
|
if (account == null) {
|
||||||
|
avatar.url = null;
|
||||||
|
item_accounts.text = "<b>" + _("No active account") + "</b>";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
avatar.url = account.avatar;
|
||||||
|
item_accounts.text = @"<b>$(account.display_name)</b>\n$(account.handle) ";
|
||||||
|
}
|
||||||
|
item_accounts.use_markup = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountsButton () {
|
private void rebuild () {
|
||||||
account_switched (accounts.current);
|
account_list.@foreach (w => account_list.remove (w));
|
||||||
|
accounts.saved.@foreach (acc => {
|
||||||
|
var item = new Item (acc, this);
|
||||||
|
var row = new ListBoxRow ();
|
||||||
|
row.add (item);
|
||||||
|
row.show ();
|
||||||
|
|
||||||
|
account_list.insert (row, -1);
|
||||||
|
if (accounts.active == acc)
|
||||||
|
row.activate ();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
var new_row = new ListBoxRow ();
|
||||||
|
new_row.add (new Item.add_new ());
|
||||||
|
new_row.selectable = false;
|
||||||
|
new_row.show ();
|
||||||
|
account_list.insert (new_row, -1);
|
||||||
|
|
||||||
|
invalidated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
using Gtk;
|
||||||
|
using GLib;
|
||||||
|
using Gee;
|
||||||
|
|
||||||
|
public class Tootle.Widgets.Attachment.Box : FlowBox {
|
||||||
|
|
||||||
|
public bool editing { get; construct set; }
|
||||||
|
|
||||||
|
construct {
|
||||||
|
hexpand = true;
|
||||||
|
can_focus = false;
|
||||||
|
selection_mode = SelectionMode.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Box (bool editing = false) {
|
||||||
|
Object (editing: editing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void select () {
|
||||||
|
var filter = new Gtk.FileFilter ();
|
||||||
|
filter.add_mime_type ("image/jpeg");
|
||||||
|
filter.add_mime_type ("image/png");
|
||||||
|
filter.add_mime_type ("image/gif");
|
||||||
|
filter.add_mime_type ("video/webm");
|
||||||
|
filter.add_mime_type ("video/mp4");
|
||||||
|
|
||||||
|
var chooser = new Gtk.FileChooserDialog (
|
||||||
|
_("Select media files to add"),
|
||||||
|
null,
|
||||||
|
Gtk.FileChooserAction.OPEN,
|
||||||
|
_("_Cancel"),
|
||||||
|
Gtk.ResponseType.CANCEL,
|
||||||
|
_("_Open"),
|
||||||
|
Gtk.ResponseType.ACCEPT);
|
||||||
|
|
||||||
|
chooser.select_multiple = true;
|
||||||
|
chooser.set_filter (filter);
|
||||||
|
|
||||||
|
if (chooser.run () == ResponseType.ACCEPT) {
|
||||||
|
show ();
|
||||||
|
foreach (unowned string uri in chooser.get_uris ()) {
|
||||||
|
//var widget = new ImageAttachment.upload (uri);
|
||||||
|
//append_widget (widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chooser.close ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool populate (ArrayList<API.Attachment>? list) {
|
||||||
|
if (list == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var max = 6;
|
||||||
|
if (list.size % 2 == 0)
|
||||||
|
max = 2;
|
||||||
|
|
||||||
|
//max_children_per_line = (int)Math.fmin (list.size, 5);
|
||||||
|
max_children_per_line = max;
|
||||||
|
list.@foreach (obj => pack (obj));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool pack (API.Attachment obj) {
|
||||||
|
var w = new Widgets.Attachment.Item (obj);
|
||||||
|
insert (w, -1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|