Compare commits
No commits in common. "main" and "Goober_0_6_2" have entirely different histories.
main
...
Goober_0_6
5
.gitignore
vendored
|
@ -23,7 +23,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 +36,3 @@ UserInterfaceState.xcuserstate
|
||||||
|
|
||||||
# other bits
|
# other bits
|
||||||
pnut-oauth.ts
|
pnut-oauth.ts
|
||||||
src/pages/login/pnutauth.ts
|
|
||||||
platforms/
|
|
||||||
|
|
64
CHANGELOG.md
|
@ -3,48 +3,7 @@ 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/).
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.6.2] -
|
||||||
### 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
|
### Fixed
|
||||||
- Missing status bar when app is loaded
|
- Missing status bar when app is loaded
|
||||||
|
|
||||||
|
@ -126,15 +85,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
- Intial pre-release for Android
|
- Intial pre-release for Android
|
||||||
|
|
||||||
[Unreleased]: https://gitlab.dreamfall.space/thrrgilag/Goober/compare/0.7.0...HEAD
|
[0.6.2]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_2
|
||||||
[0.7.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.7.0
|
[0.6.1]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_1
|
||||||
[0.6.3]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.6.3
|
[0.6.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_0
|
||||||
[0.6.2]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_2
|
[0.5.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_5_0
|
||||||
[0.6.1]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_1
|
[0.4.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_4_0
|
||||||
[0.6.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_0
|
[0.3.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_3_0
|
||||||
[0.5.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_5_0
|
[0.2.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_2_0
|
||||||
[0.4.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_4_0
|
[0.1.1]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_1_1
|
||||||
[0.3.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_3_0
|
[0.1.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_1_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
|
@ -7,29 +7,32 @@ ALIGNED = $(OUT_DIR)/app-release-unsigned-aligned.apk
|
||||||
APK = $(OUT_DIR)/goober.apk
|
APK = $(OUT_DIR)/goober.apk
|
||||||
|
|
||||||
KEYSTORE = ~/android-keystore.jks
|
KEYSTORE = ~/android-keystore.jks
|
||||||
|
LOC_ADDR = $(shell hostname -i)
|
||||||
|
PUB_ADDR = $(shell hostname -I|awk '{print $$1}')
|
||||||
|
|
||||||
|
|
||||||
lab:
|
lab:
|
||||||
ionic serve -lc
|
ionic serve -lc
|
||||||
|
|
||||||
clean: wwwclean
|
device:
|
||||||
|
ionic cordova run android -lc --address $(PUB_ADDR) --device
|
||||||
|
|
||||||
wwwclean:
|
emulator:
|
||||||
rm -r www
|
ionic cordova run android -lc --address $(LOC_ADDR) --emulator
|
||||||
|
|
||||||
distclean:
|
$(UNALIGNED):
|
||||||
rm -r node_modules platforms plugins www
|
ionic cordova build android --release --prod
|
||||||
|
|
||||||
init:
|
$(ALIGNED): $(UNALIGNED)
|
||||||
npm install
|
cd $(OUT_DIR)
|
||||||
ionic cordova platform add browser
|
$(ANDROID_HOME)/build-tools/*/zipalign -v -p 4 $< $@
|
||||||
|
|
||||||
ut:
|
$(APK): $(ALIGNED)
|
||||||
npm run ionic:build
|
cd $(OUT_DIR)
|
||||||
rm www/manifest.json
|
$(ANDROID_HOME)/build-tools/*/apksigner sign --ks $(KEYSTORE) --out $@ $<
|
||||||
cd ubuntutouch && clickable
|
ls $@
|
||||||
|
|
||||||
pwa:
|
release: $(APK)
|
||||||
ionic build --prod
|
|
||||||
cp resources/*.png www/assets/icon
|
clean:
|
||||||
cp -r www ~/opt/Goober
|
rm $(OUT_DIR)/*.apk
|
||||||
|
|
13
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
|
||||||
|
|
||||||
|
@ -43,6 +40,10 @@ npm install -g ionic cordova
|
||||||
* Install Gradle (needed if you only installed the command line tools)
|
* Install Gradle (needed if you only installed the command line tools)
|
||||||
* https://gradle.org/
|
* https://gradle.org/
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
* Install VisualStudio
|
||||||
|
* https://www.visualstudio.com/
|
||||||
|
|
||||||
|
|
||||||
## Other build dependencies
|
## Other build dependencies
|
||||||
|
|
||||||
|
@ -59,9 +60,7 @@ ionic cordova platform add android
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Build and run using ionic framework
|
## Build and run
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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.6.2" 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>
|
||||||
|
@ -84,19 +84,22 @@
|
||||||
<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>
|
||||||
|
<allow-navigation href="http://10.0.0.212:8100" />
|
||||||
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
|
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
|
||||||
<plugin name="cordova-plugin-share-content" spec="^1.0.0" />
|
<plugin name="cordova-plugin-share-content" spec="^1.0.0" />
|
||||||
|
<plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
|
||||||
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
||||||
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
||||||
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="0.0.19" />
|
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="0.0.19" />
|
||||||
<plugin name="cordova-plugin-inappbrowser" spec="^2.0.2" />
|
<plugin name="cordova-plugin-inappbrowser" spec="^2.0.2" />
|
||||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
||||||
|
<plugin name="cordova-plugin-statusbar" spec="^2.4.1" />
|
||||||
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
||||||
<plugin name="cordova-plugin-device" spec="^2.0.1" />
|
<plugin name="cordova-plugin-device" spec="^2.0.1" />
|
||||||
|
<plugin name="cordova-plugin-console" spec="^1.1.0" />
|
||||||
<plugin name="cordova-android-support-gradle-release" spec="^1.4.2">
|
<plugin name="cordova-android-support-gradle-release" spec="^1.4.2">
|
||||||
<variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
|
<variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin name="cordova-plugin-filepath" spec="^1.3.0" />
|
<plugin name="cordova-plugin-filepath" spec="^1.3.0" />
|
||||||
<plugin name="cordova-sqlite-storage" spec="2.5.1" />
|
<engine name="android" spec="7.1.0" />
|
||||||
<allow-navigation href="http://192.168.1.72:8100" />
|
|
||||||
</widget>
|
</widget>
|
||||||
|
|
4777
package-lock.json
generated
67
package.json
|
@ -12,56 +12,58 @@
|
||||||
"ionic:serve": "ionic-app-scripts serve"
|
"ionic:serve": "ionic-app-scripts serve"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "5.2.9",
|
"@angular/common": "5.2.5",
|
||||||
"@angular/compiler": "5.2.9",
|
"@angular/compiler": "5.2.5",
|
||||||
"@angular/compiler-cli": "5.2.9",
|
"@angular/compiler-cli": "5.2.5",
|
||||||
"@angular/core": "5.2.9",
|
"@angular/core": "5.2.5",
|
||||||
"@angular/forms": "5.2.9",
|
"@angular/forms": "5.2.5",
|
||||||
"@angular/http": "5.2.9",
|
"@angular/http": "5.2.5",
|
||||||
"@angular/platform-browser": "5.2.9",
|
"@angular/platform-browser": "5.2.5",
|
||||||
"@angular/platform-browser-dynamic": "5.2.9",
|
"@angular/platform-browser-dynamic": "5.2.5",
|
||||||
"@ionic-native/core": "4.9.0",
|
"@ionic-native/core": "4.5.3",
|
||||||
"@ionic-native/device": "4.9.0",
|
"@ionic-native/device": "^4.5.3",
|
||||||
"@ionic-native/file": "4.9.0",
|
"@ionic-native/file": "^4.5.3",
|
||||||
"@ionic-native/file-chooser": "4.9.0",
|
"@ionic-native/file-chooser": "^4.5.3",
|
||||||
"@ionic-native/file-path": "4.9.0",
|
"@ionic-native/file-path": "^4.5.3",
|
||||||
"@ionic-native/file-transfer": "4.9.0",
|
"@ionic-native/file-transfer": "^4.5.3",
|
||||||
"@ionic-native/splash-screen": "4.9.0",
|
"@ionic-native/splash-screen": "4.5.3",
|
||||||
"@ionic-native/status-bar": "4.9.0",
|
"@ionic-native/status-bar": "4.5.3",
|
||||||
"@ionic/storage": "2.1.3",
|
"@ionic/storage": "2.1.3",
|
||||||
"com-darryncampbell-cordova-plugin-intent": "^1.1.1",
|
"com-darryncampbell-cordova-plugin-intent": "0.0.19",
|
||||||
"cordova-android-support-gradle-release": "^1.4.7",
|
"cordova-android": "7.1.0",
|
||||||
"cordova-browser": "6.0.0",
|
"cordova-android-support-gradle-release": "^1.4.2",
|
||||||
|
"cordova-plugin-console": "^1.1.0",
|
||||||
"cordova-plugin-device": "^2.0.2",
|
"cordova-plugin-device": "^2.0.2",
|
||||||
"cordova-plugin-file": "^6.0.1",
|
"cordova-plugin-file": "^6.0.1",
|
||||||
"cordova-plugin-file-transfer": "^1.7.1",
|
"cordova-plugin-file-transfer": "^1.7.1",
|
||||||
"cordova-plugin-filechooser": "^1.0.1",
|
"cordova-plugin-filechooser": "^1.0.1",
|
||||||
"cordova-plugin-filepath": "^1.4.2",
|
"cordova-plugin-filepath": "^1.3.0",
|
||||||
"cordova-plugin-inappbrowser": "^3.0.0",
|
"cordova-plugin-inappbrowser": "^2.0.2",
|
||||||
"cordova-plugin-share-content": "^1.0.0",
|
"cordova-plugin-share-content": "^1.0.0",
|
||||||
"cordova-plugin-splashscreen": "^5.0.2",
|
"cordova-plugin-splashscreen": "^5.0.2",
|
||||||
|
"cordova-plugin-statusbar": "^2.4.2",
|
||||||
"cordova-plugin-telerik-imagepicker": "^2.1.8",
|
"cordova-plugin-telerik-imagepicker": "^2.1.8",
|
||||||
"cordova-plugin-whitelist": "^1.3.3",
|
"cordova-plugin-whitelist": "^1.3.3",
|
||||||
"cordova-sqlite-storage": "^2.5.1",
|
|
||||||
"ionic-angular": "3.9.2",
|
"ionic-angular": "3.9.2",
|
||||||
"ionicons": "4.2.4",
|
"ionic-plugin-keyboard": "^2.2.1",
|
||||||
|
"ionicons": "3.0.0",
|
||||||
"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.19.0",
|
||||||
"pnut-butter": "^0.21.0",
|
|
||||||
"run": "^1.4.0",
|
"run": "^1.4.0",
|
||||||
"rxjs": "5.5.8",
|
"rxjs": "5.5.6",
|
||||||
"sw-toolbox": "3.6.0",
|
"sw-toolbox": "3.6.0",
|
||||||
"zone.js": "0.8.26"
|
"zone.js": "0.8.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ionic/app-scripts": "3.1.10",
|
"@ionic/app-scripts": "3.1.8",
|
||||||
"ionic": "3.20.0",
|
"ionic": "3.20.0",
|
||||||
"typescript": "2.8.3"
|
"typescript": "2.7.2"
|
||||||
},
|
},
|
||||||
"description": "An Ionic project",
|
"description": "An Ionic project",
|
||||||
"cordova": {
|
"cordova": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"ionic-plugin-keyboard": {},
|
||||||
"cordova-plugin-share-content": {},
|
"cordova-plugin-share-content": {},
|
||||||
"cordova-plugin-filechooser": {},
|
"cordova-plugin-filechooser": {},
|
||||||
"cordova-plugin-file": {},
|
"cordova-plugin-file": {},
|
||||||
|
@ -69,16 +71,17 @@
|
||||||
"com-darryncampbell-cordova-plugin-intent": {},
|
"com-darryncampbell-cordova-plugin-intent": {},
|
||||||
"cordova-plugin-inappbrowser": {},
|
"cordova-plugin-inappbrowser": {},
|
||||||
"cordova-plugin-whitelist": {},
|
"cordova-plugin-whitelist": {},
|
||||||
|
"cordova-plugin-statusbar": {},
|
||||||
"cordova-plugin-splashscreen": {},
|
"cordova-plugin-splashscreen": {},
|
||||||
"cordova-plugin-device": {},
|
"cordova-plugin-device": {},
|
||||||
|
"cordova-plugin-console": {},
|
||||||
"cordova-android-support-gradle-release": {
|
"cordova-android-support-gradle-release": {
|
||||||
"ANDROID_SUPPORT_VERSION": "27.+"
|
"ANDROID_SUPPORT_VERSION": "27.+"
|
||||||
},
|
},
|
||||||
"cordova-plugin-filepath": {},
|
"cordova-plugin-filepath": {}
|
||||||
"cordova-sqlite-storage": {}
|
|
||||||
},
|
},
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"browser"
|
"android"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
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,5 +1,6 @@
|
||||||
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';
|
||||||
|
@ -7,11 +8,6 @@ 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 { 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 +18,23 @@ 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}>;
|
||||||
|
|
||||||
|
scope: Array<string> = ['basic','stream','write_post','files'];
|
||||||
|
|
||||||
|
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: 'Mentions', component: StreamPage, params: {stream: 'mentions'} },
|
||||||
{ title: 'Global', icon: 'globe', component: StreamPage, params: {stream: 'global'} },
|
{ title: 'Global', component: StreamPage, params: {stream: 'global'} },
|
||||||
{ title: 'Bookmarks', icon: 'bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
{ title: 'Bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
||||||
{ title: 'Profile', icon: 'person', component: ProfilePage, params: {}},
|
{ title: 'Settings', component: SettingsPage, params: {}},
|
||||||
{ title: 'Settings', icon: 'settings', component: SettingsPage, params: {}},
|
{ title: 'Logout', component: {}, params: {}},
|
||||||
{ title: 'About', icon: 'information-circle', component: AboutPage, params: {}},
|
|
||||||
{ title: 'Logout', icon: 'exit', component: {}, params: {}},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,6 +42,7 @@ 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.
|
||||||
|
@ -60,6 +57,7 @@ export class MyApp {
|
||||||
this.initialPage('personal');
|
this.initialPage('personal');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.statusBar.styleLightContent();
|
||||||
this.splashScreen.hide();
|
this.splashScreen.hide();
|
||||||
// console.log('---');
|
// console.log('---');
|
||||||
// console.log(this.device.platform);
|
// console.log(this.device.platform);
|
||||||
|
@ -73,37 +71,31 @@ export class MyApp {
|
||||||
pnut.token = val;
|
pnut.token = val;
|
||||||
|
|
||||||
this.storage.get('scope').then((sval) => {
|
this.storage.get('scope').then((sval) => {
|
||||||
if (JSON.stringify(sval) !== JSON.stringify(pnutauth.scope)) {
|
if (JSON.stringify(sval) !== JSON.stringify(this.scope)) {
|
||||||
this.nav.setRoot(LoginPage, {});
|
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||||
} else {
|
} else {
|
||||||
this.nav.setRoot(StreamPage, {stream: timeline});
|
this.nav.setRoot(StreamPage, {stream: timeline});
|
||||||
}
|
}
|
||||||
// this.nav.setRoot(StreamPage, {stream: timeline});
|
// this.nav.setRoot(StreamPage, {stream: timeline});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.nav.setRoot(LoginPage, {});
|
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||||
});
|
});
|
||||||
// this.nav.setRoot(StreamPage, {stream: timeline});
|
// 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, {'scope': this.scope});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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.storage.clear();
|
||||||
this.nav.setRoot(LoginPage, {});
|
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||||
} else if (page.title === 'Settings' || page.title === 'About') {
|
} else if (page.title === 'Settings') {
|
||||||
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);
|
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,21 +1,14 @@
|
||||||
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, PostMenu } 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 { 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';
|
||||||
|
@ -24,7 +17,6 @@ import { FilePath } from '@ionic-native/file-path';
|
||||||
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';
|
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: [
|
||||||
|
@ -33,22 +25,15 @@ import { ClipboardModule } from 'ngx-clipboard';
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
SettingsPage,
|
SettingsPage,
|
||||||
AboutPage,
|
|
||||||
ProfilePage,
|
|
||||||
UserListPage,
|
|
||||||
TimeagoPipe,
|
TimeagoPipe,
|
||||||
NewPostModal,
|
NewPostModal,
|
||||||
PostMenu,
|
PostMenu,
|
||||||
ProfileMenu,
|
ParserPipe
|
||||||
ParserPipe,
|
|
||||||
PostComponent
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
HttpClientModule,
|
|
||||||
IonicModule.forRoot(MyApp),
|
IonicModule.forRoot(MyApp),
|
||||||
IonicStorageModule.forRoot(),
|
IonicStorageModule.forRoot(),
|
||||||
ClipboardModule,
|
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
@ -57,14 +42,11 @@ import { ClipboardModule } from 'ngx-clipboard';
|
||||||
StreamPage,
|
StreamPage,
|
||||||
ThreadPage,
|
ThreadPage,
|
||||||
SettingsPage,
|
SettingsPage,
|
||||||
AboutPage,
|
|
||||||
ProfilePage,
|
|
||||||
UserListPage,
|
|
||||||
NewPostModal,
|
NewPostModal,
|
||||||
PostMenu,
|
PostMenu
|
||||||
ProfileMenu
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
StatusBar,
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
Device,
|
Device,
|
||||||
FileChooser,
|
FileChooser,
|
||||||
|
|
|
@ -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">
|
||||||
|
|
||||||
|
|
|
@ -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,81 @@
|
||||||
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;
|
||||||
|
public showToken: boolean = false;
|
||||||
|
private token: string;
|
||||||
|
private scope: Array<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) {
|
||||||
|
|
||||||
|
this.scope = navParams.data.scope;
|
||||||
|
|
||||||
|
if (this.device.platform === "Android" || this.device.platform === "amazon-fireos") {
|
||||||
|
this.oauth = new OauthCordova();
|
||||||
|
this.pnutProvider = new PnutAuth({
|
||||||
|
appScope: this.scope,
|
||||||
|
redirectUri: 'http://localhost/callback'
|
||||||
|
});
|
||||||
|
} else if (this.device.platform === "blackberry10") {
|
||||||
|
this.oauth = new OauthBrowser();
|
||||||
|
this.pnutProvider = new PnutAuth({
|
||||||
|
appScope: this.scope,
|
||||||
|
redirectUri: 'https://zoidberg.monkeystew.net/'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.oauth = new OauthBrowser();
|
||||||
|
this.pnutProvider = new PnutAuth({
|
||||||
|
appScope: this.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']);
|
||||||
|
this.storage.set('scope', this.scope);
|
||||||
|
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('scope', this.scope);
|
||||||
|
this.storage.set('token', this.token);
|
||||||
let params = new HttpParams()
|
pnut.token = this.token;
|
||||||
.set('client_id', pnutauth.clientId)
|
this.navCtrl.setRoot(StreamPage, {stream: 'personal'});
|
||||||
.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -40,13 +40,5 @@
|
||||||
<ion-toggle [(ngModel)]="set_cc" (ionChange)="updateCc()"></ion-toggle>
|
<ion-toggle [(ngModel)]="set_cc" (ionChange)="updateCc()"></ion-toggle>
|
||||||
</ion-item>
|
</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-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -18,7 +18,6 @@ export class SettingsPage {
|
||||||
private set_unified: boolean;
|
private set_unified: boolean;
|
||||||
private set_cc: boolean;
|
private set_cc: boolean;
|
||||||
private set_default: string;
|
private set_default: string;
|
||||||
private set_hideimg: boolean;
|
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, private storage: Storage, public navParams: NavParams,
|
constructor(public navCtrl: NavController, private storage: Storage, public navParams: NavParams,
|
||||||
public events: Events) {
|
public events: Events) {
|
||||||
|
@ -42,12 +41,6 @@ export class SettingsPage {
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log('ERROR: ' + err);
|
console.log('ERROR: ' + err);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.storage.get('hideimg').then((val) => {
|
|
||||||
this.set_hideimg = val;
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('ERROR: ' + err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ionViewDidLeave() {
|
ionViewDidLeave() {
|
||||||
|
@ -66,8 +59,4 @@ export class SettingsPage {
|
||||||
this.storage.set('timeline', this.set_default);
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,78 @@
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
<ion-item color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||||
|
<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">
|
||||||
|
<div *ngFor="let r of post.raw">
|
||||||
|
<div *ngIf="r.type == 'io.pnut.core.oembed'">
|
||||||
|
<img src="{{ r.value.url }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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)">
|
||||||
|
<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)="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>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
page-stream {
|
page-stream {
|
||||||
.item-md ion-avatar img {
|
.item-md ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-wp ion-avatar img {
|
.item-wp ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-ios ion-avatar img {
|
.item-ios ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
|
import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
import { NavController, NavParams, ModalController, Content } from 'ionic-angular';
|
import { ViewController, NavController, NavParams, ModalController, Content, ToastController, PopoverController } from 'ionic-angular';
|
||||||
|
import { ThreadPage } from '../thread/thread';
|
||||||
import { Storage } from '@ionic/storage';
|
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 { Events } from 'ionic-angular';
|
import { Events } from 'ionic-angular';
|
||||||
import { LoginPage } from '../login/login';
|
import { LoginPage } from '../login/login';
|
||||||
import { NewPostModal } from '../stream/new-post';
|
|
||||||
|
|
||||||
import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@ -31,20 +34,16 @@ export class StreamPage {
|
||||||
showScrollBtn: boolean = false;
|
showScrollBtn: boolean = false;
|
||||||
showUnified: boolean;
|
showUnified: boolean;
|
||||||
ccOnReply: boolean = false;
|
ccOnReply: boolean = false;
|
||||||
hideImg: boolean = false;
|
|
||||||
|
|
||||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController,
|
||||||
public modalCtrl: ModalController, private storage: Storage,
|
private changeDetectorRef: ChangeDetectorRef, public toastCtrl: ToastController, private storage: Storage,
|
||||||
private changeDetectorRef: ChangeDetectorRef, public events: Events) {
|
public popoverCtrl: PopoverController, public events: Events) {
|
||||||
|
// console.log(JSON.stringify(navParams));
|
||||||
|
|
||||||
this.storage.get('cc').then((val) => {
|
this.storage.get('cc').then((val) => {
|
||||||
this.ccOnReply = val;
|
this.ccOnReply = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.storage.get('hideimg').then((val) => {
|
|
||||||
this.hideImg = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.storage.get('unified').then((val) => {
|
this.storage.get('unified').then((val) => {
|
||||||
this.showUnified = val;
|
this.showUnified = val;
|
||||||
|
|
||||||
|
@ -208,6 +207,17 @@ export class StreamPage {
|
||||||
return pdata;
|
return pdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fetchMyPosts() {
|
fetchMyPosts() {
|
||||||
console.log('-- fetching mentions --');
|
console.log('-- fetching mentions --');
|
||||||
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
||||||
|
@ -224,13 +234,386 @@ export class StreamPage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bookmark(postid, bookmarked) {
|
||||||
|
if (bookmarked) {
|
||||||
|
pnut.deleteBookmark(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.bookmark(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repost(postid, reposted) {
|
||||||
|
if (reposted) {
|
||||||
|
pnut.deleteRepost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.repost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).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, {me: this.myUsername});
|
||||||
newPostModal.present();
|
newPostModal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showReplyPost(postData) {
|
||||||
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'reply', post: postData, me: this.myUsername, cc: this.ccOnReply});
|
||||||
|
newPostModal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuotedPost(postData) {
|
||||||
|
console.log(postData);
|
||||||
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'quote', post: postData, me: this.myUsername, cc: this.ccOnReply});
|
||||||
|
newPostModal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
this.content.scrollToTop();
|
this.content.scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 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>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
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) => {
|
||||||
|
|
||||||
|
this.showProgress = false;
|
||||||
|
let edata = JSON.parse(err.body);
|
||||||
|
this.presentToast(edata.meta.error_message);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('-error getting filepath-');
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unattach(id) {
|
||||||
|
console.log('removing item ' + id);
|
||||||
|
this.raw.splice(id, 1);
|
||||||
|
this.files.splice(id, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ion-list>
|
||||||
|
<button ion-item (click)="share()">Share</button>
|
||||||
|
<button ion-item *ngIf="showDelBtn" (click)="delete()">Delete</button>
|
||||||
|
</ion-list>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class PostMenu {
|
||||||
|
|
||||||
|
showDelBtn: boolean = false;
|
||||||
|
|
||||||
|
constructor(public navParams: NavParams, public viewCtrl: ViewController, public toastCtrl: ToastController,
|
||||||
|
public events: Events) {
|
||||||
|
if (navParams.data.me == navParams.data.post.user.username) {
|
||||||
|
this.showDelBtn = true;
|
||||||
|
} else {
|
||||||
|
this.showDelBtn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
(<any>window).shareContentPlugin.share(this.navParams.data.post.content.text, function(e) {
|
||||||
|
console.log('sharing post:');
|
||||||
|
console.log(JSON.stringify(e));
|
||||||
|
}, function(e) {
|
||||||
|
console.log('sharing failed:');
|
||||||
|
console.log(JSON.stringify(e));
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,66 @@
|
||||||
|
|
||||||
</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>
|
||||||
|
<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)="presentPostMenu($event, post)">
|
||||||
|
<ion-icon name="more"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
page-thread {
|
page-thread {
|
||||||
.item-md ion-avatar img {
|
.item-md ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-wp ion-avatar img {
|
.item-wp ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
.item-ios ion-avatar img {
|
.item-ios ion-avatar img {
|
||||||
border-radius: 10px;
|
border-radius: 0;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NavController, NavParams } from 'ionic-angular';
|
import { NavController, NavParams, ModalController, ToastController, PopoverController } from 'ionic-angular';
|
||||||
import { Storage } from '@ionic/storage';
|
import { NewPostModal, PostMenu } from '../stream/stream';
|
||||||
|
|
||||||
import * as pnut from 'pnut-butter';
|
import * as pnut from 'pnut-butter';
|
||||||
|
|
||||||
|
@ -19,22 +19,89 @@ export class ThreadPage {
|
||||||
title: string;
|
title: string;
|
||||||
posts: Array<Object> = [];
|
posts: Array<Object> = [];
|
||||||
myUsername: string;
|
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) {
|
public popoverCtrl: PopoverController, public toastCtrl: ToastController) {
|
||||||
this.posts = this.navParams.data.posts;
|
this.posts = this.navParams.data.posts;
|
||||||
this.myUsername = this.navParams.data.me;
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuotedPost(postData) {
|
||||||
|
console.log(postData);
|
||||||
|
let newPostModal = this.modalCtrl.create(NewPostModal, {type: 'quote', post: postData});
|
||||||
|
newPostModal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmark(postid, bookmarked) {
|
||||||
|
if (bookmarked) {
|
||||||
|
pnut.deleteBookmark(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
this.storage.get('cc').then((val) => {
|
pnut.bookmark(postid).then(res => {
|
||||||
this.ccOnReply = val;
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Bookmark updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repost(postid, reposted) {
|
||||||
|
if (reposted) {
|
||||||
|
pnut.deleteRepost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
pnut.repost(postid).then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.updatePost(res.data.id);
|
||||||
|
this.presentToast("Repost updated.");
|
||||||
|
}).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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
presentPostMenu(myEvent, postData) {
|
||||||
|
let popover = this.popoverCtrl.create(PostMenu, {post: postData, me: this.myUsername});
|
||||||
|
popover.present({ev: myEvent});
|
||||||
|
}
|
||||||
|
|
||||||
|
presentToast(text) {
|
||||||
|
let toast = this.toastCtrl.create({
|
||||||
|
position: 'top',
|
||||||
|
message: text,
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
toast.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);
|
||||||
}
|
}
|
||||||
|
|
12
src/providers/pnut-oauth.ts.sample
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
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
|
|