summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--lib/Controller/NavigationController.php7
-rw-r--r--lib/Db/CacheActorsRequest.php26
-rw-r--r--lib/Db/FollowsRequestBuilder.php16
-rw-r--r--package-lock.json12
-rw-r--r--package.json3
-rw-r--r--src/App.vue14
-rw-r--r--src/components/Composer.vue16
-rw-r--r--src/components/ProfileInfo.vue9
-rw-r--r--src/components/Search.vue36
-rw-r--r--src/components/UserEntry.vue21
-rw-r--r--src/mixins/currentUserMixin.js8
-rw-r--r--src/mixins/follow.js34
-rw-r--r--src/mixins/serverData.js29
14 files changed, 185 insertions, 52 deletions
diff --git a/README.md b/README.md
index 8d65bc79..228dee07 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Mockup:
![](img/screenshot.png)
-- **๐Ÿ™‹ Find your friends:** No matter if they use Nextcloud, [Mastodon](https://en.wikipedia.org/wiki/Mastodon_(software)), [Friendica](https://en.wikipedia.org/wiki/Friendica), [GNU social](https://en.wikipedia.org/wiki/GNU_social) or others โ€“ you can follow them!
+- **๐Ÿ™‹ Find your friends:** No matter if they use Nextcloud, [๐Ÿ˜ Mastodon](https://joinmastodon.org), [๐Ÿ‡ซ Friendica](https://friendi.ca), and soon [โœฑ diaspora*](https://joindiaspora.com), [๐Ÿ‘น MediaGoblin](https://www.mediagoblin.org) and more โ€“ you can follow them!
- **๐Ÿ“œ Profile info:** No need to fill out more profiles โ€“ your info from Nextcloud will be used and extended.
- **๐Ÿ‘ Own your posts:** Everything you post stays on your Nextcloud!
- **๐Ÿ•ธ Open standards:** We use the [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub) standard!
@@ -13,8 +13,8 @@ Mockup:
## Development setup
-1. โ˜ Clone this into your `apps` folder of your Nextcloud
-2. ๐Ÿ‘ฉโ€๐Ÿ’ป In a terminal, run the command `make dev-setup` to install the dependencies
+1. โ˜ Clone this into the `apps` folder of your Nextcloud: `git clone https://github.com/nextcloud-gmbh/social.git`
+2. ๐Ÿ‘ฉโ€๐Ÿ’ป Run `make dev-setup` to install the dependencies
3. ๐Ÿ— Then to build the Javascript whenever you make changes, run `make build-js`
4. โœ… Enable the app through the app management of your Nextcloud
5. ๐ŸŽ‰ Partytime!
diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php
index 82713dbd..b14f46bd 100644
--- a/lib/Controller/NavigationController.php
+++ b/lib/Controller/NavigationController.php
@@ -45,6 +45,7 @@ use OCA\Social\Service\ActorService;
use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\RedirectResponse;
@@ -186,7 +187,11 @@ class NavigationController extends Controller {
// neither.
}
- return new TemplateResponse(Application::APP_NAME, 'main', $data);
+ $csp = new ContentSecurityPolicy();
+ $csp->addAllowedImageDomain('*');
+ $response = new TemplateResponse(Application::APP_NAME, 'main', $data);
+ $response->setContentSecurityPolicy($csp);
+ return $response;
}
diff --git a/lib/Db/CacheActorsRequest.php b/lib/Db/CacheActorsRequest.php
index 85c4a2e8..1be44384 100644
--- a/lib/Db/CacheActorsRequest.php
+++ b/lib/Db/CacheActorsRequest.php
@@ -202,6 +202,32 @@ class CacheActorsRequest extends CacheActorsRequestBuilder {
/**
+ * get Cached version of a local Actor, based on the preferred username
+ *
+ * @param string $account
+ *
+ * @return Person
+ * @throws CacheActorDoesNotExistException
+ */
+ public function getFromLocalAccount(string $account): Person {
+ $qb = $this->getCacheActorsSelectSql();
+ $this->limitToPreferredUsername($qb, $account);
+ $this->limitToLocal($qb, true);
+ $this->leftJoinCacheDocuments($qb, 'icon_id');
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ if ($data === false) {
+ throw new CacheActorDoesNotExistException();
+ }
+
+ return $this->parseCacheActorsSelectSql($data);
+ }
+
+
+ /**
* @param string $search
* @param string $viewerId
*
diff --git a/lib/Db/FollowsRequestBuilder.php b/lib/Db/FollowsRequestBuilder.php
index 0f50b37e..3a1d9445 100644
--- a/lib/Db/FollowsRequestBuilder.php
+++ b/lib/Db/FollowsRequestBuilder.php
@@ -109,6 +109,22 @@ class FollowsRequestBuilder extends CoreRequestBuilder {
/**
+ * Base of the Sql Select request for Shares
+ *
+ * @return IQueryBuilder
+ */
+ protected function countFollowsSelectSql(): IQueryBuilder {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count')
+ ->from(self::TABLE_SERVER_FOLLOWS, 'f');
+
+ $this->defaultSelectAlias = 'f';
+
+ return $qb;
+ }
+
+
+ /**
* Base of the Sql Delete request
*
* @return IQueryBuilder
diff --git a/package-lock.json b/package-lock.json
index 7ea690f1..0669807a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7631,9 +7631,9 @@
}
},
"nextcloud-vue": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/nextcloud-vue/-/nextcloud-vue-0.4.2.tgz",
- "integrity": "sha512-4aePhl0VqpJw9LqNsQenPFmQ6I715vbOAlfVjPMdqVqoKnN9r2gr87/PfNeOLkVFrfo0vaYPx4QS0JSiLsAiqg==",
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/nextcloud-vue/-/nextcloud-vue-0.4.6.tgz",
+ "integrity": "sha512-INrIz3RmxxUCrM/xy2ytLvrrZr131p0DOT87A+IH0/+LFlfK//eR0uB32lSUsqh9Tb+bkTyu8Ztq9iuTrFfl2Q==",
"requires": {
"@babel/polyfill": "^7.0.0",
"md5": "^2.2.1",
@@ -8239,9 +8239,9 @@
"dev": true
},
"popper.js": {
- "version": "1.14.5",
- "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.5.tgz",
- "integrity": "sha512-fs4Sd8bZLgEzrk8aS7Em1qh+wcawtE87kRUJQhK6+LndyV1HerX7+LURzAylVaTyWIn5NTB/lyjnWqw/AZ6Yrw=="
+ "version": "1.14.6",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz",
+ "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA=="
},
"posix-character-classes": {
"version": "0.1.1",
diff --git a/package.json b/package.json
index c8786392..667b9fc7 100644
--- a/package.json
+++ b/package.json
@@ -30,10 +30,11 @@
"@babel/polyfill": "^7.0.0",
"linkifyjs": "^2.1.7",
"nextcloud-axios": "^0.1.2",
- "nextcloud-vue": "^0.4.2",
+ "nextcloud-vue": "^0.4.6",
"tributejs": "^3.3.5",
"twemoji": "^11.2.0",
"uuid": "^3.3.2",
+ "v-tooltip": "^2.0.0-rc.33",
"vue": "^2.5.16",
"vue-click-outside": "^1.0.7",
"vue-contenteditable-directive": "^1.2.0",
diff --git a/src/App.vue b/src/App.vue
index d27f5c4d..8d1f4c34 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -63,6 +63,7 @@ import axios from 'nextcloud-axios'
import TimelineEntry from './components/TimelineEntry'
import ProfileInfo from './components/ProfileInfo'
import Search from './components/Search'
+import currentuserMixin from './mixins/currentUserMixin'
export default {
name: 'App',
@@ -75,6 +76,7 @@ export default {
ProfileInfo,
Search
},
+ mixins: [currentuserMixin],
data: function() {
return {
infoHidden: false,
@@ -84,21 +86,9 @@ export default {
}
},
computed: {
- url: function() {
- return OC.linkTo('social', 'img/nextcloud.png')
- },
- currentUser: function() {
- return OC.getCurrentUser()
- },
- socialId: function() {
- return '@' + OC.getCurrentUser().uid + '@' + OC.getHost()
- },
timeline: function() {
return this.$store.getters.getTimeline
},
- serverData: function() {
- return this.$store.getters.getServerData
- },
menu: function() {
let defaultCategories = [
{
diff --git a/src/components/Composer.vue b/src/components/Composer.vue
index f3fb5bbc..66794749 100644
--- a/src/components/Composer.vue
+++ b/src/components/Composer.vue
@@ -28,7 +28,6 @@
<form class="new-post-form" @submit.prevent="createPost">
<div class="author currentUser">
{{ currentUser.displayName }}
-
<span class="social-id">{{ socialId }}</span>
</div>
<vue-tribute :options="tributeOptions">
@@ -385,29 +384,42 @@ export default {
}
}
},
+ activeState() {
+ return (type) => {
+ if (type === this.type) {
+ return true
+ } else {
+ return false
+ }
+ }
+ },
visibilityPopover() {
return [
{
action: () => { this.switchType('direct') },
icon: this.visibilityIconClass('direct'),
+ active: this.activeState('direct'),
text: t('social', 'Direct'),
longtext: t('social', 'Post to mentioned users only')
},
{
action: () => { this.switchType('unlisted') },
icon: this.visibilityIconClass('unlisted'),
+ active: this.activeState('unlisted'),
text: t('social', 'Unlisted'),
longtext: t('social', 'Do not post to public timelines')
},
{
action: () => { this.switchType('followers') },
icon: this.visibilityIconClass('followers'),
+ active: this.activeState('followers'),
text: t('social', 'Followers'),
longtext: t('social', 'Post to followers only')
},
{
action: () => { this.switchType('public') },
icon: this.visibilityIconClass('public'),
+ active: this.activeState('public'),
text: t('social', 'Public'),
longtext: t('social', 'Post to public timelines')
}
@@ -434,7 +446,7 @@ export default {
emoji.replaceWith(em)
})
let to = []
- const re = /@((\w+)(@[\w.]+)?)/g
+ const re = /@(([\w-_.]+)(@[\w-.]+)?)/g
let match = null
do {
match = re.exec(element.innerText)
diff --git a/src/components/ProfileInfo.vue b/src/components/ProfileInfo.vue
index d1f2bd0c..6506e125 100644
--- a/src/components/ProfileInfo.vue
+++ b/src/components/ProfileInfo.vue
@@ -81,12 +81,14 @@
<script>
import { Avatar } from 'nextcloud-vue'
+import serverData from '../mixins/serverData'
export default {
name: 'ProfileInfo',
components: {
Avatar
},
+ mixins: [serverData],
props: {
uid: {
type: String,
@@ -98,17 +100,12 @@ export default {
if (typeof this.accountInfo.displayname !== 'undefined') { return this.accountInfo.displayname.value || '' }
return this.uid
},
- serverData: function() {
- return this.$store.getters.getServerData
- },
accountInfo: function() {
return this.$store.getters.getAccount(this.uid)
}
},
methods: {
- follow() {
- // TODO: implement following users
- }
+
}
}
diff --git a/src/components/Search.vue b/src/components/Search.vue
index 0cecb528..be9165ff 100644
--- a/src/components/Search.vue
+++ b/src/components/Search.vue
@@ -22,15 +22,13 @@
<template>
<div class="social__wrapper">
- <div v-if="results.length < 1" id="emptycontent" :class="{'icon-loading': loading}"
- class="">
- <div class="icon-search" />
- <h2>{{ t('social', 'No accounts found') }}</h2>
- <p>No accounts found for {{ term }}</p>
+ <div v-if="results.length < 1" id="emptycontent" :class="{'icon-loading': loading}">
+ <div v-if="!loading" class="icon-search" />
+ <h2 v-if="!loading">{{ t('social', 'No accounts found') }}</h2>
+ <p v-if="!loading">No accounts found for {{ term }}</p>
</div>
<div v-if="match || results.length > 0">
<h3>{{ t('social', 'Search') }} {{ term }}</h3>
- <UserEntry :item="match" />
<UserEntry v-for="result in results" :key="result.id" :item="result" />
</div>
</div>
@@ -65,20 +63,32 @@ export default {
},
watch: {
term(val) {
+ // TODO: debounce
+ this.search(val)
+ }
+ },
+ beforeMount() {
+ this.search(this.term)
+ },
+ methods: {
+ search(val) {
this.loading = true
- this.accountSearch(val).then((response) => {
- this.results = response.data.result.accounts
- this.loading = false
- })
const re = /@((\w+)(@[\w.]+)?)/g
if (val.match(re)) {
this.remoteSearch(val).then((response) => {
this.match = response.data.result.account
+ this.accountSearch(val).then((response) => {
+ this.results = response.data.result.accounts
+ this.loading = false
+ })
}).catch((e) => { this.match = null })
+ } else {
+ this.accountSearch(val).then((response) => {
+ this.results = response.data.result.accounts
+ this.loading = false
+ })
}
- }
- },
- methods: {
+ },
accountSearch(term) {
this.loading = true
return axios.get(OC.generateUrl('apps/social/api/v1/accounts/search?search=' + term))
diff --git a/src/components/UserEntry.vue b/src/components/UserEntry.vue
index b615d23c..ecf6f3a7 100644
--- a/src/components/UserEntry.vue
+++ b/src/components/UserEntry.vue
@@ -21,40 +21,47 @@
-->
<template>
- <div class="user-entry">
+ <div v-if="item" class="user-entry">
<div class="entry-content">
<div class="user-avatar">
<avatar v-if="item.local" :size="32" :user="item.preferredUsername" />
- <avatar v-else url="" />
+ <avatar v-else :url="item.icon.url" />
</div>
<div class="user-details">
<router-link v-if="item.local" :to="{ name: 'profile', params: { account: item.account }}">
<span class="post-author">{{ item.preferredUsername }}</span>
</router-link>
<a v-else :href="item.id" target="_blank"
- rel="noreferrer">{{ item.preferredUsername }}</a>
- <p class="user-description">{{ item.account }}</p>
+ rel="noreferrer">{{ item.name }} <span class="user-description">{{ item.account }}</span></a>
+ <!-- TODO check where the html is coming from to avoid security issues -->
+ <p v-html="item.summary" />
</div>
- <button v-if="item.following" class="icon-checkmark-color">Following</button>
- <button v-else class="primary">Follow</button>
+ <button class="icon-checkmark-color" @click="unfollow()"
+ @mouseover="followingText=t('social', 'Unfollow')"
+ @mouseleave="followingText=t('social', 'Following')">{{ followingText }}</button>
+ <button class="primary" @click="follow">Follow</button>
</div>
</div>
</template>
<script>
import { Avatar } from 'nextcloud-vue'
+import follow from '../mixins/follow'
export default {
name: 'UserEntry',
components: {
Avatar
},
+ mixins: [
+ follow
+ ],
props: {
item: { type: Object, default: () => {} }
},
data: function() {
return {
-
+ followingText: t('social', 'Following')
}
}
}
diff --git a/src/mixins/currentUserMixin.js b/src/mixins/currentUserMixin.js
index 434aeac5..8bc5adfa 100644
--- a/src/mixins/currentUserMixin.js
+++ b/src/mixins/currentUserMixin.js
@@ -20,13 +20,19 @@
*
*/
+import serverData from './serverData'
export default {
+ mixins: [
+ serverData
+ ],
computed: {
currentUser: function() {
return OC.getCurrentUser()
},
socialId: function() {
- return '@' + OC.getCurrentUser().uid + '@' + OC.getHost()
+ const url = document.createElement('a')
+ url.setAttribute('href', this.serverData.cloudAddress)
+ return '@' + OC.getCurrentUser().uid + '@' + url.hostname
}
}
}
diff --git a/src/mixins/follow.js b/src/mixins/follow.js
new file mode 100644
index 00000000..09fde974
--- /dev/null
+++ b/src/mixins/follow.js
@@ -0,0 +1,34 @@
+/*
+ * @copyright Copyright (c) 2018 Julius Hรคrtl <jus@bitgrid.net>
+ *
+ * @author Julius Hรคrtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import axios from 'nextcloud-axios'
+
+export default {
+ methods: {
+ follow() {
+ return axios.put(OC.generateUrl('/apps/social/api/v1/account/follow?account=' + this.item.account))
+ },
+ unfollow() {
+ return axios.delete(OC.generateUrl('/apps/social/api/v1/account/follow?account=' + this.item.account))
+ }
+ }
+}
diff --git a/src/mixins/serverData.js b/src/mixins/serverData.js
new file mode 100644
index 00000000..24783e3c
--- /dev/null
+++ b/src/mixins/serverData.js
@@ -0,0 +1,29 @@
+/*
+ * @copyright Copyright (c) 2018 Julius Hรคrtl <jus@bitgrid.net>
+ *
+ * @author Julius Hรคrtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+export default {
+ computed: {
+ serverData: function() {
+ return this.$store.getters.getServerData
+ }
+ }
+}