Compare commits
140 commits
Goober_0_1
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
811b9ab0b3 | ||
|
0173d8c396 | ||
|
fd2e8e52e8 | ||
|
fd3237fff1 | ||
|
587531ab6e | ||
|
6d79b9b600 | ||
|
4f8768966c | ||
|
d464479b4f | ||
|
eb158ed5b3 | ||
|
fc1849e384 | ||
|
ea641c52fe | ||
|
1e0d8a2af0 | ||
|
7177f63b27 | ||
|
3ac8ad780f | ||
|
4261fcda94 | ||
|
724db2aafd | ||
|
9e540f774b | ||
|
da9a3a801a | ||
|
c224b081fa | ||
|
eb8fbd59ca | ||
|
4e8f7a4c69 | ||
|
6647a6ccd0 | ||
|
73664e4f44 | ||
|
38f8849972 | ||
|
bc995eb707 | ||
|
7bb6faf97a | ||
|
b4b3ddeddb | ||
|
724f5dbb29 | ||
|
e862382d38 | ||
|
257603ec95 | ||
|
c41a422968 | ||
|
eaefe3908b | ||
|
d94bec3a4b | ||
|
7f04d55641 | ||
|
d80cdf1690 | ||
|
1da3eb3b98 | ||
|
77db17acd4 | ||
|
198b804f2e | ||
|
abd237fc64 | ||
|
dad8eed809 | ||
|
9a02daa626 | ||
|
7c044cf2b8 | ||
|
a5064ee109 | ||
|
e3d62e27d3 | ||
|
5a0a18ab95 | ||
|
126f00e165 | ||
|
f34216213f | ||
|
347ba44bd1 | ||
|
0565ce9aee | ||
|
93e4c38c71 | ||
|
03a6e19b5d | ||
|
3b13d7e5ba | ||
|
284836e0dd | ||
|
ae18ab36cf | ||
|
6cf54b32ac | ||
|
c25177f758 | ||
|
10bc57a281 | ||
|
1c970a50e3 | ||
|
afa50236e2 | ||
|
892cb0e141 | ||
|
79aee37688 | ||
|
749281d959 | ||
|
43a8e0c43b | ||
|
c889a7271a | ||
|
dbc7b94130 | ||
|
7d07f5c117 | ||
|
f146597825 | ||
|
ec4dbf0265 | ||
|
d5ed4b9668 | ||
|
53ffd17808 | ||
|
cc87515a01 | ||
|
8ba0128196 | ||
|
a65f944ff7 | ||
|
2cfcf649de | ||
|
06079a4944 | ||
|
3ab9d62276 | ||
|
8f3e5fe440 | ||
|
e5cdc80b64 | ||
|
305b570cfa | ||
|
b5c68027c4 | ||
|
1c9dafa4e3 | ||
|
e55fafe04b | ||
|
a4b71c1707 | ||
|
e3b4109d7b | ||
|
f305310bd7 | ||
|
50b58cc16f | ||
|
5026760fa0 | ||
|
fc089fe12b | ||
|
e3938a2efe | ||
|
d0f69b88a9 | ||
|
f02c4d6220 | ||
|
5a016ea197 | ||
|
576f8ed493 | ||
|
399e5e68f8 | ||
|
12e7c24a3d | ||
|
9cfaaae6cb | ||
|
e6eb71003b | ||
|
72c2bcf6c9 | ||
|
2141764402 | ||
|
db9cd43d58 | ||
|
d0269b11a3 | ||
|
154900d497 | ||
|
a5ff687946 | ||
|
83d1ff2555 | ||
|
3f57ebe195 | ||
|
1d927ce58f | ||
|
384ad6ad75 | ||
|
40d9f12f79 | ||
|
b11ea8e215 | ||
|
e9176adb80 | ||
|
73f2a297ab | ||
|
61024e18ca | ||
|
7e58365dac | ||
|
e1a81b4185 | ||
|
471a255b78 | ||
|
dde9644f48 | ||
|
d310aef14d | ||
|
46ac23bb68 | ||
|
4174ec3fc6 | ||
|
62fb31b68e | ||
|
149c100206 | ||
|
680a3a0a2c | ||
|
84f09636c9 | ||
|
ee532730d6 | ||
|
aaaafa18b4 | ||
|
fe0f12f613 | ||
|
a005a2b0e7 | ||
|
057492ecd3 | ||
|
8d8c9ff9bb | ||
|
83cb73cdf4 | ||
|
cd65ed97cd | ||
|
4bbff3cf8a | ||
|
58394d83ab | ||
|
e7e2723e73 | ||
|
357776e0f3 | ||
|
2612c0853f | ||
|
6bed892b28 | ||
|
7c897c68f9 | ||
|
b48021f762 | ||
|
db22a41a51 |
6
.gitignore
vendored
|
@ -13,6 +13,7 @@ log.txt
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
.sourcemaps/
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
.tmp/
|
.tmp/
|
||||||
.versions/
|
.versions/
|
||||||
|
@ -22,10 +23,7 @@ node_modules/
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
hooks/
|
hooks/
|
||||||
platforms/
|
|
||||||
plugins/
|
plugins/
|
||||||
plugins/android.json
|
|
||||||
plugins/ios.json
|
|
||||||
www/
|
www/
|
||||||
$RECYCLE.BIN/
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
@ -35,3 +33,5 @@ UserInterfaceState.xcuserstate
|
||||||
|
|
||||||
# other bits
|
# other bits
|
||||||
pnut-oauth.ts
|
pnut-oauth.ts
|
||||||
|
src/pages/login/pnutauth.ts
|
||||||
|
platforms/
|
||||||
|
|
140
CHANGELOG.md
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- iOS icons
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Paste from clipboard into login fields
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Replaced login page and workflow
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Cordova platform build
|
||||||
|
|
||||||
|
## [0.7.0]
|
||||||
|
### Fixed
|
||||||
|
- Sharing post preserves links
|
||||||
|
- Native android build tree
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- About page
|
||||||
|
- Profile page
|
||||||
|
- Follow, mute, and block actions
|
||||||
|
- Setting to hide images in timeline
|
||||||
|
- Copy post to clipboard
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Image thumbnails are smaller and include title & description
|
||||||
|
- Icons added to main menu
|
||||||
|
- Updated to Android cordova 7.1.2
|
||||||
|
|
||||||
|
## [0.6.3]
|
||||||
|
### Added
|
||||||
|
- Open post in browser window
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Embedded images show thumbnail by default
|
||||||
|
- Tapping or clicking an image opens source up in browser
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Unused cordova plugins for keyboard, console, and statusbar
|
||||||
|
|
||||||
|
## [0.6.2] - 2018-05-07
|
||||||
|
### Fixed
|
||||||
|
- Missing status bar when app is loaded
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated android platform requirements
|
||||||
|
- Updated project dependencies
|
||||||
|
|
||||||
|
## [0.6.1] - 2018-02-14
|
||||||
|
### Fixed
|
||||||
|
- Login using multi-factor authentication
|
||||||
|
|
||||||
|
## [0.6.0] - 2018-02-24
|
||||||
|
### Added
|
||||||
|
- Convert posts exceeding 254 characters to long post format
|
||||||
|
- File uploads
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated project dependencies
|
||||||
|
- Display login page when required scope changes
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Handle invalid auth tokens
|
||||||
|
|
||||||
|
## [0.5.0] - 2018-01-19
|
||||||
|
### Added
|
||||||
|
- Option to select default timeline
|
||||||
|
- Render long posts in timeline
|
||||||
|
- Share posts outside of Goober
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Authentication on Kindle Fire OS
|
||||||
|
- Extraneous / being tacked onto individual replies
|
||||||
|
- Handle white transparent avatars like @c does
|
||||||
|
- Missing overflow menu in conversation view
|
||||||
|
|
||||||
|
## [0.4.0] - 2017-08-05
|
||||||
|
### Added
|
||||||
|
- Ability to delete posts
|
||||||
|
- Settings page
|
||||||
|
- Option for unified timeline
|
||||||
|
- Option to cc on a reply-all
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bookmarks and reposts from conversation view
|
||||||
|
- Hyperlinks in conversation view
|
||||||
|
- Deleted posts showing as empty in timeline
|
||||||
|
- Self mention on replies
|
||||||
|
|
||||||
|
## [0.3.0] - 2017-07-09
|
||||||
|
### Added
|
||||||
|
- Toast resposnes when completing actions (post, repost, bookmark)
|
||||||
|
- Highlight posts mentioning you
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated oauth workflow to work with other device platforms
|
||||||
|
- Updated order of menu and action buttons
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Show reposts from originator
|
||||||
|
|
||||||
|
## [0.2.0] - 2017-06-20
|
||||||
|
### Added
|
||||||
|
- Mentions page
|
||||||
|
- Bookmarks page
|
||||||
|
- Character count on new post form
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Post format
|
||||||
|
- Make hyperlinks active
|
||||||
|
- New post window size
|
||||||
|
- Cleaned up login page
|
||||||
|
|
||||||
|
## [0.1.1] - 2017-06-11
|
||||||
|
### Fixed
|
||||||
|
- Focus cursor on text area when composing a post or reply
|
||||||
|
- Enable spell check and auto complete
|
||||||
|
|
||||||
|
## [0.1.0] - 2017-06-11
|
||||||
|
### Added
|
||||||
|
- Intial pre-release for Android
|
||||||
|
|
||||||
|
[Unreleased]: https://gitlab.dreamfall.space/thrrgilag/Goober/compare/0.7.0...HEAD
|
||||||
|
[0.7.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.7.0
|
||||||
|
[0.6.3]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.6.3
|
||||||
|
[0.6.2]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_2
|
||||||
|
[0.6.1]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_1
|
||||||
|
[0.6.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_0
|
||||||
|
[0.5.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_5_0
|
||||||
|
[0.4.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_4_0
|
||||||
|
[0.3.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_3_0
|
||||||
|
[0.2.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_2_0
|
||||||
|
[0.1.1]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_1_1
|
||||||
|
[0.1.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_1_0
|
35
Makefile
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
PKG_ID = com.monkeystew.goober_m
|
||||||
|
|
||||||
|
OUT_DIR = platforms/android/app/build/outputs/apk/release
|
||||||
|
UNALIGNED = $(OUT_DIR)/app-release-unsigned.apk
|
||||||
|
ALIGNED = $(OUT_DIR)/app-release-unsigned-aligned.apk
|
||||||
|
APK = $(OUT_DIR)/goober.apk
|
||||||
|
|
||||||
|
KEYSTORE = ~/android-keystore.jks
|
||||||
|
|
||||||
|
|
||||||
|
lab:
|
||||||
|
ionic serve -lc
|
||||||
|
|
||||||
|
clean: wwwclean
|
||||||
|
|
||||||
|
wwwclean:
|
||||||
|
rm -r www
|
||||||
|
|
||||||
|
distclean:
|
||||||
|
rm -r node_modules platforms plugins www
|
||||||
|
|
||||||
|
init:
|
||||||
|
npm install
|
||||||
|
ionic cordova platform add browser
|
||||||
|
|
||||||
|
ut:
|
||||||
|
npm run ionic:build
|
||||||
|
rm www/manifest.json
|
||||||
|
cd ubuntutouch && clickable
|
||||||
|
|
||||||
|
pwa:
|
||||||
|
ionic build --prod
|
||||||
|
cp resources/*.png www/assets/icon
|
||||||
|
cp -r www ~/opt/Goober
|
29
README.md
|
@ -1,9 +1,12 @@
|
||||||
# Goober, a mobile app for pnut.io
|
# Goober, a mobile app for pnut.io
|
||||||
|
|
||||||
Copyright 2017 Morgan McMillian
|
Copyright 2017 - 2018 Morgan McMillian
|
||||||
|
|
||||||
Goober is a cross platform mobile client for pnut.io built using the Ionic framework (http://ionicframework.com/).
|
Goober is a cross platform mobile client for pnut.io built using the Ionic framework (http://ionicframework.com/).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
**This project is no longer under active development or support. If you decide to fork and continue development let me know. I might transfer this project or link to the new effort whichever is appropriate.**
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
|
@ -33,16 +36,12 @@ npm install -g ionic cordova
|
||||||
## Platform build dependencies
|
## Platform build dependencies
|
||||||
|
|
||||||
#### Android
|
#### Android
|
||||||
|
* Install Java Development Kit
|
||||||
|
* https://java.com
|
||||||
* Install either Android Studio or the command line tools
|
* Install either Android Studio or the command line tools
|
||||||
* https://developer.android.com/studio/index.html#downloads
|
* https://developer.android.com/studio/index.html#downloads
|
||||||
|
* Install Gradle (needed if you only installed the command line tools)
|
||||||
#### BlackBerry 10
|
* https://gradle.org/
|
||||||
* Install the either the Native SDK or the WebWorks SDK
|
|
||||||
* http://developer.blackberry.com/develop/platform_choice/bb10.html
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
* Install VisualStudio
|
|
||||||
* https://www.visualstudio.com/
|
|
||||||
|
|
||||||
|
|
||||||
## Other build dependencies
|
## Other build dependencies
|
||||||
|
@ -54,7 +53,15 @@ npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Build and run
|
Install at least one target platform
|
||||||
|
```bash
|
||||||
|
ionic cordova platform add android
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Build and run using ionic framework
|
||||||
|
|
||||||
|
Copy src/providers/pnut-oauth.ts.sample to src/providers/pnut-oauth.ts and include the client ID provided by pnut.io. See the [developer documentation](https://pnut.io/docs/api/implementation/overview) for more details.
|
||||||
|
|
||||||
#### Browser
|
#### Browser
|
||||||
```bash
|
```bash
|
||||||
|
@ -63,7 +70,7 @@ ionic serve --lab
|
||||||
|
|
||||||
#### Android
|
#### Android
|
||||||
```bash
|
```bash
|
||||||
ionic cordova platform add android # if not yet added
|
ionic cordova platform add android
|
||||||
ionic cordova build android
|
ionic cordova build android
|
||||||
ionic cordova run android
|
ionic cordova run android
|
||||||
```
|
```
|
||||||
|
|
36
config.xml
|
@ -1,5 +1,5 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<widget id="com.monkeystew.goober-m" version="0.1.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
<widget id="com.monkeystew.goober_m" version="0.8.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
<name>Goober</name>
|
<name>Goober</name>
|
||||||
<description>Goober, a mobile app for pnut.io</description>
|
<description>Goober, a mobile app for pnut.io</description>
|
||||||
<author email="gilag@monkeystew.com" href="https://monkeystew.org">Morgan McMillian</author>
|
<author email="gilag@monkeystew.com" href="https://monkeystew.org">Morgan McMillian</author>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<preference name="webviewbounce" value="false" />
|
<preference name="webviewbounce" value="false" />
|
||||||
<preference name="UIWebViewBounce" value="false" />
|
<preference name="UIWebViewBounce" value="false" />
|
||||||
<preference name="DisallowOverscroll" value="true" />
|
<preference name="DisallowOverscroll" value="true" />
|
||||||
<preference name="android-minSdkVersion" value="16" />
|
<preference name="android-minSdkVersion" value="19" />
|
||||||
<preference name="BackupWebStorage" value="none" />
|
<preference name="BackupWebStorage" value="none" />
|
||||||
<preference name="SplashMaintainAspectRatio" value="true" />
|
<preference name="SplashMaintainAspectRatio" value="true" />
|
||||||
<preference name="FadeSplashScreenDuration" value="300" />
|
<preference name="FadeSplashScreenDuration" value="300" />
|
||||||
|
@ -77,14 +77,26 @@
|
||||||
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
|
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
|
||||||
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
|
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
|
||||||
</platform>
|
</platform>
|
||||||
<engine name="android" spec="^6.2.3" />
|
<platform name="blackberry10">
|
||||||
<engine name="blackberry10" spec="^3.8.0" />
|
<preference name="WebSecurity" value="disable" />
|
||||||
<engine name="windows" spec="^5.0.0" />
|
<icon height="90" src="resources/icon-90.png" width="90" />
|
||||||
<plugin name="cordova-plugin-console" spec="^1.0.5" />
|
<icon height="96" src="resources/icon-96.png" width="96" />
|
||||||
<plugin name="cordova-plugin-device" spec="^1.1.4" />
|
<icon height="110" src="resources/icon-110.png" width="110" />
|
||||||
<plugin name="cordova-plugin-inappbrowser" spec="^1.7.1" />
|
<icon height="144" src="resources/icon-144.png" width="144" />
|
||||||
<plugin name="cordova-plugin-splashscreen" spec="^4.0.3" />
|
</platform>
|
||||||
<plugin name="cordova-plugin-statusbar" spec="^2.2.2" />
|
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
|
||||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.1" />
|
<plugin name="cordova-plugin-share-content" spec="^1.0.0" />
|
||||||
<plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
|
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
||||||
|
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
||||||
|
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="0.0.19" />
|
||||||
|
<plugin name="cordova-plugin-inappbrowser" spec="^2.0.2" />
|
||||||
|
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
||||||
|
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
||||||
|
<plugin name="cordova-plugin-device" spec="^2.0.1" />
|
||||||
|
<plugin name="cordova-android-support-gradle-release" spec="^1.4.2">
|
||||||
|
<variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
|
||||||
|
</plugin>
|
||||||
|
<plugin name="cordova-plugin-filepath" spec="^1.3.0" />
|
||||||
|
<plugin name="cordova-sqlite-storage" spec="2.5.1" />
|
||||||
|
<allow-navigation href="http://192.168.1.72:8100" />
|
||||||
</widget>
|
</widget>
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "Goober",
|
"name": "Goober",
|
||||||
"app_id": "com.monkeystew.goober-m",
|
"app_id": "com.monkeystew.goober-m",
|
||||||
"type": "ionic-angular"
|
"type": "ionic-angular",
|
||||||
|
"integrations": {
|
||||||
|
"cordova": {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
9867
package-lock.json
generated
Normal file
147
package.json
|
@ -1,69 +1,84 @@
|
||||||
{
|
{
|
||||||
"name": "Goober",
|
"name": "Goober",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"author": "Ionic Framework",
|
"author": "Ionic Framework",
|
||||||
"homepage": "http://ionicframework.com/",
|
"homepage": "http://ionicframework.com/",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "ionic-app-scripts clean",
|
"clean": "ionic-app-scripts clean",
|
||||||
"build": "ionic-app-scripts build",
|
"build": "ionic-app-scripts build",
|
||||||
"lint": "ionic-app-scripts lint",
|
"lint": "ionic-app-scripts lint",
|
||||||
"ionic:build": "ionic-app-scripts build",
|
"ionic:build": "ionic-app-scripts build",
|
||||||
"ionic:serve": "ionic-app-scripts serve"
|
"ionic:serve": "ionic-app-scripts serve"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/common": "5.2.9",
|
||||||
|
"@angular/compiler": "5.2.9",
|
||||||
|
"@angular/compiler-cli": "5.2.9",
|
||||||
|
"@angular/core": "5.2.9",
|
||||||
|
"@angular/forms": "5.2.9",
|
||||||
|
"@angular/http": "5.2.9",
|
||||||
|
"@angular/platform-browser": "5.2.9",
|
||||||
|
"@angular/platform-browser-dynamic": "5.2.9",
|
||||||
|
"@ionic-native/core": "4.9.0",
|
||||||
|
"@ionic-native/device": "4.9.0",
|
||||||
|
"@ionic-native/file": "4.9.0",
|
||||||
|
"@ionic-native/file-chooser": "4.9.0",
|
||||||
|
"@ionic-native/file-path": "4.9.0",
|
||||||
|
"@ionic-native/file-transfer": "4.9.0",
|
||||||
|
"@ionic-native/splash-screen": "4.9.0",
|
||||||
|
"@ionic-native/status-bar": "4.9.0",
|
||||||
|
"@ionic/storage": "2.1.3",
|
||||||
|
"com-darryncampbell-cordova-plugin-intent": "^1.1.1",
|
||||||
|
"cordova-android-support-gradle-release": "^1.4.7",
|
||||||
|
"cordova-browser": "6.0.0",
|
||||||
|
"cordova-plugin-device": "^2.0.2",
|
||||||
|
"cordova-plugin-file": "^6.0.1",
|
||||||
|
"cordova-plugin-file-transfer": "^1.7.1",
|
||||||
|
"cordova-plugin-filechooser": "^1.0.1",
|
||||||
|
"cordova-plugin-filepath": "^1.4.2",
|
||||||
|
"cordova-plugin-inappbrowser": "^3.0.0",
|
||||||
|
"cordova-plugin-share-content": "^1.0.0",
|
||||||
|
"cordova-plugin-splashscreen": "^5.0.2",
|
||||||
|
"cordova-plugin-telerik-imagepicker": "^2.1.8",
|
||||||
|
"cordova-plugin-whitelist": "^1.3.3",
|
||||||
|
"cordova-sqlite-storage": "^2.5.1",
|
||||||
|
"ionic-angular": "3.9.2",
|
||||||
|
"ionicons": "4.2.4",
|
||||||
|
"moment": "^2.18.1",
|
||||||
|
"ng2-cordova-oauth": "0.0.8",
|
||||||
|
"ngx-clipboard": "^11.1.9",
|
||||||
|
"pnut-butter": "^0.21.0",
|
||||||
|
"run": "^1.4.0",
|
||||||
|
"rxjs": "5.5.8",
|
||||||
|
"sw-toolbox": "3.6.0",
|
||||||
|
"zone.js": "0.8.26"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ionic/app-scripts": "3.1.10",
|
||||||
|
"ionic": "3.20.0",
|
||||||
|
"typescript": "2.8.3"
|
||||||
|
},
|
||||||
|
"description": "An Ionic project",
|
||||||
|
"cordova": {
|
||||||
|
"plugins": {
|
||||||
|
"cordova-plugin-share-content": {},
|
||||||
|
"cordova-plugin-filechooser": {},
|
||||||
|
"cordova-plugin-file": {},
|
||||||
|
"cordova-plugin-file-transfer": {},
|
||||||
|
"com-darryncampbell-cordova-plugin-intent": {},
|
||||||
|
"cordova-plugin-inappbrowser": {},
|
||||||
|
"cordova-plugin-whitelist": {},
|
||||||
|
"cordova-plugin-splashscreen": {},
|
||||||
|
"cordova-plugin-device": {},
|
||||||
|
"cordova-android-support-gradle-release": {
|
||||||
|
"ANDROID_SUPPORT_VERSION": "27.+"
|
||||||
|
},
|
||||||
|
"cordova-plugin-filepath": {},
|
||||||
|
"cordova-sqlite-storage": {}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"platforms": [
|
||||||
"@angular/common": "4.1.2",
|
"browser"
|
||||||
"@angular/compiler": "4.1.2",
|
]
|
||||||
"@angular/compiler-cli": "4.1.2",
|
}
|
||||||
"@angular/core": "4.1.2",
|
|
||||||
"@angular/forms": "4.1.2",
|
|
||||||
"@angular/http": "4.1.2",
|
|
||||||
"@angular/platform-browser": "4.1.2",
|
|
||||||
"@angular/platform-browser-dynamic": "4.1.2",
|
|
||||||
"@ionic-native/core": "3.10.2",
|
|
||||||
"@ionic-native/splash-screen": "3.10.2",
|
|
||||||
"@ionic-native/status-bar": "3.10.2",
|
|
||||||
"@ionic/storage": "2.0.1",
|
|
||||||
"cordova-android": "^6.2.3",
|
|
||||||
"cordova-blackberry10": "^3.8.0",
|
|
||||||
"cordova-plugin-console": "^1.0.5",
|
|
||||||
"cordova-plugin-device": "^1.1.4",
|
|
||||||
"cordova-plugin-inappbrowser": "^1.7.1",
|
|
||||||
"cordova-plugin-splashscreen": "^4.0.3",
|
|
||||||
"cordova-plugin-statusbar": "^2.2.2",
|
|
||||||
"cordova-plugin-whitelist": "^1.3.1",
|
|
||||||
"cordova-windows": "^5.0.0",
|
|
||||||
"ionic-angular": "3.3.0",
|
|
||||||
"ionic-plugin-keyboard": "^2.2.1",
|
|
||||||
"ionicons": "3.0.0",
|
|
||||||
"moment": "^2.18.1",
|
|
||||||
"ng2-cordova-oauth": "0.0.8",
|
|
||||||
"pnut-butter": "^0.8.1",
|
|
||||||
"rxjs": "5.1.1",
|
|
||||||
"sw-toolbox": "3.6.0",
|
|
||||||
"zone.js": "0.8.11"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@ionic/app-scripts": "1.3.7",
|
|
||||||
"@ionic/cli-plugin-cordova": "1.3.0",
|
|
||||||
"@ionic/cli-plugin-ionic-angular": "1.3.0",
|
|
||||||
"typescript": "2.3.3"
|
|
||||||
},
|
|
||||||
"description": "An Ionic project",
|
|
||||||
"cordova": {
|
|
||||||
"plugins": {
|
|
||||||
"cordova-plugin-console": {},
|
|
||||||
"cordova-plugin-device": {},
|
|
||||||
"cordova-plugin-splashscreen": {},
|
|
||||||
"cordova-plugin-statusbar": {},
|
|
||||||
"cordova-plugin-whitelist": {},
|
|
||||||
"ionic-plugin-keyboard": {},
|
|
||||||
"cordova-plugin-inappbrowser": {}
|
|
||||||
},
|
|
||||||
"platforms": [
|
|
||||||
"android",
|
|
||||||
"blackberry10",
|
|
||||||
"windows"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
BIN
resources/icon-110.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
resources/icon-128.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
resources/icon-144.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
resources/icon-512.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
resources/icon-90.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
resources/icon-96.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
resources/icon-ipad-retina.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
resources/icon-ipad.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
resources/icon-iphone-retina.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/icon-iphone.png
Normal file
After Width: | Height: | Size: 16 KiB |
1
resources/icon.png.md5
Normal file
|
@ -0,0 +1 @@
|
||||||
|
b72de713e098078c68cbffd6ab82439b
|
1
resources/splash.png.md5
Normal file
|
@ -0,0 +1 @@
|
||||||
|
7ce07128c050f41eb46a94eaddd55d4a
|
|
@ -1,11 +1,17 @@
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { Nav, Platform } from 'ionic-angular';
|
import { Nav, Platform } from 'ionic-angular';
|
||||||
import { StatusBar } from '@ionic-native/status-bar';
|
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
import { Storage } from '@ionic/storage';
|
import { Storage } from '@ionic/storage';
|
||||||
|
import { Device } from '@ionic-native/device';
|
||||||
|
|
||||||
import { LoginPage } from '../pages/login/login';
|
import { LoginPage } from '../pages/login/login';
|
||||||
import { StreamPage } from '../pages/stream/stream';
|
import { StreamPage } from '../pages/stream/stream';
|
||||||
|
import { SettingsPage } from '../pages/settings/settings';
|
||||||
|
import { AboutPage } from '../pages/about/about';
|
||||||
|
import { ProfilePage } from '../pages/profile/profile';
|
||||||
|
import { pnutauth } from '../pages/login/pnutauth';
|
||||||
|
|
||||||
|
import { IUser } from '../models/IUser';
|
||||||
|
|
||||||
import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@ -15,44 +21,93 @@ import * as pnut from 'pnut-butter';
|
||||||
export class MyApp {
|
export class MyApp {
|
||||||
@ViewChild(Nav) nav: Nav;
|
@ViewChild(Nav) nav: Nav;
|
||||||
|
|
||||||
rootPage: any = LoginPage;
|
rootPage: any = StreamPage;
|
||||||
|
pages: Array<{title: string, icon: string, component: any, params: Object}>;
|
||||||
|
profile: IUser;
|
||||||
|
|
||||||
pages: Array<{title: string, component: any, params: Object}>;
|
constructor(public platform: Platform, public splashScreen: SplashScreen,
|
||||||
|
private storage: Storage, private device: Device) {
|
||||||
constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen, private storage: Storage) {
|
|
||||||
this.initializeApp();
|
this.initializeApp();
|
||||||
|
|
||||||
// used for an example of ngFor and navigation
|
// used for an example of ngFor and navigation
|
||||||
this.pages = [
|
this.pages = [
|
||||||
{ title: 'Personal Stream', component: StreamPage, params: {stream: 'personal'} },
|
{ title: 'Timeline', icon: 'home', component: StreamPage, params: {stream: 'personal'} },
|
||||||
{ title: 'Global Stream', component: StreamPage, params: {stream: 'global'} },
|
{ title: 'Mentions', icon: 'at', component: StreamPage, params: {stream: 'mentions'} },
|
||||||
|
{ title: 'Global', icon: 'globe', component: StreamPage, params: {stream: 'global'} },
|
||||||
|
{ title: 'Bookmarks', icon: 'bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
||||||
|
{ title: 'Profile', icon: 'person', component: ProfilePage, params: {}},
|
||||||
|
{ title: 'Settings', icon: 'settings', component: SettingsPage, params: {}},
|
||||||
|
{ title: 'About', icon: 'information-circle', component: AboutPage, params: {}},
|
||||||
|
{ title: 'Logout', icon: 'exit', component: {}, params: {}},
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeApp() {
|
initializeApp() {
|
||||||
console.log('--- initializeApp ---');
|
console.log('--- initializeApp ---');
|
||||||
this.storage.get('token').then((val) => {
|
|
||||||
if (val.length > 1) {
|
|
||||||
pnut.token = val;
|
|
||||||
this.nav.setRoot(StreamPage, {stream: 'personal'});
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('ERROR: ' + err);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.platform.ready().then(() => {
|
this.platform.ready().then(() => {
|
||||||
// Okay, so the platform is ready and our plugins are available.
|
// Okay, so the platform is ready and our plugins are available.
|
||||||
// Here you can do any higher level native things you might need.
|
// Here you can do any higher level native things you might need.
|
||||||
this.statusBar.styleDefault();
|
|
||||||
|
this.storage.get('timeline').then((val) => {
|
||||||
|
if (val.length > 1) {
|
||||||
|
this.initialPage(val);
|
||||||
|
} else {
|
||||||
|
console.log('ERR WHUT?');
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
this.initialPage('personal');
|
||||||
|
});
|
||||||
|
|
||||||
this.splashScreen.hide();
|
this.splashScreen.hide();
|
||||||
|
// console.log('---');
|
||||||
|
// console.log(this.device.platform);
|
||||||
|
// console.log('---');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openPage(page) {
|
initialPage(timeline) {
|
||||||
|
this.storage.get('token').then((val) => {
|
||||||
|
if (val.length > 1) {
|
||||||
|
pnut.token = val;
|
||||||
|
|
||||||
|
this.storage.get('scope').then((sval) => {
|
||||||
|
if (JSON.stringify(sval) !== JSON.stringify(pnutauth.scope)) {
|
||||||
|
this.nav.setRoot(LoginPage, {});
|
||||||
|
} else {
|
||||||
|
this.nav.setRoot(StreamPage, {stream: timeline});
|
||||||
|
}
|
||||||
|
// this.nav.setRoot(StreamPage, {stream: timeline});
|
||||||
|
}).catch(err => {
|
||||||
|
this.nav.setRoot(LoginPage, {});
|
||||||
|
});
|
||||||
|
// this.nav.setRoot(StreamPage, {stream: timeline});
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' + err);
|
||||||
|
this.nav.setRoot(LoginPage, {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async openPage(page) {
|
||||||
// Reset the content nav to have just this page
|
// Reset the content nav to have just this page
|
||||||
// we wouldn't want the back button to show in this scenario
|
// we wouldn't want the back button to show in this scenario
|
||||||
this.nav.setRoot(page.component, page.params);
|
if (page.title === 'Logout') {
|
||||||
|
// this.storage.remove('token');
|
||||||
|
this.storage.clear();
|
||||||
|
this.nav.setRoot(LoginPage, {});
|
||||||
|
} else if (page.title === 'Settings' || page.title === 'About') {
|
||||||
|
this.nav.push(page.component, page.params);
|
||||||
|
} else if (page.title === 'Profile') {
|
||||||
|
await pnut.user('me').then(res => {
|
||||||
|
this.profile = res.data as IUser;
|
||||||
|
});
|
||||||
|
page.params = {user: this.profile, me: this.profile.username};
|
||||||
|
this.nav.push(page.component, page.params);
|
||||||
|
} else {
|
||||||
|
this.nav.setRoot(page.component, page.params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
|
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
|
||||||
{{p.title}}
|
<ion-icon name="{{p.icon}}"></ion-icon>
|
||||||
|
<span class="menuText">{{p.title}}</span>
|
||||||
</button>
|
</button>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { ErrorHandler, NgModule } from '@angular/core';
|
import { ErrorHandler, NgModule } from '@angular/core';
|
||||||
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
|
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
import { MyApp } from './app.component';
|
import { MyApp } from './app.component';
|
||||||
import { LoginPage } from '../pages/login/login';
|
import { LoginPage } from '../pages/login/login';
|
||||||
import { StreamPage, NewPostModal } from '../pages/stream/stream';
|
import { StreamPage } from '../pages/stream/stream';
|
||||||
|
import { PostMenu } from '../pages/stream/post-menu';
|
||||||
|
import { NewPostModal } from '../pages/stream/new-post';
|
||||||
import { ThreadPage } from '../pages/thread/thread';
|
import { ThreadPage } from '../pages/thread/thread';
|
||||||
|
import { SettingsPage } from '../pages/settings/settings';
|
||||||
|
import { AboutPage } from '../pages/about/about';
|
||||||
|
import { ProfilePage } from '../pages/profile/profile';
|
||||||
|
import { ProfileMenu } from '../pages/profile/profile-menu';
|
||||||
|
import { UserListPage } from '../pages/user-list/user-list';
|
||||||
|
import { PostComponent } from '../components/post/post';
|
||||||
|
|
||||||
import { StatusBar } from '@ionic-native/status-bar';
|
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
import { IonicStorageModule } from '@ionic/storage';
|
import { IonicStorageModule } from '@ionic/storage';
|
||||||
|
import { Device } from '@ionic-native/device';
|
||||||
|
import { FileChooser } from '@ionic-native/file-chooser';
|
||||||
|
import { FilePath } from '@ionic-native/file-path';
|
||||||
|
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';
|
||||||
import { TimeagoPipe } from '../pipes/timeago/timeago';
|
import { TimeagoPipe } from '../pipes/timeago/timeago';
|
||||||
|
import { ParserPipe } from '../pipes/parser/parser';
|
||||||
|
import { ClipboardModule } from 'ngx-clipboard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -18,13 +32,23 @@ import { TimeagoPipe } from '../pipes/timeago/timeago';
|
||||||
LoginPage,
|
LoginPage,
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
|
SettingsPage,
|
||||||
|
AboutPage,
|
||||||
|
ProfilePage,
|
||||||
|
UserListPage,
|
||||||
TimeagoPipe,
|
TimeagoPipe,
|
||||||
NewPostModal
|
NewPostModal,
|
||||||
|
PostMenu,
|
||||||
|
ProfileMenu,
|
||||||
|
ParserPipe,
|
||||||
|
PostComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
HttpClientModule,
|
||||||
IonicModule.forRoot(MyApp),
|
IonicModule.forRoot(MyApp),
|
||||||
IonicStorageModule.forRoot(),
|
IonicStorageModule.forRoot(),
|
||||||
|
ClipboardModule,
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
@ -32,11 +56,21 @@ import { TimeagoPipe } from '../pipes/timeago/timeago';
|
||||||
LoginPage,
|
LoginPage,
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
NewPostModal
|
SettingsPage,
|
||||||
|
AboutPage,
|
||||||
|
ProfilePage,
|
||||||
|
UserListPage,
|
||||||
|
NewPostModal,
|
||||||
|
PostMenu,
|
||||||
|
ProfileMenu
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
StatusBar,
|
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
|
Device,
|
||||||
|
FileChooser,
|
||||||
|
FilePath,
|
||||||
|
FileTransfer,
|
||||||
|
FileTransferObject,
|
||||||
{provide: ErrorHandler, useClass: IonicErrorHandler}
|
{provide: ErrorHandler, useClass: IonicErrorHandler}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,3 +14,8 @@
|
||||||
// To declare rules for a specific mode, create a child rule
|
// To declare rules for a specific mode, create a child rule
|
||||||
// for the .md, .ios, or .wp mode classes. The mode class is
|
// for the .md, .ios, or .wp mode classes. The mode class is
|
||||||
// automatically applied to the <body> element in the app.
|
// automatically applied to the <body> element in the app.
|
||||||
|
|
||||||
|
.menuText {
|
||||||
|
left: 45px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 19 KiB |
BIN
src/assets/icon/icon.png
Normal file
After Width: | Height: | Size: 21 KiB |
95
src/components/post/post.html
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
|
||||||
|
<ion-item color="{{ post.you_are_mentioned ? 'mention' : '' }}" (click)="showProfile(post.user)">
|
||||||
|
<ion-avatar item-start >
|
||||||
|
<img src="{{ post.user.content.avatar_image.link }}">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>{{ post.user.name }}</h2>
|
||||||
|
<p>@{{ post.user.username }}</p>
|
||||||
|
<ion-note item-end>
|
||||||
|
<div text-right>
|
||||||
|
{{ post.created_at | timeago }}<br/>
|
||||||
|
{{ post.source.name }}
|
||||||
|
</div>
|
||||||
|
</ion-note>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-card-content>
|
||||||
|
<div *ngIf="post.is_deleted; else renderBlock"></div>
|
||||||
|
<ng-template #renderBlock >
|
||||||
|
<div [innerHTML]="post.content.html | parser"></div>
|
||||||
|
<div *ngIf="post.raw">
|
||||||
|
<div *ngFor="let r of post.raw">
|
||||||
|
<div *ngIf="r.type == 'nl.chimpnut.blog.post'">
|
||||||
|
<hr>
|
||||||
|
<div>{{ r.value.body }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ion-card-content>
|
||||||
|
|
||||||
|
<div *ngIf="post.raw">
|
||||||
|
<ion-list *ngFor="let r of post.raw">
|
||||||
|
<div *ngIf="r.type == 'io.pnut.core.oembed'">
|
||||||
|
<div *ngIf="hideImg; then hidebtn else thumbbtn"></div>
|
||||||
|
<ng-template #thumbbtn>
|
||||||
|
<ion-item>
|
||||||
|
<ion-thumbnail item-start>
|
||||||
|
<img src="{{ r.value.thumbnail_url || r.value.url }}" (click)="showImage(r.value.url)">
|
||||||
|
</ion-thumbnail>
|
||||||
|
<h2>{{ r.value.title }}</h2>
|
||||||
|
<p>{{ r.value.description }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #hidebtn>
|
||||||
|
<ion-item>
|
||||||
|
<button ion-button icon-start (click)="showImage(r.value.url)">
|
||||||
|
<ion-icon name="image"></ion-icon>
|
||||||
|
{{ r.value.title }}
|
||||||
|
</button>
|
||||||
|
<p>{{ r.value.description }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="post.reposted_by_string">
|
||||||
|
<ion-item><ion-note>{{ post.reposted_by_string }}</ion-note></ion-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-row>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="showReplyPost(post,'reply')">
|
||||||
|
<ion-icon name="ios-undo"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="showReplyPost(post,'quote')">
|
||||||
|
<ion-icon name="quote"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="repost(post.id, post.you_reposted)">
|
||||||
|
<ion-icon name="repeat"></ion-icon>
|
||||||
|
<div *ngIf="post.counts.reposts > 0">{{ post.counts.reposts }}</div>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="bookmark(post.id, post.you_bookmarked)">
|
||||||
|
<ion-icon name="star"></ion-icon>
|
||||||
|
<div *ngIf="post.counts.bookmarks > 0">{{ post.counts.bookmarks }}</div>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="fetchThread(post.thread_id)">
|
||||||
|
<ion-icon name="chatboxes"></ion-icon>
|
||||||
|
<div *ngIf="post.counts.replies > 0">{{ post.counts.replies }}</div>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<button ion-button icon-left clear small block (click)="presentPostMenu($event, post)">
|
||||||
|
<ion-icon name="more"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
14
src/components/post/post.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
post {
|
||||||
|
.item-md ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-wp ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-ios ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
}
|
120
src/components/post/post.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { NavController, NavParams, ModalController, ToastController, PopoverController } from 'ionic-angular';
|
||||||
|
import { ProfilePage } from '../../pages/profile/profile';
|
||||||
|
import { ThreadPage } from '../../pages/thread/thread';
|
||||||
|
import { LoginPage } from '../../pages/login/login';
|
||||||
|
import { NewPostModal } from '../../pages/stream/new-post';
|
||||||
|
import { PostMenu } from '../../pages/stream/post-menu';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the PostComponent component.
|
||||||
|
*
|
||||||
|
* See https://angular.io/api/core/Component for more info on Angular
|
||||||
|
* Components.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'post',
|
||||||
|
templateUrl: 'post.html'
|
||||||
|
})
|
||||||
|
export class PostComponent {
|
||||||
|
|
||||||
|
@Input() public post: Object;
|
||||||
|
@Input() public hideImg: boolean;
|
||||||
|
@Input() public ccOnReply: boolean;
|
||||||
|
@Input() public myUsername: string;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||||
|
public modalCtrl: ModalController, public toastCtrl: ToastController,
|
||||||
|
public popoverCtrl: PopoverController) {}
|
||||||
|
|
||||||
|
fetchThread(threadid) {
|
||||||
|
pnut.thread(threadid, {include_deleted: 0, include_raw: 1, count: 140}).then(res => {
|
||||||
|
if (res.meta.code === 401) {
|
||||||
|
// this.storage.clear();
|
||||||
|
this.navCtrl.setRoot(LoginPage);
|
||||||
|
} else {
|
||||||
|
this.navCtrl.push(ThreadPage, {posts: res.data, me: this.myUsername});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showImage(url) {
|
||||||
|
window.open(url, '_system');
|
||||||
|
}
|
||||||
|
|
||||||
|
showProfile(user) {
|
||||||
|
this.navCtrl.push(ProfilePage, {user: user, me: this.myUsername});
|
||||||
|
}
|
||||||
|
|
||||||
|
showReplyPost(postData, repType) {
|
||||||
|
let newPostModal = this.modalCtrl.create(NewPostModal, {
|
||||||
|
type: repType,
|
||||||
|
post: postData,
|
||||||
|
me: this.myUsername,
|
||||||
|
cc: this.ccOnReply});
|
||||||
|
newPostModal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
repost(postid, reposted) {
|
||||||
|
if (reposted) {
|
||||||
|
pnut.deleteRepost(postid).then(res => {
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.repost(postid).then(res => {
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmark(postid, bookmarked) {
|
||||||
|
if (bookmarked) {
|
||||||
|
pnut.deleteBookmark(postid).then(res => {
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.bookmark(postid).then(res => {
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePost(postid) {
|
||||||
|
pnut.post(postid, {include_raw: 1}).then(res => {
|
||||||
|
this.post = res.data;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
presentPostMenu(myEvent, postData) {
|
||||||
|
let popover = this.popoverCtrl.create(PostMenu, {
|
||||||
|
post: postData,
|
||||||
|
me: this.myUsername});
|
||||||
|
popover.present({ev: myEvent});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,26 +2,31 @@
|
||||||
<html lang="en" dir="ltr">
|
<html lang="en" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Ionic App</title>
|
<title>Goober</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
<meta name="msapplication-tap-highlight" content="no">
|
<meta name="msapplication-tap-highlight" content="no">
|
||||||
|
|
||||||
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="assets/icon/icon-iphone.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="assets/icon/icon-ipad.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/icon/icon-iphone-retina.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="167x167" href="assets/icon/icon-ipad-retina.png">
|
||||||
|
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<meta name="theme-color" content="#4e8ef7">
|
<meta name="theme-color" content="#4e8ef7">
|
||||||
|
|
||||||
<!-- cordova.js required for cordova apps -->
|
<!-- cordova.js required for cordova apps -->
|
||||||
<script src="cordova.js"></script>
|
<script src="cordova.js"></script>
|
||||||
|
|
||||||
<!-- un-comment this code to enable service worker
|
|
||||||
<script>
|
<script>
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('service-worker.js')
|
navigator.serviceWorker.register('service-worker.js')
|
||||||
.then(() => console.log('service worker installed'))
|
.then(() => console.log('service worker installed'))
|
||||||
.catch(err => console.error('Error', err));
|
.catch(err => console.error('Error', err));
|
||||||
}
|
}
|
||||||
</script>-->
|
</script>
|
||||||
|
|
||||||
<link href="build/main.css" rel="stylesheet">
|
<link href="build/main.css" rel="stylesheet">
|
||||||
|
|
||||||
|
@ -34,6 +39,8 @@
|
||||||
<!-- The polyfills js is generated during the build process -->
|
<!-- The polyfills js is generated during the build process -->
|
||||||
<script src="build/polyfills.js"></script>
|
<script src="build/polyfills.js"></script>
|
||||||
|
|
||||||
|
<script src="build/vendor.js"></script>
|
||||||
|
|
||||||
<!-- The bundle js is generated during the build process -->
|
<!-- The bundle js is generated during the build process -->
|
||||||
<script src="build/main.js"></script>
|
<script src="build/main.js"></script>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "Ionic",
|
"name": "Goober",
|
||||||
"short_name": "Ionic",
|
"short_name": "Goober",
|
||||||
"start_url": "index.html",
|
"start_url": "index.html",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "assets/imgs/logo.png",
|
"src": "assets/icon.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}],
|
}],
|
||||||
|
|
33
src/models/IUser.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { IUserContent } from './user/IUserContent';
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
badge?: {
|
||||||
|
id: string,
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
content: IUserContent;
|
||||||
|
counts: {
|
||||||
|
bookmarks: number,
|
||||||
|
clients: number,
|
||||||
|
followers: number,
|
||||||
|
following: number,
|
||||||
|
posts: number,
|
||||||
|
users: number;
|
||||||
|
};
|
||||||
|
created_at: string;
|
||||||
|
follows_you: boolean;
|
||||||
|
id: string;
|
||||||
|
locale: string;
|
||||||
|
name: string;
|
||||||
|
timezone: string;
|
||||||
|
type: string;
|
||||||
|
username: string;
|
||||||
|
you_blocked: boolean;
|
||||||
|
you_can_follow: boolean;
|
||||||
|
you_follow: boolean;
|
||||||
|
you_muted: boolean;
|
||||||
|
verified: {
|
||||||
|
domain: string,
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
}
|
6
src/models/user/IUserAvatarImage.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IUserAvatarImage {
|
||||||
|
is_default: boolean;
|
||||||
|
height: number;
|
||||||
|
link: string;
|
||||||
|
width: number;
|
||||||
|
}
|
11
src/models/user/IUserContent.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { IUserAvatarImage } from './IUserAvatarImage';
|
||||||
|
import { IUserCoverImage } from './IUserCoverImage';
|
||||||
|
|
||||||
|
export interface IUserContent {
|
||||||
|
avatar_image: IUserAvatarImage;
|
||||||
|
cover_image: IUserCoverImage;
|
||||||
|
entities?: {};
|
||||||
|
html: string;
|
||||||
|
markdown_text: string;
|
||||||
|
text: string;
|
||||||
|
}
|
6
src/models/user/IUserCoverImage.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IUserCoverImage {
|
||||||
|
link: string;
|
||||||
|
is_default: boolean;
|
||||||
|
width: number;
|
||||||
|
heigth: number;
|
||||||
|
}
|
43
src/pages/about/about.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!--
|
||||||
|
Generated template for the AboutPage page.
|
||||||
|
|
||||||
|
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
Ionic pages and navigation.
|
||||||
|
-->
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>About</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content padding>
|
||||||
|
|
||||||
|
<ion-row center>
|
||||||
|
<ion-col text-center>
|
||||||
|
<img src="assets/icon/icon.png" height="64" width="64">
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
|
||||||
|
<ion-row center>
|
||||||
|
<ion-col text-center>
|
||||||
|
<h1>Goober {{ version }}</h1>
|
||||||
|
A mobile client for <a href="https://pnut.io" target="_system">pnut.io</a>.
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
|
||||||
|
<ion-row center>
|
||||||
|
<ion-col text-center>
|
||||||
|
<p>made by Morgan McMillian (<a href="https://pnut.io/@thrrgilag" target="_system">@thrrgilag</a>).</p>
|
||||||
|
<p>Goober is free and open source software licensed under the
|
||||||
|
Apache License 2.0.</p>
|
||||||
|
<p><a href="http://www.apache.org/licenses/LICENSE-2.0" target="_system">
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0</a></p>
|
||||||
|
<p> </p>
|
||||||
|
<p><button ion-button (click)="browse('https://gitlab.dreamfall.space/thrrgilag/Goober/wikis/home')">Project Site</button></p>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
|
||||||
|
</ion-content>
|
3
src/pages/about/about.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
page-about {
|
||||||
|
|
||||||
|
}
|
30
src/pages/about/about.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the AboutPage page.
|
||||||
|
*
|
||||||
|
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
* Ionic pages and navigation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-about',
|
||||||
|
templateUrl: 'about.html',
|
||||||
|
})
|
||||||
|
export class AboutPage {
|
||||||
|
|
||||||
|
private version: string = '0.8.0';
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public navParams: NavParams) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidLoad() {
|
||||||
|
console.log('ionViewDidLoad AboutPage');
|
||||||
|
}
|
||||||
|
|
||||||
|
browse(url) {
|
||||||
|
window.open(url, '_system');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,8 +15,17 @@
|
||||||
|
|
||||||
<ion-content padding>
|
<ion-content padding>
|
||||||
|
|
||||||
<p>Goober</p>
|
<p> </p>
|
||||||
<p>A mobile client for pnut.io</p>
|
<div text-center>
|
||||||
|
<h2>Goober</h2>
|
||||||
|
<p block>A mobile client for pnut.io</p>
|
||||||
|
</div><p> </p>
|
||||||
|
|
||||||
|
<ion-label stacked>Username</ion-label>
|
||||||
|
<ion-input [(ngModel)]="username" type="text"></ion-input>
|
||||||
|
<ion-label stacked>Password</ion-label>
|
||||||
|
<ion-input [(ngModel)]="password" type="password"></ion-input>
|
||||||
|
|
||||||
<button ion-button block (click)="login()">Log In</button>
|
<button ion-button block (click)="login()">Log In</button>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
page-login {
|
page-login {
|
||||||
|
user-select: auto !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,54 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavController, NavParams } from 'ionic-angular';
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
||||||
import { Storage } from '@ionic/storage';
|
import { Storage } from '@ionic/storage';
|
||||||
import { StreamPage } from '../stream/stream';
|
import { StreamPage } from '../stream/stream';
|
||||||
|
import { pnutauth } from './pnutauth';
|
||||||
|
|
||||||
import { OauthCordova } from 'ng2-cordova-oauth/platform/cordova';
|
|
||||||
// import { OauthBrowser } from 'ng2-cordova-oauth/platform/browser';
|
|
||||||
import { PnutAuth } from '../../providers/pnut-oauth';
|
|
||||||
import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
/**
|
|
||||||
* Generated class for the LoginPage page.
|
|
||||||
*
|
|
||||||
* See http://ionicframework.com/docs/components/#navigation for more info
|
|
||||||
* on Ionic pages and navigation.
|
|
||||||
*/
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-login',
|
selector: 'page-login',
|
||||||
templateUrl: 'login.html',
|
templateUrl: 'login.html',
|
||||||
})
|
})
|
||||||
export class LoginPage {
|
export class LoginPage {
|
||||||
|
|
||||||
private oauth: OauthCordova = new OauthCordova();
|
private username: string;
|
||||||
// private oauthb: OauthBrowser = new OauthBrowser();
|
private password: string;
|
||||||
private pnutProvider: PnutAuth = new PnutAuth({
|
|
||||||
appScope: ['basic','stream','write_post']
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams, private storage: Storage) {
|
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||||
|
private storage: Storage, private http: HttpClient) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
login() {
|
login() {
|
||||||
this.oauth.logInVia(this.pnutProvider).then(success => {
|
|
||||||
console.log('RESULT: ' + JSON.stringify(success));
|
interface LoginResponse {
|
||||||
this.storage.set('token', success['access_token']);
|
access_token: string;
|
||||||
pnut.token = success['access_token'];
|
}
|
||||||
this.navCtrl.setRoot(StreamPage, {stream: 'global'});
|
|
||||||
}, error=> {
|
let headers = new HttpHeaders()
|
||||||
console.log('ERROR: ' + error);
|
.set('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
let params = new HttpParams()
|
||||||
|
.set('client_id', pnutauth.clientId)
|
||||||
|
.set('password_grant_secret', pnutauth.clientSecret)
|
||||||
|
.set('username', this.username)
|
||||||
|
.set('password', this.password)
|
||||||
|
.set('grant_type', 'password')
|
||||||
|
.set('scope', pnutauth.scope);
|
||||||
|
|
||||||
|
this.http.post<LoginResponse>(pnutauth.url, params, {headers: headers}).subscribe(res => {
|
||||||
|
console.log('authorized');
|
||||||
|
this.storage.set('scope', pnutauth.scope);
|
||||||
|
this.storage.set('token', res.access_token);
|
||||||
|
pnut.token = res.access_token;
|
||||||
|
this.navCtrl.setRoot(StreamPage, {stream: "personal"});
|
||||||
|
}, err => {
|
||||||
|
console.log("error: " + JSON.stringify(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
6
src/pages/login/pnutauth.ts.sample
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export const pnutauth = {
|
||||||
|
url: "https://api.pnut.io/v0/oauth/access_token",
|
||||||
|
scope: "basic,stream,write_post,files",
|
||||||
|
clientId: "",
|
||||||
|
clientSecret: ""
|
||||||
|
}
|
76
src/pages/profile/profile-menu.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ViewController, NavParams, ToastController } from 'ionic-angular';
|
||||||
|
import { Events } from 'ionic-angular';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ion-list>
|
||||||
|
<button ion-item (click)="browse()">Open in browser</button>
|
||||||
|
<button ion-item [disabled]="myUsername == username">Block</button>
|
||||||
|
<button ion-item [disabled]="myUsername == username">Mute</button>
|
||||||
|
</ion-list>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class ProfileMenu {
|
||||||
|
|
||||||
|
private userid: string;
|
||||||
|
private username: string;
|
||||||
|
private you_muted: boolean;
|
||||||
|
private you_blocked: boolean;
|
||||||
|
private myUsername: string;
|
||||||
|
|
||||||
|
constructor(public navParams: NavParams, public viewCtrl: ViewController,
|
||||||
|
public toastCtrl: ToastController, public events: Events) {
|
||||||
|
this.userid = this.navParams.data.userid;
|
||||||
|
this.username = this.navParams.data.username;
|
||||||
|
this.you_muted = this.navParams.data.you_muted;
|
||||||
|
this.you_blocked = this.navParams.data.you_blocked;
|
||||||
|
this.myUsername = this.navParams.data.me;
|
||||||
|
}
|
||||||
|
|
||||||
|
browse() {
|
||||||
|
window.open('https://pnut.io/@' + this.username, '_system');
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
mute() {
|
||||||
|
if (this.you_muted) {
|
||||||
|
pnut.unmute(this.userid).then(res => {
|
||||||
|
this.presentToast('User unmuted');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.mute(this.userid).then(res => {
|
||||||
|
this.presentToast('User muted');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
if (this.you_blocked) {
|
||||||
|
pnut.unblock(this.userid).then(res => {
|
||||||
|
this.presentToast('User unblocked');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.block(this.userid).then(res => {
|
||||||
|
this.presentToast('User blocked');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
}
|
86
src/pages/profile/profile.html
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<!--
|
||||||
|
Generated template for the ProfilePage page.
|
||||||
|
|
||||||
|
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
Ionic pages and navigation.
|
||||||
|
-->
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ user.username }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button ion-button icon-only (click)="presentProfileMenu($event)">
|
||||||
|
<ion-icon name="more"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
|
||||||
|
<img src="{{ user.content.cover_image.link }}">
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-avatar item-start>
|
||||||
|
<img src="{{ user.content.avatar_image.link }}">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>{{ user.name }}</h2>
|
||||||
|
<p>@{{ user.username }}</p>
|
||||||
|
<ion-col item-end text-right>
|
||||||
|
<button ion-button [disabled]="myUsername == user.username" (click)="followUser()">{{ user.you_follow ? "Unfollow" : "Follow" }}</button>
|
||||||
|
<ion-note>{{ user.follows_you ? "Follows you" : ""}}</ion-note>
|
||||||
|
</ion-col>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<div padding [innerHTML]="user.content.html | parser"></div>
|
||||||
|
<ion-row padding>
|
||||||
|
<ion-col>
|
||||||
|
<ion-row>
|
||||||
|
<button ion-button full clear>{{ user.counts.posts }}<br/>posts</button>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row>
|
||||||
|
<button ion-button full (click)="showUserList('Followers')">{{ user.counts.followers }}<br/>followers</button>
|
||||||
|
</ion-row>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<ion-row>
|
||||||
|
<button ion-button full clear>{{ user.counts.bookmarks }}<br/>stars</button>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row>
|
||||||
|
<button ion-button full (click)="showUserList('Following')">{{ user.counts.following }}<br/>following</button>
|
||||||
|
</ion-row>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
|
||||||
|
<ion-segment [(ngModel)]="activeTab">
|
||||||
|
<ion-segment-button value="posts">Posts</ion-segment-button>
|
||||||
|
<ion-segment-button value="bookmarks">Stars</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
|
||||||
|
<div [ngSwitch]="activeTab">
|
||||||
|
|
||||||
|
<div *ngSwitchCase="'posts'">
|
||||||
|
<ion-list>
|
||||||
|
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||||
|
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||||
|
</ion-card>
|
||||||
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngSwitchCase="'bookmarks'">
|
||||||
|
<ion-list>
|
||||||
|
<ion-card *ngFor="let post of bookmarks" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||||
|
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||||
|
</ion-card>
|
||||||
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-infinite-scroll (ionInfinite)="fetchOlderPosts($event, activeTab)">
|
||||||
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
|
</ion-infinite-scroll>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ion-content>
|
14
src/pages/profile/profile.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
page-profile {
|
||||||
|
.item-md ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-wp ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-ios ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
}
|
159
src/pages/profile/profile.ts
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams, PopoverController, ToastController } from 'ionic-angular';
|
||||||
|
import { UserListPage } from '../user-list/user-list';
|
||||||
|
import { ProfileMenu } from './profile-menu'
|
||||||
|
|
||||||
|
import { IUser } from '../../models/IUser';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the ProfilePage page.
|
||||||
|
*
|
||||||
|
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
* Ionic pages and navigation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-profile',
|
||||||
|
templateUrl: 'profile.html',
|
||||||
|
})
|
||||||
|
export class ProfilePage {
|
||||||
|
|
||||||
|
private user: IUser;
|
||||||
|
private posts: Array<Object> = [];
|
||||||
|
private bookmarks: Array<Object> = [];
|
||||||
|
private before_id_post: string;
|
||||||
|
private before_id_stars: string;
|
||||||
|
private myUsername: string;
|
||||||
|
public activeTab: string = 'posts';
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||||
|
public popoverCtrl: PopoverController, public toastCtrl: ToastController) {
|
||||||
|
|
||||||
|
if (this.navParams.data.user) {
|
||||||
|
this.user = this.navParams.data.user;
|
||||||
|
this.myUsername = this.navParams.data.me;
|
||||||
|
console.log('user: ' + this.user.username);
|
||||||
|
console.log('me: ' + this.myUsername);
|
||||||
|
} else {
|
||||||
|
console.log('err, I need user data!!!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidLoad() {
|
||||||
|
console.log('ionViewDidLoad ProfilePage');
|
||||||
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
pnut.postsFrom(this.user.id, params).then(res => {
|
||||||
|
this.posts = this.parseData(res.data);
|
||||||
|
this.before_id_post = res.meta.min_id;
|
||||||
|
});
|
||||||
|
pnut.bookmarks(this.user.id, params).then(res => {
|
||||||
|
this.bookmarks = this.parseData(res.data);
|
||||||
|
this.before_id_stars = res.meta.min_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchOlderPosts(infiniteScroll, stream) {
|
||||||
|
let before_id = this.before_id_post;
|
||||||
|
let fetcher = pnut.postsFrom;
|
||||||
|
if (stream === 'bookmarks') {
|
||||||
|
before_id = this.before_id_stars;
|
||||||
|
fetcher = pnut.bookmarks;
|
||||||
|
}
|
||||||
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
before_id: before_id,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
fetcher(this.user.id, params).then(res => {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
if (stream === 'posts') {
|
||||||
|
this.posts.push.apply(this.posts, this.parseData(res.data));
|
||||||
|
this.before_id_post = res.meta.min_id;
|
||||||
|
} else if (stream === 'bookmarks') {
|
||||||
|
this.bookmarks.push.apply(this.bookmarks, this.parseData(res.data));
|
||||||
|
this.before_id_stars = res.meta.min_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infiniteScroll.complete();
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseData(data) {
|
||||||
|
var pdata = [];
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
if (!data[i].is_deleted) {
|
||||||
|
if (data[i]['repost_of']) {
|
||||||
|
data[i] = data[i]['repost_of']
|
||||||
|
var reposted_by_string = "";
|
||||||
|
let rplen = 0;
|
||||||
|
if (typeof data[i]['reposted_by'] !== "undefined") {
|
||||||
|
rplen = data[i]['reposted_by'].length;
|
||||||
|
}
|
||||||
|
for (var j = 0; j < rplen; j++) {
|
||||||
|
reposted_by_string = reposted_by_string + data[i]['reposted_by'][j]['username'] + ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data[i].content) {
|
||||||
|
for (var k = 0; k < data[i]['content']['entities']['mentions'].length; k++) {
|
||||||
|
var men = data[i]['content']['entities']['mentions'][k]['text'];
|
||||||
|
if (this.myUsername === men) {
|
||||||
|
data[i]['you_are_mentioned'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pdata.push(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUserList(list) {
|
||||||
|
this.navCtrl.push(UserListPage, {
|
||||||
|
userid: this.user.id,
|
||||||
|
username: this.myUsername,
|
||||||
|
list: list});
|
||||||
|
}
|
||||||
|
|
||||||
|
followUser() {
|
||||||
|
if (this.user.you_follow) {
|
||||||
|
pnut.unfollow(this.user.id).then(res => {
|
||||||
|
this.user = res.data;
|
||||||
|
this.presentToast('User unfollowed');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.follow(this.user.id).then(res => {
|
||||||
|
this.user = res.data;
|
||||||
|
this.presentToast('User followed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presentProfileMenu(myEvent) {
|
||||||
|
let popover = this.popoverCtrl.create(ProfileMenu, {
|
||||||
|
me: this.myUsername,
|
||||||
|
userid: this.user.id,
|
||||||
|
username: this.user.username,
|
||||||
|
you_muted: this.user.you_muted,
|
||||||
|
you_blocked: this.user.you_blocked});
|
||||||
|
popover.present({ev: myEvent});
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/pages/settings/settings.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<!--
|
||||||
|
Generated template for the SettingsPage page.
|
||||||
|
|
||||||
|
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
Ionic pages and navigation.
|
||||||
|
-->
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>settings</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content overflow-scroll=”true”>
|
||||||
|
<ion-list>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Default Timeline</ion-label>
|
||||||
|
<ion-select [(ngModel)]="set_default" (ionChange)="updateDefault()" interface="popover">
|
||||||
|
<ion-option value="personal">Home Timeline</ion-option>
|
||||||
|
<ion-option value="global">Global Timeline</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Unified Home</h2>
|
||||||
|
<p>Include mentions in home timeline</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-toggle [(ngModel)]="set_unified" (ionChange)="updateUnified()"></ion-toggle>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Use CC on Reply All</h2>
|
||||||
|
<p>Additional mentions follow /</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-toggle [(ngModel)]="set_cc" (ionChange)="updateCc()"></ion-toggle>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>Hide images</h2>
|
||||||
|
<p>Hide images in posts</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-toggle [(ngModel)]="set_hideimg" (ionChange)="updateHideImg()"></ion-toggle>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
3
src/pages/settings/settings.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
page-settings {
|
||||||
|
|
||||||
|
}
|
73
src/pages/settings/settings.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
import { Storage } from '@ionic/storage';
|
||||||
|
import { Events } from 'ionic-angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the SettingsPage page.
|
||||||
|
*
|
||||||
|
* See http://ionicframework.com/docs/components/#navigation for more info
|
||||||
|
* on Ionic pages and navigation.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-settings',
|
||||||
|
templateUrl: 'settings.html',
|
||||||
|
})
|
||||||
|
export class SettingsPage {
|
||||||
|
|
||||||
|
private set_unified: boolean;
|
||||||
|
private set_cc: boolean;
|
||||||
|
private set_default: string;
|
||||||
|
private set_hideimg: boolean;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, private storage: Storage, public navParams: NavParams,
|
||||||
|
public events: Events) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.storage.get('timeline').then((val) => {
|
||||||
|
this.set_default = val;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' +err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storage.get('unified').then((val) => {
|
||||||
|
this.set_unified = val;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' + err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storage.get('cc').then((val) => {
|
||||||
|
this.set_cc = val;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' + err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storage.get('hideimg').then((val) => {
|
||||||
|
this.set_hideimg = val;
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidLeave() {
|
||||||
|
this.events.publish('stream:reload', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUnified() {
|
||||||
|
this.storage.set('unified', this.set_unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCc() {
|
||||||
|
this.storage.set('cc', this.set_cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDefault() {
|
||||||
|
this.storage.set('timeline', this.set_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHideImg() {
|
||||||
|
this.storage.set('hideimg', this.set_hideimg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,20 +1,48 @@
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>New Post</ion-title>
|
|
||||||
<ion-buttons start>
|
<ion-buttons start>
|
||||||
<button ion-button (click)="dismiss()">
|
<button ion-button (click)="dismiss()">
|
||||||
<span ion-text color="primary" showWhen="ios">Cancel</span>
|
<span ion-text color="primary">Cancel</span>
|
||||||
<ion-icon name="md-close" showWhen="android,windows"></ion-icon>
|
|
||||||
</button>
|
</button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-buttons end>
|
|
||||||
<button ion-button>
|
<ion-title>{{ title }}</ion-title>
|
||||||
<span ion-text color="primary" showWhen="ios">Post</span>
|
|
||||||
<ion-icon name="send" showWhen="android,windows"></ion-icon>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<p>Blarp</p>
|
<ion-card>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-textarea [(ngModel)]="ptext" autocomplete="true" spellcheck="true" clearInput="true" rows="8"></ion-textarea>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card-content>
|
||||||
|
<ion-row justify-content-end>
|
||||||
|
<ion-col offset-0>
|
||||||
|
<!-- <button ion-button (click)="attachImage()">
|
||||||
|
<ion-icon name="attach"></ion-icon>
|
||||||
|
</button> -->
|
||||||
|
</ion-col>
|
||||||
|
<ion-col col-2><div text-center>{{textCount()}}</div></ion-col>
|
||||||
|
<ion-col col-2>
|
||||||
|
<button ion-button (click)="send()">
|
||||||
|
<ion-icon name="send"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<progress id='p' *ngIf="showProgress"></progress>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let f of files">
|
||||||
|
<ion-thumbnail item-start>
|
||||||
|
<img src="{{ f.link }}">
|
||||||
|
</ion-thumbnail>
|
||||||
|
<p>{{ f.name }}</p>
|
||||||
|
<button ion-button color="dark" clear item-end (click)="unattach(f.id)">
|
||||||
|
<ion-icon name="remove-circle"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
195
src/pages/stream/new-post.ts
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ViewController, NavParams, ToastController, Events } from 'ionic-angular';
|
||||||
|
import { Storage } from '@ionic/storage';
|
||||||
|
import { FileChooser } from '@ionic-native/file-chooser';
|
||||||
|
import { FilePath } from '@ionic-native/file-path';
|
||||||
|
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'modal-newpost',
|
||||||
|
templateUrl: 'new-post.html',
|
||||||
|
})
|
||||||
|
export class NewPostModal {
|
||||||
|
title: string;
|
||||||
|
replyid: string;
|
||||||
|
ptext: string = "";
|
||||||
|
showProgress: boolean = false;
|
||||||
|
files: Array<Object> = [];
|
||||||
|
fname: string = "";
|
||||||
|
fpath: string = "";
|
||||||
|
options: Object = {};
|
||||||
|
myUsername: string;
|
||||||
|
authToken: string;
|
||||||
|
longpost: Object = {};
|
||||||
|
raw: {type: string, value: Object}[] = [];
|
||||||
|
|
||||||
|
constructor(public navParams: NavParams, public viewCtrl: ViewController, public toastCtrl: ToastController,
|
||||||
|
private fileChooser: FileChooser, private storage: Storage, public events: Events, private filePath: FilePath,
|
||||||
|
private transfer: FileTransfer) {
|
||||||
|
console.log(JSON.stringify(this.navParams));
|
||||||
|
this.myUsername = navParams.data.me;
|
||||||
|
if (navParams.data.type === 'reply') {
|
||||||
|
this.replyid = navParams.data.post.id;
|
||||||
|
this.options = {replyTo: this.replyid};
|
||||||
|
if (navParams.data.post.user.username !== this.myUsername) {
|
||||||
|
this.ptext = "@" + navParams.data.post.user.username + " ";
|
||||||
|
} else {
|
||||||
|
this.ptext = ""
|
||||||
|
}
|
||||||
|
if (navParams.data.post.content.entities) {
|
||||||
|
if (navParams.data.post.content.entities.mentions.length > 0) {
|
||||||
|
this.ptext = this.ptext + this.parseMentions(navParams.data.post.content.entities.mentions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.title = "Reply to " + navParams.data.post.user.username
|
||||||
|
} else if (navParams.data.type === 'quote') {
|
||||||
|
this.ptext = " >> @" + navParams.data.post.user.username + ": " + navParams.data.post.content.text;
|
||||||
|
this.title = "New Post";
|
||||||
|
} else {
|
||||||
|
this.title = "New Post";
|
||||||
|
}
|
||||||
|
this.storage.get('token').then((val) => {
|
||||||
|
this.authToken = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
send() {
|
||||||
|
|
||||||
|
if (this.ptext.length > 254) {
|
||||||
|
this.longpost = {
|
||||||
|
'title': '',
|
||||||
|
'body': this.ptext,
|
||||||
|
'tstamp': new Date().valueOf()
|
||||||
|
}
|
||||||
|
this.ptext = this.ptext.substr(0, 40) + "... - http://chimpnut.nl/u/";
|
||||||
|
this.ptext = this.ptext + this.navParams.data.me + "/lp/{object_id} - #longpost";
|
||||||
|
this.raw.push({
|
||||||
|
type: "nl.chimpnut.blog.post",
|
||||||
|
value: this.longpost
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options['raw'] = this.raw;
|
||||||
|
pnut.createPost(this.ptext, this.options).then(res => {
|
||||||
|
console.log('-success-');
|
||||||
|
console.log(JSON.stringify(res));
|
||||||
|
this.presentToast("Status posted.");
|
||||||
|
this.events.publish('stream:reload', {});
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('-error posting-');
|
||||||
|
console.log(JSON.stringify(err));
|
||||||
|
});
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMentions(mentions) {
|
||||||
|
let mtext = ""
|
||||||
|
for(var i = 0; i < mentions.length; i++) {
|
||||||
|
let mu = mentions[i].text;
|
||||||
|
if (mu !== this.myUsername) {
|
||||||
|
mtext += "@" + mu + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mtext.length > 0) {
|
||||||
|
if (this.navParams.data.cc) {
|
||||||
|
mtext = "\n/" + mtext;
|
||||||
|
}
|
||||||
|
return mtext;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textCount() {
|
||||||
|
let counttext = ""
|
||||||
|
let count = 254 - this.ptext.length
|
||||||
|
if (count < 1) {
|
||||||
|
counttext = "longpost"
|
||||||
|
} else {
|
||||||
|
counttext = String(count)
|
||||||
|
}
|
||||||
|
return counttext
|
||||||
|
}
|
||||||
|
|
||||||
|
attachImage() {
|
||||||
|
// console.log('file chooser');
|
||||||
|
const fileTransfer: FileTransferObject = this.transfer.create();
|
||||||
|
|
||||||
|
this.fileChooser.open().then(uri => {
|
||||||
|
console.log('File URI: ' + uri);
|
||||||
|
this.filePath.resolveNativePath(uri).then(filePath => {
|
||||||
|
this.fpath = filePath;
|
||||||
|
this.fname = filePath.split('/').pop();
|
||||||
|
|
||||||
|
let options: FileUploadOptions = {
|
||||||
|
fileKey: 'content',
|
||||||
|
fileName: this.fname,
|
||||||
|
params: {
|
||||||
|
name: this.fname,
|
||||||
|
type: 'com.monkeystew.goober_m',
|
||||||
|
is_public: true
|
||||||
|
},
|
||||||
|
headers: {'Authorization': 'Bearer ' + this.authToken}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showProgress = true;
|
||||||
|
fileTransfer.upload(this.fpath, 'https://api.pnut.io/v0/files', options).then((response) => {
|
||||||
|
|
||||||
|
let rdata = JSON.parse(response.response);
|
||||||
|
let oembed = {
|
||||||
|
'+io.pnut.core.file': {
|
||||||
|
file_id: rdata.data.id,
|
||||||
|
file_token: rdata.data.file_token,
|
||||||
|
format: 'oembed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(oembed));
|
||||||
|
this.raw.push({
|
||||||
|
type: "io.pnut.core.oembed",
|
||||||
|
value: oembed
|
||||||
|
});
|
||||||
|
this.files.push({
|
||||||
|
name: this.fname,
|
||||||
|
link: rdata.data.link,
|
||||||
|
id: this.raw.length - 1
|
||||||
|
});
|
||||||
|
this.showProgress = false;
|
||||||
|
}).catch((err) => { // fileTransfer
|
||||||
|
|
||||||
|
this.showProgress = false;
|
||||||
|
let edata = JSON.parse(err.body);
|
||||||
|
this.presentToast(edata.meta.error_message);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(err => { // filePath
|
||||||
|
console.log('-error getting filepath-');
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(err => { // fileChooser
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unattach(id) {
|
||||||
|
console.log('removing item ' + id);
|
||||||
|
this.raw.splice(id, 1);
|
||||||
|
this.files.splice(id, 1);
|
||||||
|
}
|
||||||
|
}
|
106
src/pages/stream/post-menu.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ViewController, NavParams, ToastController } from 'ionic-angular';
|
||||||
|
import { Events } from 'ionic-angular';
|
||||||
|
import { Device } from '@ionic-native/device';
|
||||||
|
import { ClipboardService } from 'ngx-clipboard';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ion-list>
|
||||||
|
<button ion-item *ngIf="showShareBtn" (click)="share()">Share</button>
|
||||||
|
<button ion-item (click)="browse()">Open in Browser</button>
|
||||||
|
<button ion-item (click)="copy()">Copy to clipboard</button>
|
||||||
|
<button ion-item (click)="copyPostURL()">Copy link to post</button>
|
||||||
|
<button ion-item *ngIf="showDelBtn" (click)="delete()">Delete</button>
|
||||||
|
</ion-list>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class PostMenu {
|
||||||
|
|
||||||
|
showDelBtn: boolean = false;
|
||||||
|
showShareBtn: boolean = false;
|
||||||
|
postURL: string;
|
||||||
|
|
||||||
|
constructor(public navParams: NavParams, public viewCtrl: ViewController, public toastCtrl: ToastController,
|
||||||
|
public events: Events, private clipboardSrv: ClipboardService, private device: Device) {
|
||||||
|
this.postURL = 'https://posts.pnut.io/' + this.navParams.data.post.id;
|
||||||
|
if (navParams.data.me == navParams.data.post.user.username) {
|
||||||
|
this.showDelBtn = true;
|
||||||
|
} else {
|
||||||
|
this.showDelBtn = false;
|
||||||
|
}
|
||||||
|
if (this.device.platform === "Android" || this.device.platform === "amazon-fireos") {
|
||||||
|
this.showShareBtn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browse() {
|
||||||
|
window.open(this.postURL, '_system');
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
(<any>window).shareContentPlugin.share(this.parsePost(), function(e) {
|
||||||
|
console.log('sharing post:');
|
||||||
|
console.log(JSON.stringify(e));
|
||||||
|
}, function(e) {
|
||||||
|
console.log('sharing failed:');
|
||||||
|
console.log(JSON.stringify(e));
|
||||||
|
});
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
this.clipboardSrv.copyFromContent(this.parsePost());
|
||||||
|
this.presentToast('Post copied');
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePost() {
|
||||||
|
let text = this.navParams.data.post.content.text;
|
||||||
|
let links = this.navParams.data.post.content.entities.links;
|
||||||
|
if (links.length > 0) {
|
||||||
|
for (var i = 0; i < links.length; i++) {
|
||||||
|
text += "\n";
|
||||||
|
if (typeof links[i].title !== "undefined") {
|
||||||
|
text += links[i].title + " - ";
|
||||||
|
}
|
||||||
|
text += links[i].link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyPostURL() {
|
||||||
|
this.clipboardSrv.copyFromContent(this.postURL);
|
||||||
|
this.presentToast('Post link copied');
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
pnut.deletePost(this.navParams.data.post.id).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.presentToast('Post Deleted');
|
||||||
|
this.events.publish('stream:reload', {});
|
||||||
|
}).catch( err => {
|
||||||
|
console.log('-error-');
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,72 +16,15 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
<ion-content>
|
<ion-content overflow-scroll=”true”>
|
||||||
|
|
||||||
<ion-refresher (ionRefresh)="fetchNewerPosts($event)">
|
<ion-refresher (ionRefresh)="fetchNewerPosts($event)">
|
||||||
<ion-refresher-content></ion-refresher-content>
|
<ion-refresher-content></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-card *ngFor="let post of posts">
|
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||||
<ion-item>
|
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||||
<ion-avatar item-start>
|
|
||||||
<img src="{{ post.user.content.avatar_image.link }}">
|
|
||||||
</ion-avatar>
|
|
||||||
<h2>{{ post.user.name }}</h2>
|
|
||||||
<p>@{{ post.user.username }}</p>
|
|
||||||
<ion-note item-end>
|
|
||||||
<div text-right>
|
|
||||||
{{ post.created_at | timeago }}<br/>
|
|
||||||
{{ post.source.name }}
|
|
||||||
</div>
|
|
||||||
</ion-note>
|
|
||||||
</ion-item>
|
|
||||||
<ion-card-content>
|
|
||||||
<p>{{ post.is_deleted ? '{post deleted}' : post.content.text }}</p>
|
|
||||||
</ion-card-content>
|
|
||||||
<div *ngIf="post.raw">
|
|
||||||
<div *ngFor="let r of post.raw">
|
|
||||||
<div *ngIf="r.type == 'io.pnut.core.oembed'">
|
|
||||||
<img src="{{ r.value.url }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="showReplyPost(post)">
|
|
||||||
<ion-icon name="ios-undo"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="showQuotedPost(post)">
|
|
||||||
<ion-icon name="quote"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="bookmark(post.id, post.you_bookmarked)">
|
|
||||||
<ion-icon name="star"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.bookmarks > 0">{{ post.counts.bookmarks }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="repost(post.id, post.you_reposted)">
|
|
||||||
<ion-icon name="repeat"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.reposts > 0">{{ post.counts.reposts }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="fetchThread(post.thread_id)">
|
|
||||||
<ion-icon name="chatboxes"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.replies > 0">{{ post.counts.replies }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<!-- <ion-col>
|
|
||||||
<button ion-button icon-left clear small block>
|
|
||||||
<ion-icon name="more"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col> -->
|
|
||||||
</ion-row>
|
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
@ -95,4 +38,10 @@
|
||||||
</button>
|
</button>
|
||||||
</ion-fab>
|
</ion-fab>
|
||||||
|
|
||||||
|
<ion-fab *ngIf="showScrollBtn" left bottom>
|
||||||
|
<button ion-fab color="light" (click)="scrollToTop()">
|
||||||
|
<ion-icon name="arrow-dropup"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
page-stream {
|
page-stream {
|
||||||
.item-md ion-avatar img {
|
.item-md ion-avatar img {
|
||||||
border-radius: 0;
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-wp ion-avatar img {
|
.item-wp ion-avatar img {
|
||||||
border-radius: 0;
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-ios ion-avatar img {
|
.item-ios ion-avatar img {
|
||||||
border-radius: 0;
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modal-newpost {
|
||||||
|
.row {
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
import { ViewController, NavController, NavParams, ModalController } from 'ionic-angular';
|
import { NavController, NavParams, ModalController, Content } from 'ionic-angular';
|
||||||
import { ThreadPage } from '../thread/thread';
|
import { Storage } from '@ionic/storage';
|
||||||
|
import { Events } from 'ionic-angular';
|
||||||
|
import { LoginPage } from '../login/login';
|
||||||
|
import { NewPostModal } from '../stream/new-post';
|
||||||
|
|
||||||
import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@ -16,32 +19,111 @@ import * as pnut from 'pnut-butter';
|
||||||
})
|
})
|
||||||
export class StreamPage {
|
export class StreamPage {
|
||||||
|
|
||||||
|
@ViewChild(Content) content: Content;
|
||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
posts: Array<Object> = [];
|
posts: Array<Object> = [];
|
||||||
since_id: string;
|
since_id: string;
|
||||||
before_id: string;
|
before_id: string;
|
||||||
fetcher: any;
|
fetcher: any;
|
||||||
|
fcaller: any;
|
||||||
|
myUsername: string;
|
||||||
|
showScrollBtn: boolean = false;
|
||||||
|
showUnified: boolean;
|
||||||
|
ccOnReply: boolean = false;
|
||||||
|
hideImg: boolean = false;
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController) {
|
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||||
// console.log(JSON.stringify(navParams));
|
public modalCtrl: ModalController, private storage: Storage,
|
||||||
switch (navParams.data.stream) {
|
private changeDetectorRef: ChangeDetectorRef, public events: Events) {
|
||||||
case 'global':
|
|
||||||
this.title = 'Global';
|
this.storage.get('cc').then((val) => {
|
||||||
this.fetcher = pnut.global;
|
this.ccOnReply = val;
|
||||||
this.fetchPosts();
|
});
|
||||||
break;
|
|
||||||
case 'personal':
|
this.storage.get('hideimg').then((val) => {
|
||||||
this.title = 'Home';
|
this.hideImg = val;
|
||||||
this.fetcher = pnut.personal;
|
});
|
||||||
this.fetchPosts();
|
|
||||||
break;
|
this.storage.get('unified').then((val) => {
|
||||||
}
|
this.showUnified = val;
|
||||||
|
|
||||||
|
switch (navParams.data.stream) {
|
||||||
|
case 'global':
|
||||||
|
this.title = 'Global';
|
||||||
|
this.fetcher = pnut.global;
|
||||||
|
this.fetchPosts();
|
||||||
|
break;
|
||||||
|
case 'personal':
|
||||||
|
this.title = 'Timeline';
|
||||||
|
console.log(this.showUnified);
|
||||||
|
this.fetcher = this.showUnified ? pnut.unified : pnut.personal;
|
||||||
|
this.fetchPosts();
|
||||||
|
break;
|
||||||
|
case 'mentions':
|
||||||
|
this.title = 'Mentions';
|
||||||
|
this.fetcher = pnut.mentions;
|
||||||
|
this.fetchMyPosts();
|
||||||
|
break;
|
||||||
|
case 'bookmarks':
|
||||||
|
this.title = 'Bookmarks';
|
||||||
|
this.fetcher = pnut.bookmarks;
|
||||||
|
this.fetchMyPosts();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('ERROR: ' + err);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
pnut.user('me').then(res => {
|
||||||
|
this.myUsername = res.data.username;
|
||||||
|
}).catch(err => {
|
||||||
|
// console.log('-*-');
|
||||||
|
// console.log(JSON.stringify(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.events.subscribe('stream:reload', (event) => {
|
||||||
|
console.log('-reload-');
|
||||||
|
this.refreshPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.content.ionScroll.subscribe((event) => {
|
||||||
|
// console.log('scrolling ', event);
|
||||||
|
if (event.scrollTop > 0) {
|
||||||
|
this.showScrollBtn = true;
|
||||||
|
} else {
|
||||||
|
this.showScrollBtn = false;
|
||||||
|
}
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPage() {
|
||||||
|
// this.navCtrl.setRoot(this.navCtrl.getActive().component);
|
||||||
|
this.navCtrl.setRoot(StreamPage, {stream: 'personal'});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchOlderPosts(infiniteScroll) {
|
fetchOlderPosts(infiniteScroll) {
|
||||||
this.fetcher({include_raw: 1, before_id: this.before_id, count: 40}).then(res => {
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
include_reposted_by:1,
|
||||||
|
before_id: this.before_id,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
if (this.title === 'Mentions') {
|
||||||
|
this.fcaller = this.fetcher('me', params);
|
||||||
|
} else {
|
||||||
|
this.fcaller = this.fetcher(params);
|
||||||
|
}
|
||||||
|
this.fcaller.then(res => {
|
||||||
if (res.data.length > 0) {
|
if (res.data.length > 0) {
|
||||||
this.posts.push.apply(this.posts, res.data);
|
this.posts.push.apply(this.posts, this.parseData(res.data));
|
||||||
this.before_id = res.meta.min_id;
|
this.before_id = res.meta.min_id;
|
||||||
}
|
}
|
||||||
console.log('since_id: ' + this.since_id);
|
console.log('since_id: ' + this.since_id);
|
||||||
|
@ -53,9 +135,21 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNewerPosts(refresher) {
|
fetchNewerPosts(refresher) {
|
||||||
this.fetcher({include_raw: 1, since_id: this.since_id, count: 40}).then(res => {
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
include_reposted_by: 1,
|
||||||
|
since_id: this.since_id,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
if (this.title === 'Mentions') {
|
||||||
|
this.fcaller = this.fetcher('me', params);
|
||||||
|
} else {
|
||||||
|
this.fcaller = this.fetcher(params);
|
||||||
|
}
|
||||||
|
this.fcaller.then(res => {
|
||||||
if (res.data.length > 0) {
|
if (res.data.length > 0) {
|
||||||
Array.prototype.unshift.apply(this.posts, res.data);
|
Array.prototype.unshift.apply(this.posts, this.parseData(res.data));
|
||||||
this.since_id = res.meta.max_id;
|
this.since_id = res.meta.max_id;
|
||||||
}
|
}
|
||||||
console.log('since_id: ' + this.since_id);
|
console.log('since_id: ' + this.since_id);
|
||||||
|
@ -67,176 +161,76 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchPosts() {
|
fetchPosts() {
|
||||||
console.log('-- fetching global stream --');
|
let params = {
|
||||||
this.fetcher({include_raw: 1, count: 40}).then(res => {
|
include_deleted: 0,
|
||||||
this.posts = res.data;
|
include_raw: 1,
|
||||||
this.since_id = res.meta.max_id;
|
include_reposted_by: 1,
|
||||||
this.before_id = res.meta.min_id;
|
count: 40
|
||||||
console.log('since_id: ' + this.since_id);
|
};
|
||||||
console.log('before_id: ' + this.before_id);
|
this.fetcher(params).then(res => {
|
||||||
}).catch(err => {
|
if (res.meta.code === 401) {
|
||||||
console.log(err);
|
this.storage.clear();
|
||||||
});
|
this.navCtrl.setRoot(LoginPage);
|
||||||
}
|
} else {
|
||||||
|
this.posts = this.parseData(res.data);
|
||||||
fetchThread(threadid) {
|
this.since_id = res.meta.max_id;
|
||||||
pnut.thread(threadid).then(res => {
|
this.before_id = res.meta.min_id;
|
||||||
this.navCtrl.push(ThreadPage, {posts: res.data});
|
console.log('since_id: ' + this.since_id);
|
||||||
}).catch(err => {
|
console.log('before_id: ' + this.before_id);
|
||||||
console.log(err);
|
}
|
||||||
});
|
|
||||||
}
|
});
|
||||||
|
}
|
||||||
bookmark(postid, bookmarked) {
|
|
||||||
if (bookmarked) {
|
parseData(data) {
|
||||||
pnut.deleteBookmark(postid).then(res => {
|
var pdata = [];
|
||||||
console.log(res);
|
for (var i = 0; i < data.length; i++) {
|
||||||
this.updatePost(res.data.id);
|
if (!data[i].is_deleted) {
|
||||||
}).catch(err => {
|
if (data[i]['repost_of']) {
|
||||||
console.log(err);
|
data[i] = data[i]['repost_of']
|
||||||
});
|
var reposted_by_string = "";
|
||||||
} else {
|
for (var j = 0; j < data[i]['reposted_by'].length; j++) {
|
||||||
pnut.bookmark(postid).then(res => {
|
reposted_by_string = reposted_by_string + data[i]['reposted_by'][j]['username'] + ", ";
|
||||||
console.log(res);
|
}
|
||||||
this.updatePost(res.data.id);
|
// data[i]['reposted_by_string'] = "Reposted by: " + reposted_by_string;
|
||||||
}).catch(err => {
|
}
|
||||||
console.log(err);
|
if (data[i].content) {
|
||||||
});
|
for (var k = 0; k < data[i]['content']['entities']['mentions'].length; k++) {
|
||||||
}
|
var men = data[i]['content']['entities']['mentions'][k]['text'];
|
||||||
}
|
if (this.myUsername === men) {
|
||||||
|
data[i]['you_are_mentioned'] = true;
|
||||||
repost(postid, reposted) {
|
}
|
||||||
if (reposted) {
|
}
|
||||||
pnut.deleteRepost(postid).then(res => {
|
}
|
||||||
console.log(res);
|
pdata.push(data[i]);
|
||||||
this.updatePost(res.data.id);
|
}
|
||||||
}).catch(err => {
|
}
|
||||||
console.log(err);
|
return pdata;
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
pnut.repost(postid).then(res => {
|
fetchMyPosts() {
|
||||||
console.log(res);
|
console.log('-- fetching mentions --');
|
||||||
this.updatePost(res.data.id);
|
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
||||||
}).catch(err => {
|
if (res.meta.code === 401) {
|
||||||
console.log(err);
|
this.storage.clear();
|
||||||
});
|
this.navCtrl.setRoot(LoginPage);
|
||||||
}
|
} else {
|
||||||
}
|
this.posts = res.data;
|
||||||
|
this.since_id = res.meta.max_id;
|
||||||
updatePost(postid) {
|
this.before_id = res.meta.min_id;
|
||||||
pnut.post(postid, {include_raw: 1}).then(res => {
|
console.log('since_id: ' + this.since_id);
|
||||||
for (var i = 0; i < this.posts.length; i++) {
|
console.log('before_id: ' + this.before_id);
|
||||||
if (this.posts[i]['id'] === postid) {
|
|
||||||
this.posts[i] = res.data;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showNewPost() {
|
showNewPost() {
|
||||||
let newPostModal = this.modalCtrl.create(NewPostModal);
|
let newPostModal = this.modalCtrl.create(NewPostModal, {me: this.myUsername});
|
||||||
newPostModal.present();
|
newPostModal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
showReplyPost(postData) {
|
scrollToTop() {
|
||||||
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'reply', post: postData});
|
this.content.scrollToTop();
|
||||||
newPostModal.present();
|
|
||||||
}
|
|
||||||
|
|
||||||
showQuotedPost(postData) {
|
|
||||||
console.log(postData);
|
|
||||||
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'quote', post: postData});
|
|
||||||
newPostModal.present();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
|
||||||
// templateUrl: 'new-post.html'
|
|
||||||
template: `
|
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
|
|
||||||
<ion-buttons start>
|
|
||||||
<button ion-button (click)="dismiss()">
|
|
||||||
<span ion-text color="primary">Cancel</span>
|
|
||||||
</button>
|
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
<ion-title>{{ title }}</ion-title>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ion-card>
|
|
||||||
<ion-card-content>
|
|
||||||
<ion-textarea [(ngModel)]="ptext"></ion-textarea>
|
|
||||||
</ion-card-content>
|
|
||||||
<ion-row justify-content-end>
|
|
||||||
<ion-col col-2>
|
|
||||||
<button ion-button (click)="send()">
|
|
||||||
<ion-icon name="send"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-card>
|
|
||||||
</ion-content>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class NewPostModal {
|
|
||||||
title: string;
|
|
||||||
replyid: string;
|
|
||||||
ptext: string;
|
|
||||||
options: Object;
|
|
||||||
|
|
||||||
constructor(public navParams: NavParams, public viewCtrl: ViewController) {
|
|
||||||
console.log(this.navParams);
|
|
||||||
if (navParams.data.type === 'reply') {
|
|
||||||
this.replyid = navParams.data.post.id;
|
|
||||||
this.options = {replyTo: this.replyid};
|
|
||||||
this.ptext = "@" + navParams.data.post.user.username + " ";
|
|
||||||
if (navParams.data.post.content.entities) {
|
|
||||||
if (navParams.data.post.content.entities.mentions) {
|
|
||||||
this.ptext = this.ptext + this.parseMentions(navParams.data.post.content.entities.mentions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.title = "Reply to " + navParams.data.post.user.username
|
|
||||||
} else if (navParams.data.type === 'quote') {
|
|
||||||
this.ptext = " >> @" + navParams.data.post.user.username + ": " + navParams.data.post.content.text;
|
|
||||||
this.title = "New Post";
|
|
||||||
} else {
|
|
||||||
this.title = "New Post";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss() {
|
|
||||||
this.viewCtrl.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
send() {
|
|
||||||
console.log(this.ptext);
|
|
||||||
pnut.createPost(this.ptext, this.options).then(res => {
|
|
||||||
console.log(res);
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
this.viewCtrl.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
parseMentions(mentions) {
|
|
||||||
let mtext = ""
|
|
||||||
for(var i = 0; i < mentions.length; i++) {
|
|
||||||
let mu = mentions[i].text;
|
|
||||||
mtext += "@" + mu + " ";
|
|
||||||
}
|
|
||||||
if (mtext.length > 0) {
|
|
||||||
return mtext;
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,69 +15,11 @@
|
||||||
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-card *ngFor="let post of posts">
|
<ion-card *ngFor="let post of posts">
|
||||||
<ion-item>
|
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||||
<ion-avatar item-start>
|
|
||||||
<img src="{{ post.user.content.avatar_image.link }}">
|
|
||||||
</ion-avatar>
|
|
||||||
<h2>{{ post.user.name }}</h2>
|
|
||||||
<p>@{{ post.user.username }}</p>
|
|
||||||
<ion-note item-end right>
|
|
||||||
<div text-right>
|
|
||||||
{{ post.created_at | timeago }}<br/>
|
|
||||||
{{ post.source.name }}
|
|
||||||
</div>
|
|
||||||
</ion-note>
|
|
||||||
</ion-item>
|
|
||||||
<ion-card-content>
|
|
||||||
<p>{{ post.is_deleted ? '{post deleted}' : post.content.text }}</p>
|
|
||||||
</ion-card-content>
|
|
||||||
<div *ngIf="post.raw">
|
|
||||||
<div *ngFor="let r of post.raw">
|
|
||||||
<div *ngIf="r.type == 'io.pnut.core.oembed'">
|
|
||||||
<img src="{{ r.value.url }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="showReplyPost(post)">
|
|
||||||
<ion-icon name="text"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="showQuotedPost(post)">
|
|
||||||
<ion-icon name="quote"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="bookmark(post.id, post.you_bookmarked)">
|
|
||||||
<ion-icon name="star"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.bookmarks > 0">{{ post.counts.bookmarks }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<ion-col>
|
|
||||||
<button ion-button icon-left clear small block (click)="repost(post.id, post.you_reposted)">
|
|
||||||
<ion-icon name="repeat"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.reposts > 0">{{ post.counts.reposts }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col>
|
|
||||||
<!-- <ion-col>
|
|
||||||
<button ion-button icon-left clear small block>
|
|
||||||
<ion-icon name="chatboxes"></ion-icon>
|
|
||||||
<div *ngIf="post.counts.replies > 0">{{ post.counts.replies }}</div>
|
|
||||||
</button>
|
|
||||||
</ion-col> -->
|
|
||||||
<!-- <ion-col>
|
|
||||||
<button ion-button icon-left clear small block>
|
|
||||||
<ion-icon name="more"></ion-icon>
|
|
||||||
</button>
|
|
||||||
</ion-col> -->
|
|
||||||
</ion-row>
|
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
page-thread {
|
page-thread {
|
||||||
|
.item-md ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-wp ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-ios ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavController, NavParams, ModalController } from 'ionic-angular';
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
import { NewPostModal } from '../stream/stream';
|
import { Storage } from '@ionic/storage';
|
||||||
|
|
||||||
// import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated class for the ThreadPage page.
|
* Generated class for the ThreadPage page.
|
||||||
|
@ -18,20 +18,23 @@ export class ThreadPage {
|
||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
posts: Array<Object> = [];
|
posts: Array<Object> = [];
|
||||||
|
myUsername: string;
|
||||||
|
hideImg: boolean = false;
|
||||||
|
ccOnReply: boolean = false;
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController) {
|
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||||
|
private storage: Storage) {
|
||||||
this.posts = this.navParams.data.posts;
|
this.posts = this.navParams.data.posts;
|
||||||
}
|
this.myUsername = this.navParams.data.me;
|
||||||
|
|
||||||
showReplyPost(postData) {
|
this.storage.get('hideimg').then((val) => {
|
||||||
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'reply', post: postData});
|
this.hideImg = val;
|
||||||
newPostModal.present();
|
});
|
||||||
}
|
|
||||||
|
this.storage.get('cc').then((val) => {
|
||||||
|
this.ccOnReply = val;
|
||||||
|
});
|
||||||
|
|
||||||
showQuotedPost(postData) {
|
|
||||||
console.log(postData);
|
|
||||||
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'quote', post: postData});
|
|
||||||
newPostModal.present();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
35
src/pages/user-list/user-list.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<!--
|
||||||
|
Generated template for the UserListPage page.
|
||||||
|
|
||||||
|
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
Ionic pages and navigation.
|
||||||
|
-->
|
||||||
|
<ion-header>
|
||||||
|
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ list }}</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-list *ngFor="let user of users">
|
||||||
|
<ion-item (click)="showProfile(user)">
|
||||||
|
<ion-avatar item-start>
|
||||||
|
<img src="{{ user.content.avatar_image.link }}">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>{{ user.name }}</h2>
|
||||||
|
<p>@{{ user.username }}</p>
|
||||||
|
<!-- <ion-col item-end text-right>
|
||||||
|
<button ion-button disabled>{{ user.you_follow ? "Unfollow" : "Follow" }}</button>
|
||||||
|
<ion-note>{{ user.follows_you ? "Follows you" : ""}}</ion-note>
|
||||||
|
</ion-col> -->
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-infinite-scroll (ionInfinite)="fetchMoreUsers($event)">
|
||||||
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
|
</ion-infinite-scroll>
|
||||||
|
|
||||||
|
</ion-content>
|
14
src/pages/user-list/user-list.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
page-user-list {
|
||||||
|
.item-md ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-wp ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
.item-ios ion-avatar img {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
}
|
71
src/pages/user-list/user-list.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavController, NavParams } from 'ionic-angular';
|
||||||
|
import { ProfilePage } from '../../pages/profile/profile';
|
||||||
|
|
||||||
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the UserListPage page.
|
||||||
|
*
|
||||||
|
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||||
|
* Ionic pages and navigation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-user-list',
|
||||||
|
templateUrl: 'user-list.html',
|
||||||
|
})
|
||||||
|
export class UserListPage {
|
||||||
|
|
||||||
|
private list: string;
|
||||||
|
private userid: string;
|
||||||
|
private users: Array<Object> = [];
|
||||||
|
private myUsername: string;
|
||||||
|
private before_id: string;
|
||||||
|
private fetcher: any;
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public navParams: NavParams) {
|
||||||
|
this.list = this.navParams.data.list;
|
||||||
|
this.userid = this.navParams.data.userid;
|
||||||
|
this.myUsername = this.navParams.data.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidLoad() {
|
||||||
|
console.log('ionViewDidLoad UserListPage');
|
||||||
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
if (this.list == "Followers") {
|
||||||
|
this.fetcher = pnut.followers;
|
||||||
|
} else if (this.list == "Following") {
|
||||||
|
this.fetcher = pnut.following;
|
||||||
|
}
|
||||||
|
this.fetcher(this.userid, params).then(res => {
|
||||||
|
this.users = res.data;
|
||||||
|
this.before_id = res.meta.min_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showProfile(user) {
|
||||||
|
this.navCtrl.push(ProfilePage, {user: user, me: this.myUsername});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMoreUsers(infiniteScroll) {
|
||||||
|
let params = {
|
||||||
|
include_deleted: 0,
|
||||||
|
include_raw: 1,
|
||||||
|
before_id: this.before_id,
|
||||||
|
count: 40
|
||||||
|
};
|
||||||
|
this.fetcher(this.userid, params).then(res => {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
this.users.push.apply(this.users, res.data);
|
||||||
|
this.before_id = res.meta.min_id;
|
||||||
|
}
|
||||||
|
infiniteScroll.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
src/pipes/parser/parser.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import {DomSanitizer, SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated class for the ParserPipe pipe.
|
||||||
|
*
|
||||||
|
* See https://angular.io/docs/ts/latest/guide/pipes.html for more info on
|
||||||
|
* Angular Pipes.
|
||||||
|
*/
|
||||||
|
@Pipe({
|
||||||
|
name: 'parser',
|
||||||
|
})
|
||||||
|
export class ParserPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(protected _sanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
|
transform(value: string, ...args): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
|
||||||
|
let hregex = /href="([\S]+)"/g;
|
||||||
|
if (typeof value == "undefined") value = "";
|
||||||
|
value = value.replace(hregex, "onClick=\"window.open('$1', '_system', 'location=yes')\"");
|
||||||
|
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,9 +12,7 @@ import * as moment from 'moment';
|
||||||
})
|
})
|
||||||
export class TimeagoPipe implements PipeTransform {
|
export class TimeagoPipe implements PipeTransform {
|
||||||
now: any;
|
now: any;
|
||||||
/**
|
|
||||||
* Takes a value and makes it lowercase.
|
|
||||||
*/
|
|
||||||
transform(value: string, ...args) {
|
transform(value: string, ...args) {
|
||||||
this.now = moment(value).fromNow(true);
|
this.now = moment(value).fromNow(true);
|
||||||
return this.now;
|
return this.now;
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { OAuthProvider } from 'ng2-cordova-oauth/provider';
|
|
||||||
|
|
||||||
export class PnutAuth extends OAuthProvider {
|
|
||||||
|
|
||||||
protected authUrl: string = 'https://pnut.io/oauth/authenticate';
|
|
||||||
protected defaults: Object = {
|
|
||||||
responseType: 'token',
|
|
||||||
clientId: '' // Insert your client ID and rename this file to pnut-oauth.ts
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -28,7 +28,8 @@ $colors: (
|
||||||
secondary: #32db64,
|
secondary: #32db64,
|
||||||
danger: #f53d3d,
|
danger: #f53d3d,
|
||||||
light: #f4f4f4,
|
light: #f4f4f4,
|
||||||
dark: #222
|
dark: #222,
|
||||||
|
mention: #f0f0f0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
1
ubuntutouch/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build
|
1
ubuntutouch/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE
|
1
ubuntutouch/README.md
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../README.md
|
7
ubuntutouch/clickable.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"template": "pure",
|
||||||
|
"kill": "webapp-container",
|
||||||
|
"ignore": [
|
||||||
|
".git"
|
||||||
|
]
|
||||||
|
}
|
9
ubuntutouch/goober.apparmor
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"template": "ubuntu-webapp",
|
||||||
|
"policy_groups": [
|
||||||
|
"webview",
|
||||||
|
"audio",
|
||||||
|
"networking"
|
||||||
|
],
|
||||||
|
"policy_version": 16.04
|
||||||
|
}
|
7
ubuntutouch/goober.desktop
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Goober
|
||||||
|
Exec=webapp-container --app-id="goober.thrrgilag" $@ www/index.html
|
||||||
|
Icon=icon.png
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
X-Ubuntu-Touch=true
|
1
ubuntutouch/icon.png
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../resources/icon-144.png
|
15
ubuntutouch/manifest.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "goober.thrrgilag",
|
||||||
|
"description": "A pnut.io client",
|
||||||
|
"architecture": "all",
|
||||||
|
"title": "Goober",
|
||||||
|
"hooks": {
|
||||||
|
"goober": {
|
||||||
|
"apparmor": "goober.apparmor",
|
||||||
|
"desktop": "goober.desktop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "0.8.0",
|
||||||
|
"maintainer": "Morgan McMillian <gilag@monkeystew.com>",
|
||||||
|
"framework" : "ubuntu-sdk-16.04"
|
||||||
|
}
|
1
ubuntutouch/www
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../www
|