Compare commits
64 commits
Goober_0_6
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
811b9ab0b3 | ||
|
0173d8c396 | ||
|
fd2e8e52e8 | ||
|
fd3237fff1 | ||
|
587531ab6e | ||
|
6d79b9b600 | ||
|
4f8768966c | ||
|
d464479b4f | ||
|
eb158ed5b3 | ||
|
fc1849e384 | ||
|
ea641c52fe | ||
|
1e0d8a2af0 | ||
|
7177f63b27 | ||
|
3ac8ad780f | ||
|
4261fcda94 | ||
|
724db2aafd | ||
|
9e540f774b | ||
|
da9a3a801a | ||
|
c224b081fa | ||
|
eb8fbd59ca | ||
|
4e8f7a4c69 | ||
|
6647a6ccd0 | ||
|
73664e4f44 | ||
|
38f8849972 | ||
|
bc995eb707 | ||
|
7bb6faf97a | ||
|
b4b3ddeddb | ||
|
724f5dbb29 | ||
|
e862382d38 | ||
|
257603ec95 | ||
|
c41a422968 | ||
|
eaefe3908b | ||
|
d94bec3a4b | ||
|
7f04d55641 | ||
|
d80cdf1690 | ||
|
1da3eb3b98 | ||
|
77db17acd4 | ||
|
198b804f2e | ||
|
abd237fc64 | ||
|
dad8eed809 | ||
|
9a02daa626 | ||
|
7c044cf2b8 | ||
|
a5064ee109 | ||
|
e3d62e27d3 | ||
|
5a0a18ab95 | ||
|
126f00e165 | ||
|
f34216213f | ||
|
347ba44bd1 | ||
|
0565ce9aee | ||
|
93e4c38c71 | ||
|
03a6e19b5d | ||
|
3b13d7e5ba | ||
|
284836e0dd | ||
|
ae18ab36cf | ||
|
6cf54b32ac | ||
|
c25177f758 | ||
|
10bc57a281 | ||
|
1c970a50e3 | ||
|
afa50236e2 | ||
|
892cb0e141 | ||
|
79aee37688 | ||
|
749281d959 | ||
|
43a8e0c43b | ||
|
c889a7271a |
5
.gitignore
vendored
|
@ -23,10 +23,7 @@ node_modules/
|
|||
tmp/
|
||||
temp/
|
||||
hooks/
|
||||
platforms/
|
||||
plugins/
|
||||
plugins/android.json
|
||||
plugins/ios.json
|
||||
www/
|
||||
$RECYCLE.BIN/
|
||||
|
||||
|
@ -36,3 +33,5 @@ UserInterfaceState.xcuserstate
|
|||
|
||||
# other bits
|
||||
pnut-oauth.ts
|
||||
src/pages/login/pnutauth.ts
|
||||
platforms/
|
||||
|
|
64
CHANGELOG.md
|
@ -3,7 +3,48 @@ 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/).
|
||||
|
||||
## [0.6.2] -
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- iOS icons
|
||||
|
||||
### Fixed
|
||||
- Paste from clipboard into login fields
|
||||
|
||||
### Changed
|
||||
- Replaced login page and workflow
|
||||
|
||||
### Removed
|
||||
- Cordova platform build
|
||||
|
||||
## [0.7.0]
|
||||
### Fixed
|
||||
- Sharing post preserves links
|
||||
- Native android build tree
|
||||
|
||||
### Added
|
||||
- About page
|
||||
- Profile page
|
||||
- Follow, mute, and block actions
|
||||
- Setting to hide images in timeline
|
||||
- Copy post to clipboard
|
||||
|
||||
### Changed
|
||||
- Image thumbnails are smaller and include title & description
|
||||
- Icons added to main menu
|
||||
- Updated to Android cordova 7.1.2
|
||||
|
||||
## [0.6.3]
|
||||
### Added
|
||||
- Open post in browser window
|
||||
|
||||
### Changed
|
||||
- Embedded images show thumbnail by default
|
||||
- Tapping or clicking an image opens source up in browser
|
||||
|
||||
### Removed
|
||||
- Unused cordova plugins for keyboard, console, and statusbar
|
||||
|
||||
## [0.6.2] - 2018-05-07
|
||||
### Fixed
|
||||
- Missing status bar when app is loaded
|
||||
|
||||
|
@ -85,12 +126,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
|||
### Added
|
||||
- Intial pre-release for Android
|
||||
|
||||
[0.6.2]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_2
|
||||
[0.6.1]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_1
|
||||
[0.6.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_6_0
|
||||
[0.5.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_5_0
|
||||
[0.4.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_4_0
|
||||
[0.3.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_3_0
|
||||
[0.2.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_2_0
|
||||
[0.1.1]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_1_1
|
||||
[0.1.0]: https://code.monkeystew.net/thrrgilag/Goober/src/Goober_0_1_0
|
||||
[Unreleased]: https://gitlab.dreamfall.space/thrrgilag/Goober/compare/0.7.0...HEAD
|
||||
[0.7.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.7.0
|
||||
[0.6.3]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/0.6.3
|
||||
[0.6.2]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_2
|
||||
[0.6.1]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_1
|
||||
[0.6.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_6_0
|
||||
[0.5.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_5_0
|
||||
[0.4.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_4_0
|
||||
[0.3.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_3_0
|
||||
[0.2.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_2_0
|
||||
[0.1.1]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_1_1
|
||||
[0.1.0]: https://gitlab.dreamfall.space/thrrgilag/Goober/tags/Goober_0_1_0
|
||||
|
|
35
Makefile
|
@ -7,32 +7,29 @@ ALIGNED = $(OUT_DIR)/app-release-unsigned-aligned.apk
|
|||
APK = $(OUT_DIR)/goober.apk
|
||||
|
||||
KEYSTORE = ~/android-keystore.jks
|
||||
LOC_ADDR = $(shell hostname -i)
|
||||
PUB_ADDR = $(shell hostname -I|awk '{print $$1}')
|
||||
|
||||
|
||||
lab:
|
||||
ionic serve -lc
|
||||
|
||||
device:
|
||||
ionic cordova run android -lc --address $(PUB_ADDR) --device
|
||||
clean: wwwclean
|
||||
|
||||
emulator:
|
||||
ionic cordova run android -lc --address $(LOC_ADDR) --emulator
|
||||
wwwclean:
|
||||
rm -r www
|
||||
|
||||
$(UNALIGNED):
|
||||
ionic cordova build android --release --prod
|
||||
distclean:
|
||||
rm -r node_modules platforms plugins www
|
||||
|
||||
$(ALIGNED): $(UNALIGNED)
|
||||
cd $(OUT_DIR)
|
||||
$(ANDROID_HOME)/build-tools/*/zipalign -v -p 4 $< $@
|
||||
init:
|
||||
npm install
|
||||
ionic cordova platform add browser
|
||||
|
||||
$(APK): $(ALIGNED)
|
||||
cd $(OUT_DIR)
|
||||
$(ANDROID_HOME)/build-tools/*/apksigner sign --ks $(KEYSTORE) --out $@ $<
|
||||
ls $@
|
||||
ut:
|
||||
npm run ionic:build
|
||||
rm www/manifest.json
|
||||
cd ubuntutouch && clickable
|
||||
|
||||
release: $(APK)
|
||||
|
||||
clean:
|
||||
rm $(OUT_DIR)/*.apk
|
||||
pwa:
|
||||
ionic build --prod
|
||||
cp resources/*.png www/assets/icon
|
||||
cp -r www ~/opt/Goober
|
||||
|
|
13
README.md
|
@ -1,9 +1,12 @@
|
|||
# Goober, a mobile app for pnut.io
|
||||
|
||||
Copyright 2017 Morgan McMillian
|
||||
Copyright 2017 - 2018 Morgan McMillian
|
||||
|
||||
Goober is a cross platform mobile client for pnut.io built using the Ionic framework (http://ionicframework.com/).
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -40,10 +43,6 @@ npm install -g ionic cordova
|
|||
* Install Gradle (needed if you only installed the command line tools)
|
||||
* https://gradle.org/
|
||||
|
||||
#### Windows
|
||||
* Install VisualStudio
|
||||
* https://www.visualstudio.com/
|
||||
|
||||
|
||||
## Other build dependencies
|
||||
|
||||
|
@ -60,7 +59,9 @@ ionic cordova platform add android
|
|||
```
|
||||
|
||||
|
||||
## Build and run
|
||||
## Build and run using ionic framework
|
||||
|
||||
Copy src/providers/pnut-oauth.ts.sample to src/providers/pnut-oauth.ts and include the client ID provided by pnut.io. See the [developer documentation](https://pnut.io/docs/api/implementation/overview) for more details.
|
||||
|
||||
#### Browser
|
||||
```bash
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<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">
|
||||
<widget id="com.monkeystew.goober_m" version="0.8.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<name>Goober</name>
|
||||
<description>Goober, a mobile app for pnut.io</description>
|
||||
<author email="gilag@monkeystew.com" href="https://monkeystew.org">Morgan McMillian</author>
|
||||
|
@ -84,22 +84,19 @@
|
|||
<icon height="110" src="resources/icon-110.png" width="110" />
|
||||
<icon height="144" src="resources/icon-144.png" width="144" />
|
||||
</platform>
|
||||
<allow-navigation href="http://10.0.0.212:8100" />
|
||||
<plugin name="cordova-plugin-filechooser" spec="^1.0.1" />
|
||||
<plugin name="cordova-plugin-share-content" spec="^1.0.0" />
|
||||
<plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
|
||||
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
||||
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
||||
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="0.0.19" />
|
||||
<plugin name="cordova-plugin-inappbrowser" spec="^2.0.2" />
|
||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
||||
<plugin name="cordova-plugin-statusbar" spec="^2.4.1" />
|
||||
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
||||
<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">
|
||||
<variable name="ANDROID_SUPPORT_VERSION" value="27.+" />
|
||||
</plugin>
|
||||
<plugin name="cordova-plugin-filepath" spec="^1.3.0" />
|
||||
<engine name="android" spec="7.1.0" />
|
||||
<plugin name="cordova-sqlite-storage" spec="2.5.1" />
|
||||
<allow-navigation href="http://192.168.1.72:8100" />
|
||||
</widget>
|
||||
|
|
4787
package-lock.json
generated
67
package.json
|
@ -12,58 +12,56 @@
|
|||
"ionic:serve": "ionic-app-scripts serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/common": "5.2.5",
|
||||
"@angular/compiler": "5.2.5",
|
||||
"@angular/compiler-cli": "5.2.5",
|
||||
"@angular/core": "5.2.5",
|
||||
"@angular/forms": "5.2.5",
|
||||
"@angular/http": "5.2.5",
|
||||
"@angular/platform-browser": "5.2.5",
|
||||
"@angular/platform-browser-dynamic": "5.2.5",
|
||||
"@ionic-native/core": "4.5.3",
|
||||
"@ionic-native/device": "^4.5.3",
|
||||
"@ionic-native/file": "^4.5.3",
|
||||
"@ionic-native/file-chooser": "^4.5.3",
|
||||
"@ionic-native/file-path": "^4.5.3",
|
||||
"@ionic-native/file-transfer": "^4.5.3",
|
||||
"@ionic-native/splash-screen": "4.5.3",
|
||||
"@ionic-native/status-bar": "4.5.3",
|
||||
"@angular/common": "5.2.9",
|
||||
"@angular/compiler": "5.2.9",
|
||||
"@angular/compiler-cli": "5.2.9",
|
||||
"@angular/core": "5.2.9",
|
||||
"@angular/forms": "5.2.9",
|
||||
"@angular/http": "5.2.9",
|
||||
"@angular/platform-browser": "5.2.9",
|
||||
"@angular/platform-browser-dynamic": "5.2.9",
|
||||
"@ionic-native/core": "4.9.0",
|
||||
"@ionic-native/device": "4.9.0",
|
||||
"@ionic-native/file": "4.9.0",
|
||||
"@ionic-native/file-chooser": "4.9.0",
|
||||
"@ionic-native/file-path": "4.9.0",
|
||||
"@ionic-native/file-transfer": "4.9.0",
|
||||
"@ionic-native/splash-screen": "4.9.0",
|
||||
"@ionic-native/status-bar": "4.9.0",
|
||||
"@ionic/storage": "2.1.3",
|
||||
"com-darryncampbell-cordova-plugin-intent": "0.0.19",
|
||||
"cordova-android": "7.1.0",
|
||||
"cordova-android-support-gradle-release": "^1.4.2",
|
||||
"cordova-plugin-console": "^1.1.0",
|
||||
"com-darryncampbell-cordova-plugin-intent": "^1.1.1",
|
||||
"cordova-android-support-gradle-release": "^1.4.7",
|
||||
"cordova-browser": "6.0.0",
|
||||
"cordova-plugin-device": "^2.0.2",
|
||||
"cordova-plugin-file": "^6.0.1",
|
||||
"cordova-plugin-file-transfer": "^1.7.1",
|
||||
"cordova-plugin-filechooser": "^1.0.1",
|
||||
"cordova-plugin-filepath": "^1.3.0",
|
||||
"cordova-plugin-inappbrowser": "^2.0.2",
|
||||
"cordova-plugin-filepath": "^1.4.2",
|
||||
"cordova-plugin-inappbrowser": "^3.0.0",
|
||||
"cordova-plugin-share-content": "^1.0.0",
|
||||
"cordova-plugin-splashscreen": "^5.0.2",
|
||||
"cordova-plugin-statusbar": "^2.4.2",
|
||||
"cordova-plugin-telerik-imagepicker": "^2.1.8",
|
||||
"cordova-plugin-whitelist": "^1.3.3",
|
||||
"cordova-sqlite-storage": "^2.5.1",
|
||||
"ionic-angular": "3.9.2",
|
||||
"ionic-plugin-keyboard": "^2.2.1",
|
||||
"ionicons": "3.0.0",
|
||||
"ionicons": "4.2.4",
|
||||
"moment": "^2.18.1",
|
||||
"ng2-cordova-oauth": "0.0.8",
|
||||
"pnut-butter": "^0.19.0",
|
||||
"ngx-clipboard": "^11.1.9",
|
||||
"pnut-butter": "^0.21.0",
|
||||
"run": "^1.4.0",
|
||||
"rxjs": "5.5.6",
|
||||
"rxjs": "5.5.8",
|
||||
"sw-toolbox": "3.6.0",
|
||||
"zone.js": "0.8.20"
|
||||
"zone.js": "0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/app-scripts": "3.1.8",
|
||||
"@ionic/app-scripts": "3.1.10",
|
||||
"ionic": "3.20.0",
|
||||
"typescript": "2.7.2"
|
||||
"typescript": "2.8.3"
|
||||
},
|
||||
"description": "An Ionic project",
|
||||
"cordova": {
|
||||
"plugins": {
|
||||
"ionic-plugin-keyboard": {},
|
||||
"cordova-plugin-share-content": {},
|
||||
"cordova-plugin-filechooser": {},
|
||||
"cordova-plugin-file": {},
|
||||
|
@ -71,17 +69,16 @@
|
|||
"com-darryncampbell-cordova-plugin-intent": {},
|
||||
"cordova-plugin-inappbrowser": {},
|
||||
"cordova-plugin-whitelist": {},
|
||||
"cordova-plugin-statusbar": {},
|
||||
"cordova-plugin-splashscreen": {},
|
||||
"cordova-plugin-device": {},
|
||||
"cordova-plugin-console": {},
|
||||
"cordova-android-support-gradle-release": {
|
||||
"ANDROID_SUPPORT_VERSION": "27.+"
|
||||
},
|
||||
"cordova-plugin-filepath": {}
|
||||
"cordova-plugin-filepath": {},
|
||||
"cordova-sqlite-storage": {}
|
||||
},
|
||||
"platforms": [
|
||||
"android"
|
||||
"browser"
|
||||
]
|
||||
}
|
||||
}
|
BIN
resources/icon-128.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
resources/icon-512.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
resources/icon-ipad-retina.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
resources/icon-ipad.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
resources/icon-iphone-retina.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/icon-iphone.png
Normal file
After Width: | Height: | Size: 16 KiB |
|
@ -1,6 +1,5 @@
|
|||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Nav, Platform } from 'ionic-angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||
import { Storage } from '@ionic/storage';
|
||||
import { Device } from '@ionic-native/device';
|
||||
|
@ -8,6 +7,11 @@ import { Device } from '@ionic-native/device';
|
|||
import { LoginPage } from '../pages/login/login';
|
||||
import { StreamPage } from '../pages/stream/stream';
|
||||
import { SettingsPage } from '../pages/settings/settings';
|
||||
import { AboutPage } from '../pages/about/about';
|
||||
import { ProfilePage } from '../pages/profile/profile';
|
||||
import { pnutauth } from '../pages/login/pnutauth';
|
||||
|
||||
import { IUser } from '../models/IUser';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
|
@ -18,23 +22,23 @@ export class MyApp {
|
|||
@ViewChild(Nav) nav: Nav;
|
||||
|
||||
rootPage: any = StreamPage;
|
||||
pages: Array<{title: string, icon: string, component: any, params: Object}>;
|
||||
profile: IUser;
|
||||
|
||||
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,
|
||||
constructor(public platform: Platform, public splashScreen: SplashScreen,
|
||||
private storage: Storage, private device: Device) {
|
||||
this.initializeApp();
|
||||
|
||||
// used for an example of ngFor and navigation
|
||||
this.pages = [
|
||||
{ title: 'Timeline', component: StreamPage, params: {stream: 'personal'} },
|
||||
{ title: 'Mentions', component: StreamPage, params: {stream: 'mentions'} },
|
||||
{ title: 'Global', component: StreamPage, params: {stream: 'global'} },
|
||||
{ title: 'Bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
||||
{ title: 'Settings', component: SettingsPage, params: {}},
|
||||
{ title: 'Logout', component: {}, params: {}},
|
||||
{ title: 'Timeline', icon: 'home', component: StreamPage, params: {stream: 'personal'} },
|
||||
{ title: 'Mentions', icon: 'at', component: StreamPage, params: {stream: 'mentions'} },
|
||||
{ title: 'Global', icon: 'globe', component: StreamPage, params: {stream: 'global'} },
|
||||
{ title: 'Bookmarks', icon: 'bookmarks', component: StreamPage, params: {stream: 'bookmarks'} },
|
||||
{ title: 'Profile', icon: 'person', component: ProfilePage, params: {}},
|
||||
{ title: 'Settings', icon: 'settings', component: SettingsPage, params: {}},
|
||||
{ title: 'About', icon: 'information-circle', component: AboutPage, params: {}},
|
||||
{ title: 'Logout', icon: 'exit', component: {}, params: {}},
|
||||
];
|
||||
|
||||
}
|
||||
|
@ -42,7 +46,6 @@ export class MyApp {
|
|||
initializeApp() {
|
||||
console.log('--- initializeApp ---');
|
||||
|
||||
|
||||
this.platform.ready().then(() => {
|
||||
// Okay, so the platform is ready and our plugins are available.
|
||||
// Here you can do any higher level native things you might need.
|
||||
|
@ -57,7 +60,6 @@ export class MyApp {
|
|||
this.initialPage('personal');
|
||||
});
|
||||
|
||||
this.statusBar.styleLightContent();
|
||||
this.splashScreen.hide();
|
||||
// console.log('---');
|
||||
// console.log(this.device.platform);
|
||||
|
@ -71,31 +73,37 @@ export class MyApp {
|
|||
pnut.token = val;
|
||||
|
||||
this.storage.get('scope').then((sval) => {
|
||||
if (JSON.stringify(sval) !== JSON.stringify(this.scope)) {
|
||||
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||
if (JSON.stringify(sval) !== JSON.stringify(pnutauth.scope)) {
|
||||
this.nav.setRoot(LoginPage, {});
|
||||
} else {
|
||||
this.nav.setRoot(StreamPage, {stream: timeline});
|
||||
}
|
||||
// this.nav.setRoot(StreamPage, {stream: timeline});
|
||||
}).catch(err => {
|
||||
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||
this.nav.setRoot(LoginPage, {});
|
||||
});
|
||||
// this.nav.setRoot(StreamPage, {stream: timeline});
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log('ERROR: ' + err);
|
||||
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||
this.nav.setRoot(LoginPage, {});
|
||||
});
|
||||
}
|
||||
|
||||
openPage(page) {
|
||||
async openPage(page) {
|
||||
// Reset the content nav to have just this page
|
||||
// we wouldn't want the back button to show in this scenario
|
||||
if (page.title === 'Logout') {
|
||||
// this.storage.remove('token');
|
||||
this.storage.clear();
|
||||
this.nav.setRoot(LoginPage, {'scope': this.scope});
|
||||
} else if (page.title === 'Settings') {
|
||||
this.nav.setRoot(LoginPage, {});
|
||||
} else if (page.title === 'Settings' || page.title === 'About') {
|
||||
this.nav.push(page.component, page.params);
|
||||
} else if (page.title === 'Profile') {
|
||||
await pnut.user('me').then(res => {
|
||||
this.profile = res.data as IUser;
|
||||
});
|
||||
page.params = {user: this.profile, me: this.profile.username};
|
||||
this.nav.push(page.component, page.params);
|
||||
} else {
|
||||
this.nav.setRoot(page.component, page.params);
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
<ion-content>
|
||||
<ion-list>
|
||||
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
|
||||
{{p.title}}
|
||||
<ion-icon name="{{p.icon}}"></ion-icon>
|
||||
<span class="menuText">{{p.title}}</span>
|
||||
</button>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
@ -16,4 +17,4 @@
|
|||
</ion-menu>
|
||||
|
||||
<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
|
||||
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
|
||||
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { MyApp } from './app.component';
|
||||
import { LoginPage } from '../pages/login/login';
|
||||
import { StreamPage, NewPostModal, PostMenu } from '../pages/stream/stream';
|
||||
import { StreamPage } from '../pages/stream/stream';
|
||||
import { PostMenu } from '../pages/stream/post-menu';
|
||||
import { NewPostModal } from '../pages/stream/new-post';
|
||||
import { ThreadPage } from '../pages/thread/thread';
|
||||
import { 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 { IonicStorageModule } from '@ionic/storage';
|
||||
import { Device } from '@ionic-native/device';
|
||||
|
@ -17,6 +24,7 @@ import { FilePath } from '@ionic-native/file-path';
|
|||
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';
|
||||
import { TimeagoPipe } from '../pipes/timeago/timeago';
|
||||
import { ParserPipe } from '../pipes/parser/parser';
|
||||
import { ClipboardModule } from 'ngx-clipboard';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -25,15 +33,22 @@ import { ParserPipe } from '../pipes/parser/parser';
|
|||
StreamPage,
|
||||
ThreadPage,
|
||||
SettingsPage,
|
||||
AboutPage,
|
||||
ProfilePage,
|
||||
UserListPage,
|
||||
TimeagoPipe,
|
||||
NewPostModal,
|
||||
PostMenu,
|
||||
ParserPipe
|
||||
ProfileMenu,
|
||||
ParserPipe,
|
||||
PostComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
IonicModule.forRoot(MyApp),
|
||||
IonicStorageModule.forRoot(),
|
||||
ClipboardModule,
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
|
@ -42,11 +57,14 @@ import { ParserPipe } from '../pipes/parser/parser';
|
|||
StreamPage,
|
||||
ThreadPage,
|
||||
SettingsPage,
|
||||
AboutPage,
|
||||
ProfilePage,
|
||||
UserListPage,
|
||||
NewPostModal,
|
||||
PostMenu
|
||||
PostMenu,
|
||||
ProfileMenu
|
||||
],
|
||||
providers: [
|
||||
StatusBar,
|
||||
SplashScreen,
|
||||
Device,
|
||||
FileChooser,
|
||||
|
|
|
@ -14,3 +14,8 @@
|
|||
// To declare rules for a specific mode, create a child rule
|
||||
// for the .md, .ios, or .wp mode classes. The mode class is
|
||||
// automatically applied to the <body> element in the app.
|
||||
|
||||
.menuText {
|
||||
left: 45px;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 19 KiB |
BIN
src/assets/icon/icon.png
Normal file
After Width: | Height: | Size: 21 KiB |
95
src/components/post/post.html
Normal file
|
@ -0,0 +1,95 @@
|
|||
|
||||
<ion-item color="{{ post.you_are_mentioned ? 'mention' : '' }}" (click)="showProfile(post.user)">
|
||||
<ion-avatar item-start >
|
||||
<img src="{{ post.user.content.avatar_image.link }}">
|
||||
</ion-avatar>
|
||||
<h2>{{ post.user.name }}</h2>
|
||||
<p>@{{ post.user.username }}</p>
|
||||
<ion-note item-end>
|
||||
<div text-right>
|
||||
{{ post.created_at | timeago }}<br/>
|
||||
{{ post.source.name }}
|
||||
</div>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<ion-card-content>
|
||||
<div *ngIf="post.is_deleted; else renderBlock"></div>
|
||||
<ng-template #renderBlock >
|
||||
<div [innerHTML]="post.content.html | parser"></div>
|
||||
<div *ngIf="post.raw">
|
||||
<div *ngFor="let r of post.raw">
|
||||
<div *ngIf="r.type == 'nl.chimpnut.blog.post'">
|
||||
<hr>
|
||||
<div>{{ r.value.body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ion-card-content>
|
||||
|
||||
<div *ngIf="post.raw">
|
||||
<ion-list *ngFor="let r of post.raw">
|
||||
<div *ngIf="r.type == 'io.pnut.core.oembed'">
|
||||
<div *ngIf="hideImg; then hidebtn else thumbbtn"></div>
|
||||
<ng-template #thumbbtn>
|
||||
<ion-item>
|
||||
<ion-thumbnail item-start>
|
||||
<img src="{{ r.value.thumbnail_url || r.value.url }}" (click)="showImage(r.value.url)">
|
||||
</ion-thumbnail>
|
||||
<h2>{{ r.value.title }}</h2>
|
||||
<p>{{ r.value.description }}</p>
|
||||
</ion-item>
|
||||
</ng-template>
|
||||
<ng-template #hidebtn>
|
||||
<ion-item>
|
||||
<button ion-button icon-start (click)="showImage(r.value.url)">
|
||||
<ion-icon name="image"></ion-icon>
|
||||
{{ r.value.title }}
|
||||
</button>
|
||||
<p>{{ r.value.description }}</p>
|
||||
</ion-item>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div *ngIf="post.reposted_by_string">
|
||||
<ion-item><ion-note>{{ post.reposted_by_string }}</ion-note></ion-item>
|
||||
</div>
|
||||
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="showReplyPost(post,'reply')">
|
||||
<ion-icon name="ios-undo"></ion-icon>
|
||||
</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="showReplyPost(post,'quote')">
|
||||
<ion-icon name="quote"></ion-icon>
|
||||
</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="repost(post.id, post.you_reposted)">
|
||||
<ion-icon name="repeat"></ion-icon>
|
||||
<div *ngIf="post.counts.reposts > 0">{{ post.counts.reposts }}</div>
|
||||
</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="bookmark(post.id, post.you_bookmarked)">
|
||||
<ion-icon name="star"></ion-icon>
|
||||
<div *ngIf="post.counts.bookmarks > 0">{{ post.counts.bookmarks }}</div>
|
||||
</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="fetchThread(post.thread_id)">
|
||||
<ion-icon name="chatboxes"></ion-icon>
|
||||
<div *ngIf="post.counts.replies > 0">{{ post.counts.replies }}</div>
|
||||
</button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button icon-left clear small block (click)="presentPostMenu($event, post)">
|
||||
<ion-icon name="more"></ion-icon>
|
||||
</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
14
src/components/post/post.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
post {
|
||||
.item-md ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-wp ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-ios ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
120
src/components/post/post.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
import { NavController, NavParams, ModalController, ToastController, PopoverController } from 'ionic-angular';
|
||||
import { ProfilePage } from '../../pages/profile/profile';
|
||||
import { ThreadPage } from '../../pages/thread/thread';
|
||||
import { LoginPage } from '../../pages/login/login';
|
||||
import { NewPostModal } from '../../pages/stream/new-post';
|
||||
import { PostMenu } from '../../pages/stream/post-menu';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
/**
|
||||
* Generated class for the PostComponent component.
|
||||
*
|
||||
* See https://angular.io/api/core/Component for more info on Angular
|
||||
* Components.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'post',
|
||||
templateUrl: 'post.html'
|
||||
})
|
||||
export class PostComponent {
|
||||
|
||||
@Input() public post: Object;
|
||||
@Input() public hideImg: boolean;
|
||||
@Input() public ccOnReply: boolean;
|
||||
@Input() public myUsername: string;
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||
public modalCtrl: ModalController, public toastCtrl: ToastController,
|
||||
public popoverCtrl: PopoverController) {}
|
||||
|
||||
fetchThread(threadid) {
|
||||
pnut.thread(threadid, {include_deleted: 0, include_raw: 1, count: 140}).then(res => {
|
||||
if (res.meta.code === 401) {
|
||||
// this.storage.clear();
|
||||
this.navCtrl.setRoot(LoginPage);
|
||||
} else {
|
||||
this.navCtrl.push(ThreadPage, {posts: res.data, me: this.myUsername});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showImage(url) {
|
||||
window.open(url, '_system');
|
||||
}
|
||||
|
||||
showProfile(user) {
|
||||
this.navCtrl.push(ProfilePage, {user: user, me: this.myUsername});
|
||||
}
|
||||
|
||||
showReplyPost(postData, repType) {
|
||||
let newPostModal = this.modalCtrl.create(NewPostModal, {
|
||||
type: repType,
|
||||
post: postData,
|
||||
me: this.myUsername,
|
||||
cc: this.ccOnReply});
|
||||
newPostModal.present();
|
||||
}
|
||||
|
||||
repost(postid, reposted) {
|
||||
if (reposted) {
|
||||
pnut.deleteRepost(postid).then(res => {
|
||||
this.updatePost(res.data.id);
|
||||
this.presentToast("Repost updated.");
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
pnut.repost(postid).then(res => {
|
||||
this.updatePost(res.data.id);
|
||||
this.presentToast("Repost updated.");
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bookmark(postid, bookmarked) {
|
||||
if (bookmarked) {
|
||||
pnut.deleteBookmark(postid).then(res => {
|
||||
this.updatePost(res.data.id);
|
||||
this.presentToast("Bookmark updated.");
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
pnut.bookmark(postid).then(res => {
|
||||
this.updatePost(res.data.id);
|
||||
this.presentToast("Bookmark updated.");
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatePost(postid) {
|
||||
pnut.post(postid, {include_raw: 1}).then(res => {
|
||||
this.post = res.data;
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
presentToast(text) {
|
||||
let toast = this.toastCtrl.create({
|
||||
position: 'top',
|
||||
message: text,
|
||||
duration: 2000
|
||||
});
|
||||
toast.present();
|
||||
}
|
||||
|
||||
presentPostMenu(myEvent, postData) {
|
||||
let popover = this.popoverCtrl.create(PostMenu, {
|
||||
post: postData,
|
||||
me: this.myUsername});
|
||||
popover.present({ev: myEvent});
|
||||
}
|
||||
|
||||
}
|
|
@ -2,26 +2,31 @@
|
|||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic App</title>
|
||||
<title>Goober</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
|
||||
<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">
|
||||
<meta name="theme-color" content="#4e8ef7">
|
||||
|
||||
|
||||
<!-- cordova.js required for cordova apps -->
|
||||
<script src="cordova.js"></script>
|
||||
|
||||
<!-- un-comment this code to enable service worker
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('service-worker.js')
|
||||
.then(() => console.log('service worker installed'))
|
||||
.catch(err => console.error('Error', err));
|
||||
}
|
||||
</script>-->
|
||||
</script>
|
||||
|
||||
<link href="build/main.css" rel="stylesheet">
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "Ionic",
|
||||
"short_name": "Ionic",
|
||||
"name": "Goober",
|
||||
"short_name": "Goober",
|
||||
"start_url": "index.html",
|
||||
"display": "standalone",
|
||||
"icons": [{
|
||||
"src": "assets/imgs/logo.png",
|
||||
"src": "assets/icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"background_color": "#4e8ef7",
|
||||
"theme_color": "#4e8ef7"
|
||||
}
|
||||
}
|
||||
|
|
33
src/models/IUser.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { IUserContent } from './user/IUserContent';
|
||||
|
||||
export interface IUser {
|
||||
badge?: {
|
||||
id: string,
|
||||
name: string;
|
||||
};
|
||||
content: IUserContent;
|
||||
counts: {
|
||||
bookmarks: number,
|
||||
clients: number,
|
||||
followers: number,
|
||||
following: number,
|
||||
posts: number,
|
||||
users: number;
|
||||
};
|
||||
created_at: string;
|
||||
follows_you: boolean;
|
||||
id: string;
|
||||
locale: string;
|
||||
name: string;
|
||||
timezone: string;
|
||||
type: string;
|
||||
username: string;
|
||||
you_blocked: boolean;
|
||||
you_can_follow: boolean;
|
||||
you_follow: boolean;
|
||||
you_muted: boolean;
|
||||
verified: {
|
||||
domain: string,
|
||||
link: string;
|
||||
};
|
||||
}
|
6
src/models/user/IUserAvatarImage.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface IUserAvatarImage {
|
||||
is_default: boolean;
|
||||
height: number;
|
||||
link: string;
|
||||
width: number;
|
||||
}
|
11
src/models/user/IUserContent.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { IUserAvatarImage } from './IUserAvatarImage';
|
||||
import { IUserCoverImage } from './IUserCoverImage';
|
||||
|
||||
export interface IUserContent {
|
||||
avatar_image: IUserAvatarImage;
|
||||
cover_image: IUserCoverImage;
|
||||
entities?: {};
|
||||
html: string;
|
||||
markdown_text: string;
|
||||
text: string;
|
||||
}
|
6
src/models/user/IUserCoverImage.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface IUserCoverImage {
|
||||
link: string;
|
||||
is_default: boolean;
|
||||
width: number;
|
||||
heigth: number;
|
||||
}
|
43
src/pages/about/about.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!--
|
||||
Generated template for the AboutPage page.
|
||||
|
||||
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||
Ionic pages and navigation.
|
||||
-->
|
||||
<ion-header>
|
||||
|
||||
<ion-navbar>
|
||||
<ion-title>About</ion-title>
|
||||
</ion-navbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content padding>
|
||||
|
||||
<ion-row center>
|
||||
<ion-col text-center>
|
||||
<img src="assets/icon/icon.png" height="64" width="64">
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
<ion-row center>
|
||||
<ion-col text-center>
|
||||
<h1>Goober {{ version }}</h1>
|
||||
A mobile client for <a href="https://pnut.io" target="_system">pnut.io</a>.
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
<ion-row center>
|
||||
<ion-col text-center>
|
||||
<p>made by Morgan McMillian (<a href="https://pnut.io/@thrrgilag" target="_system">@thrrgilag</a>).</p>
|
||||
<p>Goober is free and open source software licensed under the
|
||||
Apache License 2.0.</p>
|
||||
<p><a href="http://www.apache.org/licenses/LICENSE-2.0" target="_system">
|
||||
http://www.apache.org/licenses/LICENSE-2.0</a></p>
|
||||
<p> </p>
|
||||
<p><button ion-button (click)="browse('https://gitlab.dreamfall.space/thrrgilag/Goober/wikis/home')">Project Site</button></p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
</ion-content>
|
3
src/pages/about/about.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
page-about {
|
||||
|
||||
}
|
30
src/pages/about/about.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavController, NavParams } from 'ionic-angular';
|
||||
|
||||
/**
|
||||
* Generated class for the AboutPage page.
|
||||
*
|
||||
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||
* Ionic pages and navigation.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'page-about',
|
||||
templateUrl: 'about.html',
|
||||
})
|
||||
export class AboutPage {
|
||||
|
||||
private version: string = '0.8.0';
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams) {
|
||||
}
|
||||
|
||||
ionViewDidLoad() {
|
||||
console.log('ionViewDidLoad AboutPage');
|
||||
}
|
||||
|
||||
browse(url) {
|
||||
window.open(url, '_system');
|
||||
}
|
||||
|
||||
}
|
|
@ -20,17 +20,12 @@
|
|||
<h2>Goober</h2>
|
||||
<p block>A mobile client for pnut.io</p>
|
||||
</div><p> </p>
|
||||
<div *ngIf="!showToken">
|
||||
<p>Tap the Log In button to open browser window and enter your pnut.io creditionals and authorize Goober.</p>
|
||||
<div *ngIf="oob">
|
||||
<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>
|
||||
</div>
|
||||
<button ion-button block (click)="login()">Log In</button><p>
|
||||
</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-label stacked>Username</ion-label>
|
||||
<ion-input [(ngModel)]="username" type="text"></ion-input>
|
||||
<ion-label stacked>Password</ion-label>
|
||||
<ion-input [(ngModel)]="password" type="password"></ion-input>
|
||||
|
||||
<button ion-button block (click)="login()">Log In</button>
|
||||
|
||||
</ion-content>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
page-login {
|
||||
|
||||
user-select: auto !important;
|
||||
}
|
||||
|
|
|
@ -1,81 +1,54 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavController, NavParams } from 'ionic-angular';
|
||||
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
||||
import { Storage } from '@ionic/storage';
|
||||
import { Device } from '@ionic-native/device';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Generated class for the LoginPage page.
|
||||
*
|
||||
* See http://ionicframework.com/docs/components/#navigation for more info
|
||||
* on Ionic pages and navigation.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'page-login',
|
||||
templateUrl: 'login.html',
|
||||
})
|
||||
export class LoginPage {
|
||||
|
||||
private oauth: any;
|
||||
private pnutProvider: any;
|
||||
private oob: boolean = false;
|
||||
public showToken: boolean = false;
|
||||
private token: string;
|
||||
private scope: Array<string> = [];
|
||||
private username: string;
|
||||
private password: string;
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams, private storage: Storage,
|
||||
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;
|
||||
}
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||
private storage: Storage, private http: HttpClient) {
|
||||
|
||||
}
|
||||
|
||||
login() {
|
||||
this.oauth.logInVia(this.pnutProvider).then(success => {
|
||||
console.log('RESULT: ' + JSON.stringify(success));
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveToken() {
|
||||
this.storage.set('scope', this.scope);
|
||||
this.storage.set('token', this.token);
|
||||
pnut.token = this.token;
|
||||
this.navCtrl.setRoot(StreamPage, {stream: 'personal'});
|
||||
interface LoginResponse {
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
let headers = new HttpHeaders()
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
let params = new HttpParams()
|
||||
.set('client_id', pnutauth.clientId)
|
||||
.set('password_grant_secret', pnutauth.clientSecret)
|
||||
.set('username', this.username)
|
||||
.set('password', this.password)
|
||||
.set('grant_type', 'password')
|
||||
.set('scope', pnutauth.scope);
|
||||
|
||||
this.http.post<LoginResponse>(pnutauth.url, params, {headers: headers}).subscribe(res => {
|
||||
console.log('authorized');
|
||||
this.storage.set('scope', pnutauth.scope);
|
||||
this.storage.set('token', res.access_token);
|
||||
pnut.token = res.access_token;
|
||||
this.navCtrl.setRoot(StreamPage, {stream: "personal"});
|
||||
}, err => {
|
||||
console.log("error: " + JSON.stringify(err));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
6
src/pages/login/pnutauth.ts.sample
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const pnutauth = {
|
||||
url: "https://api.pnut.io/v0/oauth/access_token",
|
||||
scope: "basic,stream,write_post,files",
|
||||
clientId: "",
|
||||
clientSecret: ""
|
||||
}
|
76
src/pages/profile/profile-menu.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ViewController, NavParams, ToastController } from 'ionic-angular';
|
||||
import { Events } from 'ionic-angular';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-list>
|
||||
<button ion-item (click)="browse()">Open in browser</button>
|
||||
<button ion-item [disabled]="myUsername == username">Block</button>
|
||||
<button ion-item [disabled]="myUsername == username">Mute</button>
|
||||
</ion-list>
|
||||
`
|
||||
})
|
||||
export class ProfileMenu {
|
||||
|
||||
private userid: string;
|
||||
private username: string;
|
||||
private you_muted: boolean;
|
||||
private you_blocked: boolean;
|
||||
private myUsername: string;
|
||||
|
||||
constructor(public navParams: NavParams, public viewCtrl: ViewController,
|
||||
public toastCtrl: ToastController, public events: Events) {
|
||||
this.userid = this.navParams.data.userid;
|
||||
this.username = this.navParams.data.username;
|
||||
this.you_muted = this.navParams.data.you_muted;
|
||||
this.you_blocked = this.navParams.data.you_blocked;
|
||||
this.myUsername = this.navParams.data.me;
|
||||
}
|
||||
|
||||
browse() {
|
||||
window.open('https://pnut.io/@' + this.username, '_system');
|
||||
this.close();
|
||||
}
|
||||
|
||||
mute() {
|
||||
if (this.you_muted) {
|
||||
pnut.unmute(this.userid).then(res => {
|
||||
this.presentToast('User unmuted');
|
||||
});
|
||||
} else {
|
||||
pnut.mute(this.userid).then(res => {
|
||||
this.presentToast('User muted');
|
||||
});
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
|
||||
block() {
|
||||
if (this.you_blocked) {
|
||||
pnut.unblock(this.userid).then(res => {
|
||||
this.presentToast('User unblocked');
|
||||
});
|
||||
} else {
|
||||
pnut.block(this.userid).then(res => {
|
||||
this.presentToast('User blocked');
|
||||
});
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
|
||||
presentToast(text) {
|
||||
let toast = this.toastCtrl.create({
|
||||
position: 'top',
|
||||
message: text,
|
||||
duration: 2000
|
||||
});
|
||||
toast.present();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
86
src/pages/profile/profile.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
<!--
|
||||
Generated template for the ProfilePage page.
|
||||
|
||||
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||
Ionic pages and navigation.
|
||||
-->
|
||||
<ion-header>
|
||||
|
||||
<ion-navbar>
|
||||
<ion-title>{{ user.username }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only (click)="presentProfileMenu($event)">
|
||||
<ion-icon name="more"></ion-icon>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content>
|
||||
|
||||
<img src="{{ user.content.cover_image.link }}">
|
||||
|
||||
<ion-item>
|
||||
<ion-avatar item-start>
|
||||
<img src="{{ user.content.avatar_image.link }}">
|
||||
</ion-avatar>
|
||||
<h2>{{ user.name }}</h2>
|
||||
<p>@{{ user.username }}</p>
|
||||
<ion-col item-end text-right>
|
||||
<button ion-button [disabled]="myUsername == user.username" (click)="followUser()">{{ user.you_follow ? "Unfollow" : "Follow" }}</button>
|
||||
<ion-note>{{ user.follows_you ? "Follows you" : ""}}</ion-note>
|
||||
</ion-col>
|
||||
</ion-item>
|
||||
|
||||
<div padding [innerHTML]="user.content.html | parser"></div>
|
||||
<ion-row padding>
|
||||
<ion-col>
|
||||
<ion-row>
|
||||
<button ion-button full clear>{{ user.counts.posts }}<br/>posts</button>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<button ion-button full (click)="showUserList('Followers')">{{ user.counts.followers }}<br/>followers</button>
|
||||
</ion-row>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<ion-row>
|
||||
<button ion-button full clear>{{ user.counts.bookmarks }}<br/>stars</button>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<button ion-button full (click)="showUserList('Following')">{{ user.counts.following }}<br/>following</button>
|
||||
</ion-row>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
<ion-segment [(ngModel)]="activeTab">
|
||||
<ion-segment-button value="posts">Posts</ion-segment-button>
|
||||
<ion-segment-button value="bookmarks">Stars</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<div [ngSwitch]="activeTab">
|
||||
|
||||
<div *ngSwitchCase="'posts'">
|
||||
<ion-list>
|
||||
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||
</ion-card>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'bookmarks'">
|
||||
<ion-list>
|
||||
<ion-card *ngFor="let post of bookmarks" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||
</ion-card>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<ion-infinite-scroll (ionInfinite)="fetchOlderPosts($event, activeTab)">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
</div>
|
||||
|
||||
</ion-content>
|
14
src/pages/profile/profile.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
page-profile {
|
||||
.item-md ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-wp ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-ios ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
159
src/pages/profile/profile.ts
Normal file
|
@ -0,0 +1,159 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavController, NavParams, PopoverController, ToastController } from 'ionic-angular';
|
||||
import { UserListPage } from '../user-list/user-list';
|
||||
import { ProfileMenu } from './profile-menu'
|
||||
|
||||
import { IUser } from '../../models/IUser';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
/**
|
||||
* Generated class for the ProfilePage page.
|
||||
*
|
||||
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||
* Ionic pages and navigation.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'page-profile',
|
||||
templateUrl: 'profile.html',
|
||||
})
|
||||
export class ProfilePage {
|
||||
|
||||
private user: IUser;
|
||||
private posts: Array<Object> = [];
|
||||
private bookmarks: Array<Object> = [];
|
||||
private before_id_post: string;
|
||||
private before_id_stars: string;
|
||||
private myUsername: string;
|
||||
public activeTab: string = 'posts';
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||
public popoverCtrl: PopoverController, public toastCtrl: ToastController) {
|
||||
|
||||
if (this.navParams.data.user) {
|
||||
this.user = this.navParams.data.user;
|
||||
this.myUsername = this.navParams.data.me;
|
||||
console.log('user: ' + this.user.username);
|
||||
console.log('me: ' + this.myUsername);
|
||||
} else {
|
||||
console.log('err, I need user data!!!');
|
||||
}
|
||||
}
|
||||
|
||||
ionViewDidLoad() {
|
||||
console.log('ionViewDidLoad ProfilePage');
|
||||
let params = {
|
||||
include_deleted: 0,
|
||||
include_raw: 1,
|
||||
count: 40
|
||||
};
|
||||
pnut.postsFrom(this.user.id, params).then(res => {
|
||||
this.posts = this.parseData(res.data);
|
||||
this.before_id_post = res.meta.min_id;
|
||||
});
|
||||
pnut.bookmarks(this.user.id, params).then(res => {
|
||||
this.bookmarks = this.parseData(res.data);
|
||||
this.before_id_stars = res.meta.min_id;
|
||||
});
|
||||
}
|
||||
|
||||
fetchOlderPosts(infiniteScroll, stream) {
|
||||
let before_id = this.before_id_post;
|
||||
let fetcher = pnut.postsFrom;
|
||||
if (stream === 'bookmarks') {
|
||||
before_id = this.before_id_stars;
|
||||
fetcher = pnut.bookmarks;
|
||||
}
|
||||
let params = {
|
||||
include_deleted: 0,
|
||||
include_raw: 1,
|
||||
before_id: before_id,
|
||||
count: 40
|
||||
};
|
||||
fetcher(this.user.id, params).then(res => {
|
||||
if (res.data.length > 0) {
|
||||
if (stream === 'posts') {
|
||||
this.posts.push.apply(this.posts, this.parseData(res.data));
|
||||
this.before_id_post = res.meta.min_id;
|
||||
} else if (stream === 'bookmarks') {
|
||||
this.bookmarks.push.apply(this.bookmarks, this.parseData(res.data));
|
||||
this.before_id_stars = res.meta.min_id;
|
||||
}
|
||||
}
|
||||
infiniteScroll.complete();
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
parseData(data) {
|
||||
var pdata = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (!data[i].is_deleted) {
|
||||
if (data[i]['repost_of']) {
|
||||
data[i] = data[i]['repost_of']
|
||||
var reposted_by_string = "";
|
||||
let rplen = 0;
|
||||
if (typeof data[i]['reposted_by'] !== "undefined") {
|
||||
rplen = data[i]['reposted_by'].length;
|
||||
}
|
||||
for (var j = 0; j < rplen; j++) {
|
||||
reposted_by_string = reposted_by_string + data[i]['reposted_by'][j]['username'] + ", ";
|
||||
}
|
||||
}
|
||||
if (data[i].content) {
|
||||
for (var k = 0; k < data[i]['content']['entities']['mentions'].length; k++) {
|
||||
var men = data[i]['content']['entities']['mentions'][k]['text'];
|
||||
if (this.myUsername === men) {
|
||||
data[i]['you_are_mentioned'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
pdata.push(data[i]);
|
||||
}
|
||||
}
|
||||
return pdata;
|
||||
}
|
||||
|
||||
showUserList(list) {
|
||||
this.navCtrl.push(UserListPage, {
|
||||
userid: this.user.id,
|
||||
username: this.myUsername,
|
||||
list: list});
|
||||
}
|
||||
|
||||
followUser() {
|
||||
if (this.user.you_follow) {
|
||||
pnut.unfollow(this.user.id).then(res => {
|
||||
this.user = res.data;
|
||||
this.presentToast('User unfollowed');
|
||||
});
|
||||
} else {
|
||||
pnut.follow(this.user.id).then(res => {
|
||||
this.user = res.data;
|
||||
this.presentToast('User followed');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
presentProfileMenu(myEvent) {
|
||||
let popover = this.popoverCtrl.create(ProfileMenu, {
|
||||
me: this.myUsername,
|
||||
userid: this.user.id,
|
||||
username: this.user.username,
|
||||
you_muted: this.user.you_muted,
|
||||
you_blocked: this.user.you_blocked});
|
||||
popover.present({ev: myEvent});
|
||||
}
|
||||
|
||||
presentToast(text) {
|
||||
let toast = this.toastCtrl.create({
|
||||
position: 'top',
|
||||
message: text,
|
||||
duration: 2000
|
||||
});
|
||||
toast.present();
|
||||
}
|
||||
|
||||
}
|
|
@ -40,5 +40,13 @@
|
|||
<ion-toggle [(ngModel)]="set_cc" (ionChange)="updateCc()"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>Hide images</h2>
|
||||
<p>Hide images in posts</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="set_hideimg" (ionChange)="updateHideImg()"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
|
|
@ -18,6 +18,7 @@ export class SettingsPage {
|
|||
private set_unified: boolean;
|
||||
private set_cc: boolean;
|
||||
private set_default: string;
|
||||
private set_hideimg: boolean;
|
||||
|
||||
constructor(public navCtrl: NavController, private storage: Storage, public navParams: NavParams,
|
||||
public events: Events) {
|
||||
|
@ -41,6 +42,12 @@ export class SettingsPage {
|
|||
}).catch(err => {
|
||||
console.log('ERROR: ' + err);
|
||||
});
|
||||
|
||||
this.storage.get('hideimg').then((val) => {
|
||||
this.set_hideimg = val;
|
||||
}).catch(err => {
|
||||
console.log('ERROR: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
ionViewDidLeave() {
|
||||
|
@ -59,4 +66,8 @@ export class SettingsPage {
|
|||
this.storage.set('timeline', this.set_default);
|
||||
}
|
||||
|
||||
updateHideImg() {
|
||||
this.storage.set('hideimg', this.set_hideimg);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,20 +1,48 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>New Post</ion-title>
|
||||
|
||||
<ion-buttons start>
|
||||
<button ion-button (click)="dismiss()">
|
||||
<span ion-text color="primary" showWhen="ios">Cancel</span>
|
||||
<ion-icon name="md-close" showWhen="android,windows"></ion-icon>
|
||||
<span ion-text color="primary">Cancel</span>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
<ion-buttons end>
|
||||
<button ion-button>
|
||||
<span ion-text color="primary" showWhen="ios">Post</span>
|
||||
<ion-icon name="send" showWhen="android,windows"></ion-icon>
|
||||
</ion-buttons>
|
||||
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<p>Blarp</p>
|
||||
<ion-card>
|
||||
<ion-card-content>
|
||||
<ion-item>
|
||||
<ion-textarea [(ngModel)]="ptext" autocomplete="true" spellcheck="true" clearInput="true" rows="8"></ion-textarea>
|
||||
</ion-item>
|
||||
</ion-card-content>
|
||||
<ion-row justify-content-end>
|
||||
<ion-col offset-0>
|
||||
<!-- <button ion-button (click)="attachImage()">
|
||||
<ion-icon name="attach"></ion-icon>
|
||||
</button> -->
|
||||
</ion-col>
|
||||
<ion-col col-2><div text-center>{{textCount()}}</div></ion-col>
|
||||
<ion-col col-2>
|
||||
<button ion-button (click)="send()">
|
||||
<ion-icon name="send"></ion-icon>
|
||||
</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<progress id='p' *ngIf="showProgress"></progress>
|
||||
<ion-list>
|
||||
<ion-item *ngFor="let f of files">
|
||||
<ion-thumbnail item-start>
|
||||
<img src="{{ f.link }}">
|
||||
</ion-thumbnail>
|
||||
<p>{{ f.name }}</p>
|
||||
<button ion-button color="dark" clear item-end (click)="unattach(f.id)">
|
||||
<ion-icon name="remove-circle"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
</ion-content>
|
||||
|
|
195
src/pages/stream/new-post.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ViewController, NavParams, ToastController, Events } from 'ionic-angular';
|
||||
import { Storage } from '@ionic/storage';
|
||||
import { FileChooser } from '@ionic-native/file-chooser';
|
||||
import { FilePath } from '@ionic-native/file-path';
|
||||
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
@Component({
|
||||
selector: 'modal-newpost',
|
||||
templateUrl: 'new-post.html',
|
||||
})
|
||||
export class NewPostModal {
|
||||
title: string;
|
||||
replyid: string;
|
||||
ptext: string = "";
|
||||
showProgress: boolean = false;
|
||||
files: Array<Object> = [];
|
||||
fname: string = "";
|
||||
fpath: string = "";
|
||||
options: Object = {};
|
||||
myUsername: string;
|
||||
authToken: string;
|
||||
longpost: Object = {};
|
||||
raw: {type: string, value: Object}[] = [];
|
||||
|
||||
constructor(public navParams: NavParams, public viewCtrl: ViewController, public toastCtrl: ToastController,
|
||||
private fileChooser: FileChooser, private storage: Storage, public events: Events, private filePath: FilePath,
|
||||
private transfer: FileTransfer) {
|
||||
console.log(JSON.stringify(this.navParams));
|
||||
this.myUsername = navParams.data.me;
|
||||
if (navParams.data.type === 'reply') {
|
||||
this.replyid = navParams.data.post.id;
|
||||
this.options = {replyTo: this.replyid};
|
||||
if (navParams.data.post.user.username !== this.myUsername) {
|
||||
this.ptext = "@" + navParams.data.post.user.username + " ";
|
||||
} else {
|
||||
this.ptext = ""
|
||||
}
|
||||
if (navParams.data.post.content.entities) {
|
||||
if (navParams.data.post.content.entities.mentions.length > 0) {
|
||||
this.ptext = this.ptext + this.parseMentions(navParams.data.post.content.entities.mentions);
|
||||
}
|
||||
}
|
||||
this.title = "Reply to " + navParams.data.post.user.username
|
||||
} else if (navParams.data.type === 'quote') {
|
||||
this.ptext = " >> @" + navParams.data.post.user.username + ": " + navParams.data.post.content.text;
|
||||
this.title = "New Post";
|
||||
} else {
|
||||
this.title = "New Post";
|
||||
}
|
||||
this.storage.get('token').then((val) => {
|
||||
this.authToken = val;
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
send() {
|
||||
|
||||
if (this.ptext.length > 254) {
|
||||
this.longpost = {
|
||||
'title': '',
|
||||
'body': this.ptext,
|
||||
'tstamp': new Date().valueOf()
|
||||
}
|
||||
this.ptext = this.ptext.substr(0, 40) + "... - http://chimpnut.nl/u/";
|
||||
this.ptext = this.ptext + this.navParams.data.me + "/lp/{object_id} - #longpost";
|
||||
this.raw.push({
|
||||
type: "nl.chimpnut.blog.post",
|
||||
value: this.longpost
|
||||
});
|
||||
}
|
||||
|
||||
this.options['raw'] = this.raw;
|
||||
pnut.createPost(this.ptext, this.options).then(res => {
|
||||
console.log('-success-');
|
||||
console.log(JSON.stringify(res));
|
||||
this.presentToast("Status posted.");
|
||||
this.events.publish('stream:reload', {});
|
||||
}).catch(err => {
|
||||
console.log('-error posting-');
|
||||
console.log(JSON.stringify(err));
|
||||
});
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
presentToast(text) {
|
||||
let toast = this.toastCtrl.create({
|
||||
position: 'top',
|
||||
message: text,
|
||||
duration: 2000
|
||||
});
|
||||
toast.present();
|
||||
}
|
||||
|
||||
parseMentions(mentions) {
|
||||
let mtext = ""
|
||||
for(var i = 0; i < mentions.length; i++) {
|
||||
let mu = mentions[i].text;
|
||||
if (mu !== this.myUsername) {
|
||||
mtext += "@" + mu + " ";
|
||||
}
|
||||
}
|
||||
if (mtext.length > 0) {
|
||||
if (this.navParams.data.cc) {
|
||||
mtext = "\n/" + mtext;
|
||||
}
|
||||
return mtext;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
textCount() {
|
||||
let counttext = ""
|
||||
let count = 254 - this.ptext.length
|
||||
if (count < 1) {
|
||||
counttext = "longpost"
|
||||
} else {
|
||||
counttext = String(count)
|
||||
}
|
||||
return counttext
|
||||
}
|
||||
|
||||
attachImage() {
|
||||
// console.log('file chooser');
|
||||
const fileTransfer: FileTransferObject = this.transfer.create();
|
||||
|
||||
this.fileChooser.open().then(uri => {
|
||||
console.log('File URI: ' + uri);
|
||||
this.filePath.resolveNativePath(uri).then(filePath => {
|
||||
this.fpath = filePath;
|
||||
this.fname = filePath.split('/').pop();
|
||||
|
||||
let options: FileUploadOptions = {
|
||||
fileKey: 'content',
|
||||
fileName: this.fname,
|
||||
params: {
|
||||
name: this.fname,
|
||||
type: 'com.monkeystew.goober_m',
|
||||
is_public: true
|
||||
},
|
||||
headers: {'Authorization': 'Bearer ' + this.authToken}
|
||||
}
|
||||
|
||||
this.showProgress = true;
|
||||
fileTransfer.upload(this.fpath, 'https://api.pnut.io/v0/files', options).then((response) => {
|
||||
|
||||
let rdata = JSON.parse(response.response);
|
||||
let oembed = {
|
||||
'+io.pnut.core.file': {
|
||||
file_id: rdata.data.id,
|
||||
file_token: rdata.data.file_token,
|
||||
format: 'oembed'
|
||||
}
|
||||
}
|
||||
// console.log(JSON.stringify(oembed));
|
||||
this.raw.push({
|
||||
type: "io.pnut.core.oembed",
|
||||
value: oembed
|
||||
});
|
||||
this.files.push({
|
||||
name: this.fname,
|
||||
link: rdata.data.link,
|
||||
id: this.raw.length - 1
|
||||
});
|
||||
this.showProgress = false;
|
||||
}).catch((err) => { // fileTransfer
|
||||
|
||||
this.showProgress = false;
|
||||
let edata = JSON.parse(err.body);
|
||||
this.presentToast(edata.meta.error_message);
|
||||
});
|
||||
|
||||
}).catch(err => { // filePath
|
||||
console.log('-error getting filepath-');
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
}).catch(err => { // fileChooser
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
unattach(id) {
|
||||
console.log('removing item ' + id);
|
||||
this.raw.splice(id, 1);
|
||||
this.files.splice(id, 1);
|
||||
}
|
||||
}
|
106
src/pages/stream/post-menu.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ViewController, NavParams, ToastController } from 'ionic-angular';
|
||||
import { Events } from 'ionic-angular';
|
||||
import { Device } from '@ionic-native/device';
|
||||
import { ClipboardService } from 'ngx-clipboard';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-list>
|
||||
<button ion-item *ngIf="showShareBtn" (click)="share()">Share</button>
|
||||
<button ion-item (click)="browse()">Open in Browser</button>
|
||||
<button ion-item (click)="copy()">Copy to clipboard</button>
|
||||
<button ion-item (click)="copyPostURL()">Copy link to post</button>
|
||||
<button ion-item *ngIf="showDelBtn" (click)="delete()">Delete</button>
|
||||
</ion-list>
|
||||
`
|
||||
})
|
||||
export class PostMenu {
|
||||
|
||||
showDelBtn: boolean = false;
|
||||
showShareBtn: boolean = false;
|
||||
postURL: string;
|
||||
|
||||
constructor(public navParams: NavParams, public viewCtrl: ViewController, public toastCtrl: ToastController,
|
||||
public events: Events, private clipboardSrv: ClipboardService, private device: Device) {
|
||||
this.postURL = 'https://posts.pnut.io/' + this.navParams.data.post.id;
|
||||
if (navParams.data.me == navParams.data.post.user.username) {
|
||||
this.showDelBtn = true;
|
||||
} else {
|
||||
this.showDelBtn = false;
|
||||
}
|
||||
if (this.device.platform === "Android" || this.device.platform === "amazon-fireos") {
|
||||
this.showShareBtn = true;
|
||||
}
|
||||
}
|
||||
|
||||
browse() {
|
||||
window.open(this.postURL, '_system');
|
||||
this.close();
|
||||
}
|
||||
|
||||
share() {
|
||||
(<any>window).shareContentPlugin.share(this.parsePost(), function(e) {
|
||||
console.log('sharing post:');
|
||||
console.log(JSON.stringify(e));
|
||||
}, function(e) {
|
||||
console.log('sharing failed:');
|
||||
console.log(JSON.stringify(e));
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
|
||||
copy() {
|
||||
this.clipboardSrv.copyFromContent(this.parsePost());
|
||||
this.presentToast('Post copied');
|
||||
this.close();
|
||||
}
|
||||
|
||||
parsePost() {
|
||||
let text = this.navParams.data.post.content.text;
|
||||
let links = this.navParams.data.post.content.entities.links;
|
||||
if (links.length > 0) {
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
text += "\n";
|
||||
if (typeof links[i].title !== "undefined") {
|
||||
text += links[i].title + " - ";
|
||||
}
|
||||
text += links[i].link;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
copyPostURL() {
|
||||
this.clipboardSrv.copyFromContent(this.postURL);
|
||||
this.presentToast('Post link copied');
|
||||
this.close();
|
||||
}
|
||||
|
||||
delete() {
|
||||
pnut.deletePost(this.navParams.data.post.id).then(res => {
|
||||
console.log(res);
|
||||
this.presentToast('Post Deleted');
|
||||
this.events.publish('stream:reload', {});
|
||||
}).catch( err => {
|
||||
console.log('-error-');
|
||||
console.log(err);
|
||||
});
|
||||
this.close()
|
||||
}
|
||||
|
||||
presentToast(text) {
|
||||
let toast = this.toastCtrl.create({
|
||||
position: 'top',
|
||||
message: text,
|
||||
duration: 2000
|
||||
});
|
||||
toast.present();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
|
@ -24,78 +24,7 @@
|
|||
|
||||
<ion-list>
|
||||
<ion-card *ngFor="let post of posts" color="{{ post.you_are_mentioned ? 'mention' : '' }}">
|
||||
<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>
|
||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||
</ion-card>
|
||||
</ion-list>
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
page-stream {
|
||||
.item-md ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-wp ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-ios ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { Component, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { ViewController, NavController, NavParams, ModalController, Content, ToastController, PopoverController } from 'ionic-angular';
|
||||
import { ThreadPage } from '../thread/thread';
|
||||
import { NavController, NavParams, ModalController, Content } 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 { Events } from 'ionic-angular';
|
||||
import { LoginPage } from '../login/login';
|
||||
import { NewPostModal } from '../stream/new-post';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
|
@ -34,16 +31,20 @@ export class StreamPage {
|
|||
showScrollBtn: boolean = false;
|
||||
showUnified: boolean;
|
||||
ccOnReply: boolean = false;
|
||||
hideImg: boolean = false;
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController,
|
||||
private changeDetectorRef: ChangeDetectorRef, public toastCtrl: ToastController, private storage: Storage,
|
||||
public popoverCtrl: PopoverController, public events: Events) {
|
||||
// console.log(JSON.stringify(navParams));
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||
public modalCtrl: ModalController, private storage: Storage,
|
||||
private changeDetectorRef: ChangeDetectorRef, public events: Events) {
|
||||
|
||||
this.storage.get('cc').then((val) => {
|
||||
this.ccOnReply = val;
|
||||
});
|
||||
|
||||
this.storage.get('hideimg').then((val) => {
|
||||
this.hideImg = val;
|
||||
});
|
||||
|
||||
this.storage.get('unified').then((val) => {
|
||||
this.showUnified = val;
|
||||
|
||||
|
@ -207,17 +208,6 @@ export class StreamPage {
|
|||
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() {
|
||||
console.log('-- fetching mentions --');
|
||||
this.fetcher('me', {include_raw: 1, count: 40}).then(res => {
|
||||
|
@ -234,386 +224,13 @@ 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() {
|
||||
let newPostModal = this.modalCtrl.create(NewPostModal, {me: this.myUsername});
|
||||
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() {
|
||||
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,66 +15,11 @@
|
|||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
<ion-card *ngFor="let post of posts">
|
||||
<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>
|
||||
<post [post]="post" [hideImg]="hideImg" [ccOnReply]="ccOnReply" [myUsername]="myUsername"></post>
|
||||
</ion-card>
|
||||
</ion-list>
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
page-thread {
|
||||
.item-md ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-wp ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-ios ion-avatar img {
|
||||
border-radius: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavController, NavParams, ModalController, ToastController, PopoverController } from 'ionic-angular';
|
||||
import { NewPostModal, PostMenu } from '../stream/stream';
|
||||
import { NavController, NavParams } from 'ionic-angular';
|
||||
import { Storage } from '@ionic/storage';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
|
@ -19,89 +19,22 @@ export class ThreadPage {
|
|||
title: string;
|
||||
posts: Array<Object> = [];
|
||||
myUsername: string;
|
||||
hideImg: boolean = false;
|
||||
ccOnReply: boolean = false;
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController,
|
||||
public popoverCtrl: PopoverController, public toastCtrl: ToastController) {
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams,
|
||||
private storage: Storage) {
|
||||
this.posts = this.navParams.data.posts;
|
||||
this.myUsername = this.navParams.data.me;
|
||||
}
|
||||
|
||||
showReplyPost(postData) {
|
||||
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 {
|
||||
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);
|
||||
this.storage.get('hideimg').then((val) => {
|
||||
this.hideImg = val;
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
this.storage.get('cc').then((val) => {
|
||||
this.ccOnReply = val;
|
||||
});
|
||||
toast.present();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
35
src/pages/user-list/user-list.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
Generated template for the UserListPage page.
|
||||
|
||||
See http://ionicframework.com/docs/components/#navigation for more info on
|
||||
Ionic pages and navigation.
|
||||
-->
|
||||
<ion-header>
|
||||
|
||||
<ion-navbar>
|
||||
<ion-title>{{ list }}</ion-title>
|
||||
</ion-navbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content>
|
||||
<ion-list *ngFor="let user of users">
|
||||
<ion-item (click)="showProfile(user)">
|
||||
<ion-avatar item-start>
|
||||
<img src="{{ user.content.avatar_image.link }}">
|
||||
</ion-avatar>
|
||||
<h2>{{ user.name }}</h2>
|
||||
<p>@{{ user.username }}</p>
|
||||
<!-- <ion-col item-end text-right>
|
||||
<button ion-button disabled>{{ user.you_follow ? "Unfollow" : "Follow" }}</button>
|
||||
<ion-note>{{ user.follows_you ? "Follows you" : ""}}</ion-note>
|
||||
</ion-col> -->
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-infinite-scroll (ionInfinite)="fetchMoreUsers($event)">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
</ion-content>
|
14
src/pages/user-list/user-list.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
page-user-list {
|
||||
.item-md ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-wp ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.item-ios ion-avatar img {
|
||||
border-radius: 10px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
71
src/pages/user-list/user-list.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavController, NavParams } from 'ionic-angular';
|
||||
import { ProfilePage } from '../../pages/profile/profile';
|
||||
|
||||
import * as pnut from 'pnut-butter';
|
||||
|
||||
/**
|
||||
* Generated class for the UserListPage page.
|
||||
*
|
||||
* See https://ionicframework.com/docs/components/#navigation for more info on
|
||||
* Ionic pages and navigation.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'page-user-list',
|
||||
templateUrl: 'user-list.html',
|
||||
})
|
||||
export class UserListPage {
|
||||
|
||||
private list: string;
|
||||
private userid: string;
|
||||
private users: Array<Object> = [];
|
||||
private myUsername: string;
|
||||
private before_id: string;
|
||||
private fetcher: any;
|
||||
|
||||
constructor(public navCtrl: NavController, public navParams: NavParams) {
|
||||
this.list = this.navParams.data.list;
|
||||
this.userid = this.navParams.data.userid;
|
||||
this.myUsername = this.navParams.data.username;
|
||||
}
|
||||
|
||||
ionViewDidLoad() {
|
||||
console.log('ionViewDidLoad UserListPage');
|
||||
let params = {
|
||||
include_deleted: 0,
|
||||
include_raw: 1,
|
||||
count: 40
|
||||
};
|
||||
if (this.list == "Followers") {
|
||||
this.fetcher = pnut.followers;
|
||||
} else if (this.list == "Following") {
|
||||
this.fetcher = pnut.following;
|
||||
}
|
||||
this.fetcher(this.userid, params).then(res => {
|
||||
this.users = res.data;
|
||||
this.before_id = res.meta.min_id;
|
||||
});
|
||||
}
|
||||
|
||||
showProfile(user) {
|
||||
this.navCtrl.push(ProfilePage, {user: user, me: this.myUsername});
|
||||
}
|
||||
|
||||
fetchMoreUsers(infiniteScroll) {
|
||||
let params = {
|
||||
include_deleted: 0,
|
||||
include_raw: 1,
|
||||
before_id: this.before_id,
|
||||
count: 40
|
||||
};
|
||||
this.fetcher(this.userid, params).then(res => {
|
||||
if (res.data.length > 0) {
|
||||
this.users.push.apply(this.users, res.data);
|
||||
this.before_id = res.meta.min_id;
|
||||
}
|
||||
infiniteScroll.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ export class ParserPipe implements PipeTransform {
|
|||
|
||||
transform(value: string, ...args): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
|
||||
let hregex = /href="([\S]+)"/g;
|
||||
// value = value.replace(hregex, "class=\"ex-link\" href=");
|
||||
if (typeof value == "undefined") value = "";
|
||||
value = value.replace(hregex, "onClick=\"window.open('$1', '_system', 'location=yes')\"");
|
||||
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { OAuthProvider } from 'ng2-cordova-oauth/provider';
|
||||
|
||||
export class PnutAuth extends OAuthProvider {
|
||||
|
||||
protected authUrl: string = 'https://pnut.io/oauth/authenticate';
|
||||
protected defaults: Object = {
|
||||
responseType: 'token',
|
||||
clientId: '' // Insert your client ID and rename this file to pnut-oauth.ts
|
||||
};
|
||||
|
||||
}
|
||||
|
1
ubuntutouch/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
build
|
1
ubuntutouch/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../LICENSE
|
1
ubuntutouch/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../README.md
|
7
ubuntutouch/clickable.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"template": "pure",
|
||||
"kill": "webapp-container",
|
||||
"ignore": [
|
||||
".git"
|
||||
]
|
||||
}
|
9
ubuntutouch/goober.apparmor
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"template": "ubuntu-webapp",
|
||||
"policy_groups": [
|
||||
"webview",
|
||||
"audio",
|
||||
"networking"
|
||||
],
|
||||
"policy_version": 16.04
|
||||
}
|
7
ubuntutouch/goober.desktop
Normal file
|
@ -0,0 +1,7 @@
|
|||
[Desktop Entry]
|
||||
Name=Goober
|
||||
Exec=webapp-container --app-id="goober.thrrgilag" $@ www/index.html
|
||||
Icon=icon.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
X-Ubuntu-Touch=true
|
1
ubuntutouch/icon.png
Symbolic link
|
@ -0,0 +1 @@
|
|||
../resources/icon-144.png
|
15
ubuntutouch/manifest.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "goober.thrrgilag",
|
||||
"description": "A pnut.io client",
|
||||
"architecture": "all",
|
||||
"title": "Goober",
|
||||
"hooks": {
|
||||
"goober": {
|
||||
"apparmor": "goober.apparmor",
|
||||
"desktop": "goober.desktop"
|
||||
}
|
||||
},
|
||||
"version": "0.8.0",
|
||||
"maintainer": "Morgan McMillian <gilag@monkeystew.com>",
|
||||
"framework" : "ubuntu-sdk-16.04"
|
||||
}
|
1
ubuntutouch/www
Symbolic link
|
@ -0,0 +1 @@
|
|||
../www
|