Compare commits
No commits in common. "main" and "Goober_0_2_0" have entirely different histories.
main
...
Goober_0_2
6
.gitignore
vendored
|
@ -13,7 +13,6 @@ log.txt
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.sourcemaps/
|
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
.tmp/
|
.tmp/
|
||||||
.versions/
|
.versions/
|
||||||
|
@ -23,7 +22,10 @@ node_modules/
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
hooks/
|
hooks/
|
||||||
|
platforms/
|
||||||
plugins/
|
plugins/
|
||||||
|
plugins/android.json
|
||||||
|
plugins/ios.json
|
||||||
www/
|
www/
|
||||||
$RECYCLE.BIN/
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
@ -33,5 +35,3 @@ UserInterfaceState.xcuserstate
|
||||||
|
|
||||||
# other bits
|
# other bits
|
||||||
pnut-oauth.ts
|
pnut-oauth.ts
|
||||||
src/pages/login/pnutauth.ts
|
|
||||||
platforms/
|
|
||||||
|
|
140
CHANGELOG.md
|
@ -1,140 +0,0 @@
|
||||||
# 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
|
@ -1,35 +0,0 @@
|
||||||
|
|
||||||
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,12 +1,9 @@
|
||||||
# Goober, a mobile app for pnut.io
|
# Goober, a mobile app for pnut.io
|
||||||
|
|
||||||
Copyright 2017 - 2018 Morgan McMillian
|
Copyright 2017 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
|
||||||
|
|
||||||
|
@ -36,12 +33,16 @@ 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)
|
|
||||||
* https://gradle.org/
|
#### BlackBerry 10
|
||||||
|
* 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
|
||||||
|
@ -53,15 +54,7 @@ npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Install at least one target platform
|
## Build and run
|
||||||
```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
|
||||||
|
@ -70,7 +63,7 @@ ionic serve --lab
|
||||||
|
|
||||||
#### Android
|
#### Android
|
||||||
```bash
|
```bash
|
||||||
ionic cordova platform add android
|
ionic cordova platform add android # if not yet added
|
||||||
ionic cordova build android
|
ionic cordova build android
|
||||||
ionic cordova run android
|
ionic cordova run android
|
||||||
```
|
```
|
||||||
|
|
29
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.8.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.2.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="19" />
|
<preference name="android-minSdkVersion" value="16" />
|
||||||
<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" />
|
||||||
|
@ -84,19 +84,14 @@
|
||||||
<icon height="110" src="resources/icon-110.png" width="110" />
|
<icon height="110" src="resources/icon-110.png" width="110" />
|
||||||
<icon height="144" src="resources/icon-144.png" width="144" />
|
<icon height="144" src="resources/icon-144.png" width="144" />
|
||||||
</platform>
|
</platform>
|
||||||
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
|
<engine name="android" spec="^6.2.3" />
|
||||||
<plugin name="cordova-plugin-share-content" spec="^1.0.0" />
|
<engine name="blackberry10" spec="^3.8.0" />
|
||||||
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
<engine name="windows" spec="^5.0.0" />
|
||||||
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
<plugin name="cordova-plugin-console" spec="^1.0.5" />
|
||||||
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="0.0.19" />
|
<plugin name="cordova-plugin-device" spec="^1.1.4" />
|
||||||
<plugin name="cordova-plugin-inappbrowser" spec="^2.0.2" />
|
<plugin name="cordova-plugin-inappbrowser" spec="^1.7.1" />
|
||||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
<plugin name="cordova-plugin-splashscreen" spec="^4.0.3" />
|
||||||
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
<plugin name="cordova-plugin-statusbar" spec="^2.2.2" />
|
||||||
<plugin name="cordova-plugin-device" spec="^2.0.1" />
|
<plugin name="cordova-plugin-whitelist" spec="^1.3.1" />
|
||||||
<plugin name="cordova-android-support-gradle-release" spec="^1.4.2">
|
<plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
|
||||||
<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,8 +1,5 @@
|
||||||
{
|
{
|
||||||
"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
95
package.json
|
@ -12,73 +12,58 @@
|
||||||
"ionic:serve": "ionic-app-scripts serve"
|
"ionic:serve": "ionic-app-scripts serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "5.2.9",
|
"@angular/common": "4.1.2",
|
||||||
"@angular/compiler": "5.2.9",
|
"@angular/compiler": "4.1.2",
|
||||||
"@angular/compiler-cli": "5.2.9",
|
"@angular/compiler-cli": "4.1.2",
|
||||||
"@angular/core": "5.2.9",
|
"@angular/core": "4.1.2",
|
||||||
"@angular/forms": "5.2.9",
|
"@angular/forms": "4.1.2",
|
||||||
"@angular/http": "5.2.9",
|
"@angular/http": "4.1.2",
|
||||||
"@angular/platform-browser": "5.2.9",
|
"@angular/platform-browser": "4.1.2",
|
||||||
"@angular/platform-browser-dynamic": "5.2.9",
|
"@angular/platform-browser-dynamic": "4.1.2",
|
||||||
"@ionic-native/core": "4.9.0",
|
"@ionic-native/core": "3.10.2",
|
||||||
"@ionic-native/device": "4.9.0",
|
"@ionic-native/splash-screen": "3.10.2",
|
||||||
"@ionic-native/file": "4.9.0",
|
"@ionic-native/status-bar": "3.10.2",
|
||||||
"@ionic-native/file-chooser": "4.9.0",
|
"@ionic/storage": "2.0.1",
|
||||||
"@ionic-native/file-path": "4.9.0",
|
"cordova-android": "^6.2.3",
|
||||||
"@ionic-native/file-transfer": "4.9.0",
|
"cordova-blackberry10": "^3.8.0",
|
||||||
"@ionic-native/splash-screen": "4.9.0",
|
"cordova-plugin-console": "^1.0.5",
|
||||||
"@ionic-native/status-bar": "4.9.0",
|
"cordova-plugin-device": "^1.1.4",
|
||||||
"@ionic/storage": "2.1.3",
|
"cordova-plugin-inappbrowser": "^1.7.1",
|
||||||
"com-darryncampbell-cordova-plugin-intent": "^1.1.1",
|
"cordova-plugin-splashscreen": "^4.0.3",
|
||||||
"cordova-android-support-gradle-release": "^1.4.7",
|
"cordova-plugin-statusbar": "^2.2.2",
|
||||||
"cordova-browser": "6.0.0",
|
"cordova-plugin-whitelist": "^1.3.1",
|
||||||
"cordova-plugin-device": "^2.0.2",
|
"cordova-windows": "^5.0.0",
|
||||||
"cordova-plugin-file": "^6.0.1",
|
"ionic-angular": "3.3.0",
|
||||||
"cordova-plugin-file-transfer": "^1.7.1",
|
"ionic-plugin-keyboard": "^2.2.1",
|
||||||
"cordova-plugin-filechooser": "^1.0.1",
|
"ionicons": "3.0.0",
|
||||||
"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",
|
"moment": "^2.18.1",
|
||||||
"ng2-cordova-oauth": "0.0.8",
|
"ng2-cordova-oauth": "0.0.8",
|
||||||
"ngx-clipboard": "^11.1.9",
|
"pnut-butter": "^0.8.1",
|
||||||
"pnut-butter": "^0.21.0",
|
"rxjs": "5.1.1",
|
||||||
"run": "^1.4.0",
|
|
||||||
"rxjs": "5.5.8",
|
|
||||||
"sw-toolbox": "3.6.0",
|
"sw-toolbox": "3.6.0",
|
||||||
"zone.js": "0.8.26"
|
"zone.js": "0.8.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ionic/app-scripts": "3.1.10",
|
"@ionic/app-scripts": "1.3.7",
|
||||||
"ionic": "3.20.0",
|
"@ionic/cli-plugin-cordova": "1.3.0",
|
||||||
"typescript": "2.8.3"
|
"@ionic/cli-plugin-ionic-angular": "1.3.0",
|
||||||
|
"typescript": "2.3.3"
|
||||||
},
|
},
|
||||||
"description": "An Ionic project",
|
"description": "An Ionic project",
|
||||||
"cordova": {
|
"cordova": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"cordova-plugin-share-content": {},
|
"cordova-plugin-console": {},
|
||||||
"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-plugin-device": {},
|
||||||
"cordova-android-support-gradle-release": {
|
"cordova-plugin-splashscreen": {},
|
||||||
"ANDROID_SUPPORT_VERSION": "27.+"
|
"cordova-plugin-statusbar": {},
|
||||||
},
|
"cordova-plugin-whitelist": {},
|
||||||
"cordova-plugin-filepath": {},
|
"ionic-plugin-keyboard": {},
|
||||||
"cordova-sqlite-storage": {}
|
"cordova-plugin-inappbrowser": {}
|
||||||
},
|
},
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"browser"
|
"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 |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -1 +0,0 @@
|
||||||
b72de713e098078c68cbffd6ab82439b
|
|
|
@ -1 +0,0 @@
|
||||||
7ce07128c050f41eb46a94eaddd55d4a
|
|
|
@ -1,17 +1,12 @@
|
||||||
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 { 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';
|
||||||
|
|
||||||
|
@ -22,23 +17,20 @@ export class MyApp {
|
||||||
@ViewChild(Nav) nav: Nav;
|
@ViewChild(Nav) nav: Nav;
|
||||||
|
|
||||||
rootPage: any = StreamPage;
|
rootPage: any = StreamPage;
|
||||||
pages: Array<{title: string, icon: string, component: any, params: Object}>;
|
|
||||||
profile: IUser;
|
|
||||||
|
|
||||||
constructor(public platform: Platform, public splashScreen: SplashScreen,
|
pages: Array<{title: string, component: any, params: Object}>;
|
||||||
|
|
||||||
|
constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen,
|
||||||
private storage: Storage, private device: Device) {
|
private storage: Storage, private device: Device) {
|
||||||
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: 'Timeline', icon: 'home', component: StreamPage, params: {stream: 'personal'} },
|
{ title: 'Timeline', component: StreamPage, params: {stream: 'personal'} },
|
||||||
{ title: 'Mentions', icon: 'at', component: StreamPage, params: {stream: 'mentions'} },
|
{ title: 'Global', component: StreamPage, params: {stream: 'global'} },
|
||||||
{ title: 'Global', icon: 'globe', component: StreamPage, params: {stream: 'global'} },
|
{ title: 'Mentions', component: StreamPage, params: {stream: 'mentions'} },
|
||||||
{ title: 'Bookmarks', icon: 'bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
{ title: 'Bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
||||||
{ title: 'Profile', icon: 'person', component: ProfilePage, params: {}},
|
{ title: 'Logout', component: {}, params: {}},
|
||||||
{ title: 'Settings', icon: 'settings', component: SettingsPage, params: {}},
|
|
||||||
{ title: 'About', icon: 'information-circle', component: AboutPage, params: {}},
|
|
||||||
{ title: 'Logout', icon: 'exit', component: {}, params: {}},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,65 +38,35 @@ export class MyApp {
|
||||||
initializeApp() {
|
initializeApp() {
|
||||||
console.log('--- initializeApp ---');
|
console.log('--- initializeApp ---');
|
||||||
|
|
||||||
|
|
||||||
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.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();
|
|
||||||
// console.log('---');
|
|
||||||
// console.log(this.device.platform);
|
|
||||||
// console.log('---');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initialPage(timeline) {
|
|
||||||
this.storage.get('token').then((val) => {
|
this.storage.get('token').then((val) => {
|
||||||
if (val.length > 1) {
|
if (val.length > 1) {
|
||||||
pnut.token = val;
|
pnut.token = val;
|
||||||
|
this.nav.setRoot(StreamPage, {stream: 'personal'});
|
||||||
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 => {
|
}).catch(err => {
|
||||||
console.log('ERROR: ' + err);
|
console.log('ERROR: ' + err);
|
||||||
this.nav.setRoot(LoginPage, {});
|
this.nav.setRoot(LoginPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.statusBar.styleDefault();
|
||||||
|
this.splashScreen.hide();
|
||||||
|
console.log('---');
|
||||||
|
console.log(this.device.platform);
|
||||||
|
console.log('---');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openPage(page) {
|
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
|
||||||
if (page.title === 'Logout') {
|
if (page.title === 'Logout') {
|
||||||
// this.storage.remove('token');
|
this.storage.remove('token');
|
||||||
this.storage.clear();
|
this.nav.setRoot(LoginPage);
|
||||||
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 {
|
} else {
|
||||||
this.nav.setRoot(page.component, page.params);
|
this.nav.setRoot(page.component, page.params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
<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)">
|
||||||
<ion-icon name="{{p.icon}}"></ion-icon>
|
{{p.title}}
|
||||||
<span class="menuText">{{p.title}}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
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 } from '../pages/stream/stream';
|
import { StreamPage, NewPostModal } 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 { 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 { ParserPipe } from '../pipes/parser/parser';
|
||||||
import { ClipboardModule } from 'ngx-clipboard';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -32,23 +20,14 @@ import { ClipboardModule } from 'ngx-clipboard';
|
||||||
LoginPage,
|
LoginPage,
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
SettingsPage,
|
|
||||||
AboutPage,
|
|
||||||
ProfilePage,
|
|
||||||
UserListPage,
|
|
||||||
TimeagoPipe,
|
TimeagoPipe,
|
||||||
NewPostModal,
|
NewPostModal,
|
||||||
PostMenu,
|
ParserPipe
|
||||||
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: [
|
||||||
|
@ -56,21 +35,12 @@ import { ClipboardModule } from 'ngx-clipboard';
|
||||||
LoginPage,
|
LoginPage,
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
SettingsPage,
|
NewPostModal
|
||||||
AboutPage,
|
|
||||||
ProfilePage,
|
|
||||||
UserListPage,
|
|
||||||
NewPostModal,
|
|
||||||
PostMenu,
|
|
||||||
ProfileMenu
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
StatusBar,
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
Device,
|
Device,
|
||||||
FileChooser,
|
|
||||||
FilePath,
|
|
||||||
FileTransfer,
|
|
||||||
FileTransferObject,
|
|
||||||
{provide: ErrorHandler, useClass: IonicErrorHandler}
|
{provide: ErrorHandler, useClass: IonicErrorHandler}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,8 +14,3 @@
|
||||||
// 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: 19 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -1,95 +0,0 @@
|
||||||
|
|
||||||
<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>
|
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
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,31 +2,26 @@
|
||||||
<html lang="en" dir="ltr">
|
<html lang="en" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Goober</title>
|
<title>Ionic App</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">
|
||||||
|
|
||||||
|
@ -39,8 +34,6 @@
|
||||||
<!-- 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": "Goober",
|
"name": "Ionic",
|
||||||
"short_name": "Goober",
|
"short_name": "Ionic",
|
||||||
"start_url": "index.html",
|
"start_url": "index.html",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "assets/icon.png",
|
"src": "assets/imgs/logo.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
export interface IUserAvatarImage {
|
|
||||||
is_default: boolean;
|
|
||||||
height: number;
|
|
||||||
link: string;
|
|
||||||
width: number;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
export interface IUserCoverImage {
|
|
||||||
link: string;
|
|
||||||
is_default: boolean;
|
|
||||||
width: number;
|
|
||||||
heigth: number;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
<!--
|
|
||||||
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>
|
|
|
@ -1,3 +0,0 @@
|
||||||
page-about {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -20,12 +20,17 @@
|
||||||
<h2>Goober</h2>
|
<h2>Goober</h2>
|
||||||
<p block>A mobile client for pnut.io</p>
|
<p block>A mobile client for pnut.io</p>
|
||||||
</div><p> </p>
|
</div><p> </p>
|
||||||
|
<div *ngIf="!showToken">
|
||||||
<ion-label stacked>Username</ion-label>
|
<p>Tap the Log In button to open browser window and enter your pnut.io creditionals and authorize Goober.</p>
|
||||||
<ion-input [(ngModel)]="username" type="text"></ion-input>
|
<div *ngIf="oob">
|
||||||
<ion-label stacked>Password</ion-label>
|
<p>Afterwards, copy the token provided, close the pop up window, and paste the token into the input field shown and tap Save Token.</p>
|
||||||
<ion-input [(ngModel)]="password" type="password"></ion-input>
|
</div>
|
||||||
|
<button ion-button block (click)="login()">Log In</button><p>
|
||||||
<button ion-button block (click)="login()">Log In</button>
|
</div>
|
||||||
|
<div *ngIf="showToken">
|
||||||
|
<p>Paste the token provided into this field and then tap Save Token.</p>
|
||||||
|
<ion-input [(ngModel)]="token" type="text" placeholder="Token"></ion-input>
|
||||||
|
<p><button ion-button block (click)="saveToken()">Save Token</button></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
page-login {
|
page-login {
|
||||||
user-select: auto !important;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,72 @@
|
||||||
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 { Device } from '@ionic-native/device';
|
||||||
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 username: string;
|
private oauth: any;
|
||||||
private password: string;
|
private pnutProvider: any;
|
||||||
|
private oob: boolean = false;
|
||||||
|
private showToken: boolean = false;
|
||||||
|
private token: string;
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
constructor(public navCtrl: NavController, public navParams: NavParams, private storage: Storage,
|
||||||
private storage: Storage, private http: HttpClient) {
|
private device: Device) {
|
||||||
|
|
||||||
|
let scope = ['basic','stream','write_post'];
|
||||||
|
|
||||||
|
if (this.device.platform === "Android") {
|
||||||
|
this.oauth = new OauthCordova();
|
||||||
|
this.pnutProvider = new PnutAuth({
|
||||||
|
appScope: scope,
|
||||||
|
redirectUri: 'http://localhost/callback'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.oauth = new OauthBrowser();
|
||||||
|
this.pnutProvider = new PnutAuth({
|
||||||
|
appScope: scope,
|
||||||
|
redirectUri: 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
});
|
||||||
|
this.oob = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
login() {
|
login() {
|
||||||
|
this.oauth.logInVia(this.pnutProvider).then(success => {
|
||||||
interface LoginResponse {
|
console.log('RESULT: ' + JSON.stringify(success));
|
||||||
access_token: string;
|
this.storage.set('token', success['access_token']);
|
||||||
|
pnut.token = success['access_token'];
|
||||||
|
this.navCtrl.setRoot(StreamPage, {stream: 'personal'});
|
||||||
|
}, error => {
|
||||||
|
console.log(error);
|
||||||
|
if (this.oob) {
|
||||||
|
this.showToken = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = new HttpHeaders()
|
saveToken() {
|
||||||
.set('Content-Type', 'application/x-www-form-urlencoded');
|
this.storage.set('token', this.token);
|
||||||
|
pnut.token = this.token;
|
||||||
let params = new HttpParams()
|
this.navCtrl.setRoot(StreamPage, {stream: 'personal'});
|
||||||
.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));
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
export const pnutauth = {
|
|
||||||
url: "https://api.pnut.io/v0/oauth/access_token",
|
|
||||||
scope: "basic,stream,write_post,files",
|
|
||||||
clientId: "",
|
|
||||||
clientSecret: ""
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
<!--
|
|
||||||
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>
|
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
<!--
|
|
||||||
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>
|
|
|
@ -1,3 +0,0 @@
|
||||||
page-settings {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
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,48 +1,20 @@
|
||||||
<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">Cancel</span>
|
<span ion-text color="primary" showWhen="ios">Cancel</span>
|
||||||
|
<ion-icon name="md-close" showWhen="android,windows"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-buttons end>
|
||||||
<ion-title>{{ title }}</ion-title>
|
<button ion-button>
|
||||||
|
<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>
|
||||||
<ion-card>
|
<p>Blarp</p>
|
||||||
<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>
|
||||||
|
|
|
@ -1,195 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
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,15 +16,70 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
<ion-content overflow-scroll=”true”>
|
<ion-content>
|
||||||
|
|
||||||
<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" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
<ion-card *ngFor="let post of posts">
|
||||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
<ion-item>
|
||||||
|
<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>
|
||||||
|
</ng-template>
|
||||||
|
</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-row>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
@ -38,10 +93,4 @@
|
||||||
</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,15 +1,12 @@
|
||||||
page-stream {
|
page-stream {
|
||||||
.item-md ion-avatar img {
|
.item-md ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
|
||||||
}
|
}
|
||||||
.item-wp ion-avatar img {
|
.item-wp ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
|
||||||
}
|
}
|
||||||
.item-ios ion-avatar img {
|
.item-ios ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modal-newpost {
|
modal-newpost {
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavController, NavParams, ModalController, Content } from 'ionic-angular';
|
import { ViewController, NavController, NavParams, ModalController } from 'ionic-angular';
|
||||||
import { Storage } from '@ionic/storage';
|
import { ThreadPage } from '../thread/thread';
|
||||||
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';
|
||||||
|
|
||||||
|
@ -19,8 +16,6 @@ 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;
|
||||||
|
@ -28,26 +23,9 @@ export class StreamPage {
|
||||||
fetcher: any;
|
fetcher: any;
|
||||||
fcaller: any;
|
fcaller: any;
|
||||||
myUsername: string;
|
myUsername: string;
|
||||||
showScrollBtn: boolean = false;
|
|
||||||
showUnified: boolean;
|
|
||||||
ccOnReply: boolean = false;
|
|
||||||
hideImg: boolean = false;
|
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
|
||||||
public modalCtrl: ModalController, private storage: Storage,
|
|
||||||
private changeDetectorRef: ChangeDetectorRef, public events: Events) {
|
|
||||||
|
|
||||||
this.storage.get('cc').then((val) => {
|
|
||||||
this.ccOnReply = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.storage.get('hideimg').then((val) => {
|
|
||||||
this.hideImg = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.storage.get('unified').then((val) => {
|
|
||||||
this.showUnified = val;
|
|
||||||
|
|
||||||
|
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController) {
|
||||||
|
// console.log(JSON.stringify(navParams));
|
||||||
switch (navParams.data.stream) {
|
switch (navParams.data.stream) {
|
||||||
case 'global':
|
case 'global':
|
||||||
this.title = 'Global';
|
this.title = 'Global';
|
||||||
|
@ -56,8 +34,7 @@ export class StreamPage {
|
||||||
break;
|
break;
|
||||||
case 'personal':
|
case 'personal':
|
||||||
this.title = 'Timeline';
|
this.title = 'Timeline';
|
||||||
console.log(this.showUnified);
|
this.fetcher = pnut.personal;
|
||||||
this.fetcher = this.showUnified ? pnut.unified : pnut.personal;
|
|
||||||
this.fetchPosts();
|
this.fetchPosts();
|
||||||
break;
|
break;
|
||||||
case 'mentions':
|
case 'mentions':
|
||||||
|
@ -71,51 +48,15 @@ export class StreamPage {
|
||||||
this.fetchMyPosts();
|
this.fetchMyPosts();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('ERROR: ' + err);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
pnut.user('me').then(res => {
|
pnut.user('me').then(res => {
|
||||||
this.myUsername = res.data.username;
|
this.myUsername = res.data.username;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
// console.log('-*-');
|
console.log(err);
|
||||||
// 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) {
|
||||||
let params = {
|
let params = {include_raw: 1, before_id: this.before_id, count: 40};
|
||||||
include_deleted: 0,
|
|
||||||
include_raw: 1,
|
|
||||||
include_reposted_by:1,
|
|
||||||
before_id: this.before_id,
|
|
||||||
count: 40
|
|
||||||
};
|
|
||||||
if (this.title === 'Mentions') {
|
if (this.title === 'Mentions') {
|
||||||
this.fcaller = this.fetcher('me', params);
|
this.fcaller = this.fetcher('me', params);
|
||||||
} else {
|
} else {
|
||||||
|
@ -123,7 +64,7 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
this.fcaller.then(res => {
|
this.fcaller.then(res => {
|
||||||
if (res.data.length > 0) {
|
if (res.data.length > 0) {
|
||||||
this.posts.push.apply(this.posts, this.parseData(res.data));
|
this.posts.push.apply(this.posts, 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);
|
||||||
|
@ -135,13 +76,7 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNewerPosts(refresher) {
|
fetchNewerPosts(refresher) {
|
||||||
let params = {
|
let params = {include_raw: 1, since_id: this.since_id, count: 40};
|
||||||
include_deleted: 0,
|
|
||||||
include_raw: 1,
|
|
||||||
include_reposted_by: 1,
|
|
||||||
since_id: this.since_id,
|
|
||||||
count: 40
|
|
||||||
};
|
|
||||||
if (this.title === 'Mentions') {
|
if (this.title === 'Mentions') {
|
||||||
this.fcaller = this.fetcher('me', params);
|
this.fcaller = this.fetcher('me', params);
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,7 +84,7 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
this.fcaller.then(res => {
|
this.fcaller.then(res => {
|
||||||
if (res.data.length > 0) {
|
if (res.data.length > 0) {
|
||||||
Array.prototype.unshift.apply(this.posts, this.parseData(res.data));
|
Array.prototype.unshift.apply(this.posts, 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);
|
||||||
|
@ -161,76 +96,197 @@ export class StreamPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchPosts() {
|
fetchPosts() {
|
||||||
let params = {
|
console.log('-- fetching global stream --');
|
||||||
include_deleted: 0,
|
this.fetcher({include_raw: 1, count: 40}).then(res => {
|
||||||
include_raw: 1,
|
|
||||||
include_reposted_by: 1,
|
|
||||||
count: 40
|
|
||||||
};
|
|
||||||
this.fetcher(params).then(res => {
|
|
||||||
if (res.meta.code === 401) {
|
|
||||||
this.storage.clear();
|
|
||||||
this.navCtrl.setRoot(LoginPage);
|
|
||||||
} else {
|
|
||||||
this.posts = this.parseData(res.data);
|
|
||||||
this.since_id = res.meta.max_id;
|
|
||||||
this.before_id = res.meta.min_id;
|
|
||||||
console.log('since_id: ' + this.since_id);
|
|
||||||
console.log('before_id: ' + this.before_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = "";
|
|
||||||
for (var j = 0; j < data[i]['reposted_by'].length; j++) {
|
|
||||||
reposted_by_string = reposted_by_string + data[i]['reposted_by'][j]['username'] + ", ";
|
|
||||||
}
|
|
||||||
// data[i]['reposted_by_string'] = "Reposted by: " + reposted_by_string;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchMyPosts() {
|
|
||||||
console.log('-- fetching mentions --');
|
|
||||||
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
|
||||||
if (res.meta.code === 401) {
|
|
||||||
this.storage.clear();
|
|
||||||
this.navCtrl.setRoot(LoginPage);
|
|
||||||
} else {
|
|
||||||
this.posts = res.data;
|
this.posts = res.data;
|
||||||
this.since_id = res.meta.max_id;
|
this.since_id = res.meta.max_id;
|
||||||
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);
|
||||||
console.log('before_id: ' + this.before_id);
|
console.log('before_id: ' + this.before_id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchThread(threadid) {
|
||||||
|
pnut.thread(threadid, {include_raw: 1, count: 140}).then(res => {
|
||||||
|
this.navCtrl.push(ThreadPage, {posts: res.data});
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMyPosts() {
|
||||||
|
console.log('-- fetching mentions --');
|
||||||
|
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
||||||
|
this.posts = res.data;
|
||||||
|
this.since_id = res.meta.max_id;
|
||||||
|
this.before_id = res.meta.min_id;
|
||||||
|
console.log('since_id: ' + this.since_id);
|
||||||
|
console.log('before_id: ' + this.before_id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmark(postid, bookmarked) {
|
||||||
|
if (bookmarked) {
|
||||||
|
pnut.deleteBookmark(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.bookmark(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repost(postid, reposted) {
|
||||||
|
if (reposted) {
|
||||||
|
pnut.deleteRepost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.repost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePost(postid) {
|
||||||
|
pnut.post(postid, {include_raw: 1}).then(res => {
|
||||||
|
for (var i = 0; i < this.posts.length; i++) {
|
||||||
|
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, {me: this.myUsername});
|
let newPostModal = this.modalCtrl.create(NewPostModal);
|
||||||
newPostModal.present();
|
newPostModal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToTop() {
|
showReplyPost(postData) {
|
||||||
this.content.scrollToTop();
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'reply', post: postData, me: this.myUsername});
|
||||||
|
newPostModal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuotedPost(postData) {
|
||||||
|
console.log(postData);
|
||||||
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'quote', post: postData, me: this.myUsername});
|
||||||
|
newPostModal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
// templateUrl: 'new-post.html'
|
||||||
|
selector: 'modal-newpost',
|
||||||
|
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-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 col-2><div text-center>{{254 - ptext.length}}</div></ion-col>
|
||||||
|
<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;
|
||||||
|
myUsername: string;
|
||||||
|
|
||||||
|
constructor(public navParams: NavParams, public viewCtrl: ViewController) {
|
||||||
|
console.log(this.navParams);
|
||||||
|
this.myUsername = navParams.data.me;
|
||||||
|
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;
|
||||||
|
if (mu !== this.myUsername) {
|
||||||
|
mtext += "@" + mu + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mtext.length > 0) {
|
||||||
|
return mtext;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,69 @@
|
||||||
|
|
||||||
</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">
|
||||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
<ion-item>
|
||||||
|
<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="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>
|
||||||
|
<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,14 +1,3 @@
|
||||||
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 } from 'ionic-angular';
|
import { NavController, NavParams, ModalController } from 'ionic-angular';
|
||||||
import { Storage } from '@ionic/storage';
|
import { NewPostModal } from '../stream/stream';
|
||||||
|
|
||||||
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,23 +18,20 @@ 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,
|
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController) {
|
||||||
private storage: Storage) {
|
|
||||||
this.posts = this.navParams.data.posts;
|
this.posts = this.navParams.data.posts;
|
||||||
this.myUsername = this.navParams.data.me;
|
}
|
||||||
|
|
||||||
this.storage.get('hideimg').then((val) => {
|
showReplyPost(postData) {
|
||||||
this.hideImg = val;
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'reply', post: postData});
|
||||||
});
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<!--
|
|
||||||
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>
|
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ export class ParserPipe implements PipeTransform {
|
||||||
|
|
||||||
transform(value: string, ...args): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
|
transform(value: string, ...args): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
|
||||||
let hregex = /href="([\S]+)"/g;
|
let hregex = /href="([\S]+)"/g;
|
||||||
if (typeof value == "undefined") value = "";
|
// value = value.replace(hregex, "class=\"ex-link\" href=");
|
||||||
value = value.replace(hregex, "onClick=\"window.open('$1', '_system', 'location=yes')\"");
|
value = value.replace(hregex, "onClick=\"window.open('$1', '_system', 'location=yes')\"");
|
||||||
return this._sanitizer.bypassSecurityTrustHtml(value);
|
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||||
}
|
}
|
||||||
|
|
11
src/providers/pnut-oauth.ts.sample
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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,8 +28,7 @@ $colors: (
|
||||||
secondary: #32db64,
|
secondary: #32db64,
|
||||||
danger: #f53d3d,
|
danger: #f53d3d,
|
||||||
light: #f4f4f4,
|
light: #f4f4f4,
|
||||||
dark: #222,
|
dark: #222
|
||||||
mention: #f0f0f0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
1
ubuntutouch/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
build
|
|
|
@ -1 +0,0 @@
|
||||||
../LICENSE
|
|
|
@ -1 +0,0 @@
|
||||||
../README.md
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"template": "pure",
|
|
||||||
"kill": "webapp-container",
|
|
||||||
"ignore": [
|
|
||||||
".git"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"template": "ubuntu-webapp",
|
|
||||||
"policy_groups": [
|
|
||||||
"webview",
|
|
||||||
"audio",
|
|
||||||
"networking"
|
|
||||||
],
|
|
||||||
"policy_version": 16.04
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
[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 +0,0 @@
|
||||||
../resources/icon-144.png
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"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 +0,0 @@
|
||||||
../www
|
|