sforkowany z mirror/social
Let's not support oci yet
Signed-off-by: Carl Schwan <carl@carlschwan.eu>update-master-target-versions
rodzic
ffe5f1bacb
commit
db3cb63810
72
.eslintrc.js
72
.eslintrc.js
|
@ -1,72 +1,8 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
jest: true
|
||||
},
|
||||
globals: {
|
||||
t: true,
|
||||
n: true,
|
||||
OC: true,
|
||||
OCA: true,
|
||||
Vue: true,
|
||||
VueRouter: true
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
ecmaVersion: 6
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:node/recommended',
|
||||
'plugin:vue/essential',
|
||||
'plugin:vue/recommended',
|
||||
'standard'
|
||||
'@nextcloud'
|
||||
],
|
||||
plugins: ['vue', 'node'],
|
||||
rules: {
|
||||
// space before function ()
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
// curly braces always space
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
// stay consistent with array brackets
|
||||
'array-bracket-newline': ['error', 'consistent'],
|
||||
// 1tbs brace style
|
||||
'brace-style': 'error',
|
||||
// tabs only
|
||||
indent: ['error', 'tab'],
|
||||
'no-tabs': 0,
|
||||
'vue/html-indent': ['error', 'tab'],
|
||||
// only debug console
|
||||
'no-console': ['error', { allow: ['error', 'warn', 'debug'] }],
|
||||
// classes blocks
|
||||
'padded-blocks': ['error', { classes: 'always' }],
|
||||
// always have the operator in front
|
||||
'operator-linebreak': ['error', 'before'],
|
||||
// ternary on multiline
|
||||
'multiline-ternary': ['error', 'always-multiline'],
|
||||
// es6 import/export and require
|
||||
'node/no-unpublished-require': ['off'],
|
||||
'node/no-unsupported-features/es-syntax': ['off'],
|
||||
// space before self-closing elements
|
||||
'vue/html-closing-bracket-spacing': 'error',
|
||||
// newline before closing bracket
|
||||
'vue/html-closing-bracket-newline': ["error", {
|
||||
"singleline": "never",
|
||||
"multiline": "never"
|
||||
}],
|
||||
// code spacing with attributes
|
||||
'vue/max-attributes-per-line': [
|
||||
'error',
|
||||
{
|
||||
singleline: 3,
|
||||
multiline: {
|
||||
max: 3,
|
||||
allowFirstLine: true
|
||||
}
|
||||
}
|
||||
]
|
||||
globals: {
|
||||
appName: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: PHPUnit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'appinfo/**'
|
||||
- 'lib/**'
|
||||
- 'templates/**'
|
||||
- 'tests/**'
|
||||
- 'vendor/**'
|
||||
- 'vendor-bin/**'
|
||||
- '.php-cs-fixer.dist.php'
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- stable*
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: phpunit-oci-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Location of the phpunit.xml and phpunit.integration.xml files
|
||||
PHPUNIT_CONFIG: ./tests/phpunit.xml
|
||||
PHPUNIT_INTEGRATION_CONFIG: ./tests/phpunit.integration.xml
|
||||
|
||||
jobs:
|
||||
phpunit-oci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.0']
|
||||
server-versions: ['master']
|
||||
|
||||
services:
|
||||
oracle:
|
||||
image: deepdiver/docker-oracle-xe-11g # 'wnameless/oracle-xe-11g-r2'
|
||||
ports:
|
||||
- 1521:1521/tcp
|
||||
|
||||
steps:
|
||||
- name: Set app env
|
||||
run: |
|
||||
# Split and keep last
|
||||
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
repository: nextcloud/server
|
||||
ref: ${{ matrix.server-versions }}
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, fileinfo, intl, sqlite, pdo_sqlite, oci8
|
||||
tools: phpunit
|
||||
coverage: none
|
||||
|
||||
- name: Check composer file existence
|
||||
id: check_composer
|
||||
uses: andstor/file-existence-action@v2
|
||||
with:
|
||||
files: apps/${{ env.APP_NAME }}/composer.json
|
||||
|
||||
- name: Set up PHPUnit
|
||||
# Only run if phpunit config file exists
|
||||
if: steps.check_composer.outputs.files_exists == 'true'
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: composer i
|
||||
|
||||
- name: Set up Nextcloud
|
||||
env:
|
||||
DB_PORT: 1521
|
||||
run: |
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=owncloud --admin-user admin --admin-pass admin
|
||||
./occ app:enable --force ${{ env.APP_NAME }}
|
||||
|
||||
- name: Check PHPUnit config file existence
|
||||
id: check_phpunit
|
||||
uses: andstor/file-existence-action@v2
|
||||
with:
|
||||
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_CONFIG }}
|
||||
|
||||
- name: PHPUnit
|
||||
# Only run if phpunit config file exists
|
||||
if: steps.check_phpunit.outputs.files_exists == 'true'
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_CONFIG }}
|
||||
|
||||
- name: Check PHPUnit integration config file existence
|
||||
id: check_integration
|
||||
uses: andstor/file-existence-action@v2
|
||||
with:
|
||||
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||
|
||||
- name: Run Nextcloud
|
||||
# Only run if phpunit integration config file exists
|
||||
if: steps.check_integration.outputs.files_exists == 'true'
|
||||
run: php -S localhost:8080 &
|
||||
|
||||
- name: PHPUnit integration
|
||||
# Only run if phpunit integration config file exists
|
||||
if: steps.check_integration.outputs.files_exists == 'true'
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||
|
||||
summary:
|
||||
permissions:
|
||||
contents: none
|
||||
runs-on: ubuntu-latest
|
||||
needs: phpunit-oci
|
||||
|
||||
if: always()
|
||||
|
||||
name: phpunit-oci-summary
|
||||
|
||||
steps:
|
||||
- name: Summary status
|
||||
run: if ${{ needs.phpunit-oci.result != 'success' }}; then exit 1; fi
|
|
@ -11,7 +11,9 @@
|
|||
"dependencies": {
|
||||
"@nextcloud/auth": "^2.0.0",
|
||||
"@nextcloud/axios": "^2.0.0",
|
||||
"@nextcloud/dialogs": "^3.2.0",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/logger": "^2.2.1",
|
||||
"@nextcloud/moment": "^1.2.1",
|
||||
"@nextcloud/router": "^1.2.0",
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
"dependencies": {
|
||||
"@nextcloud/auth": "^2.0.0",
|
||||
"@nextcloud/axios": "^2.0.0",
|
||||
"@nextcloud/dialogs": "^3.2.0",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/logger": "^2.2.1",
|
||||
"@nextcloud/moment": "^1.2.1",
|
||||
"@nextcloud/router": "^1.2.0",
|
||||
|
|
59
src/App.vue
59
src/App.vue
|
@ -2,8 +2,11 @@
|
|||
<NcContent v-if="!serverData.setup" app-name="social" :class="{public: serverData.public}">
|
||||
<NcAppNavigation v-if="!serverData.public">
|
||||
<template #list>
|
||||
<NcAppNavigationItem v-for="item in menu.items" :key="item.key" :to="item.to"
|
||||
:title="item.title" :exact="true">
|
||||
<NcAppNavigationItem v-for="item in menu.items"
|
||||
:key="item.key"
|
||||
:to="item.to"
|
||||
:title="item.title"
|
||||
:exact="true">
|
||||
<template #icon>
|
||||
<component :is="item.icon" />
|
||||
</template>
|
||||
|
@ -94,27 +97,27 @@ export default {
|
|||
Search,
|
||||
},
|
||||
mixins: [currentuserMixin],
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
state: [],
|
||||
cloudAddress: '',
|
||||
searchTerm: ''
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
timeline: function() {
|
||||
timeline() {
|
||||
return this.$store.getters.getTimeline
|
||||
},
|
||||
menu: function() {
|
||||
menu() {
|
||||
const defaultCategories = [
|
||||
{
|
||||
id: 'social-timeline',
|
||||
icon: Home,
|
||||
title: t('social', 'Home'),
|
||||
to: {
|
||||
name: 'timeline'
|
||||
}
|
||||
name: 'timeline',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-direct-messages',
|
||||
|
@ -122,8 +125,8 @@ export default {
|
|||
title: t('social', 'Direct messages'),
|
||||
to: {
|
||||
name: 'timeline',
|
||||
params: { type: 'direct' }
|
||||
}
|
||||
params: { type: 'direct' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-notifications',
|
||||
|
@ -131,8 +134,8 @@ export default {
|
|||
title: t('social', 'Notifications'),
|
||||
to: {
|
||||
name: 'timeline',
|
||||
params: { type: 'notifications' }
|
||||
}
|
||||
params: { type: 'notifications' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-account',
|
||||
|
@ -140,8 +143,8 @@ export default {
|
|||
title: t('social', 'Profile'),
|
||||
to: {
|
||||
name: 'profile',
|
||||
params: { account: this.currentUser.uid }
|
||||
}
|
||||
params: { account: this.currentUser.uid },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-liked',
|
||||
|
@ -149,8 +152,8 @@ export default {
|
|||
title: t('social', 'Liked'),
|
||||
to: {
|
||||
name: 'timeline',
|
||||
params: { type: 'liked' }
|
||||
}
|
||||
params: { type: 'liked' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-local',
|
||||
|
@ -158,8 +161,8 @@ export default {
|
|||
title: t('social', 'Local timeline'),
|
||||
to: {
|
||||
name: 'timeline',
|
||||
params: { type: 'timeline' }
|
||||
}
|
||||
params: { type: 'timeline' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'social-global',
|
||||
|
@ -167,22 +170,22 @@ export default {
|
|||
title: t('social', 'Global timeline'),
|
||||
to: {
|
||||
name: 'timeline',
|
||||
params: { type: 'federated' }
|
||||
}
|
||||
}
|
||||
params: { type: 'federated' },
|
||||
},
|
||||
},
|
||||
]
|
||||
return {
|
||||
items: defaultCategories,
|
||||
loading: false
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
this.searchTerm = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount: function() {
|
||||
beforeMount() {
|
||||
// importing server data into the store
|
||||
this.$store.commit('setServerData', loadState('social', 'serverData'))
|
||||
|
||||
|
@ -212,7 +215,7 @@ export default {
|
|||
resetSearch() {
|
||||
this.searchTerm = ''
|
||||
},
|
||||
fromPushApp: function(data) {
|
||||
fromPushApp(data) {
|
||||
// FIXME: might be better to use Timeline.type() ?
|
||||
let timeline = 'home'
|
||||
if (this.$route.name === 'tags') {
|
||||
|
@ -227,8 +230,8 @@ export default {
|
|||
if (data.source === 'timeline.direct' && timeline === 'direct') {
|
||||
this.$store.dispatch('addToTimeline', [data.payload])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -21,9 +21,16 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<NcAvatar v-if="actor.local" :size="size" :user="actor.preferredUsername"
|
||||
:display-name="actor.account" :disable-tooltip="true" :showUserStatus="false" />
|
||||
<NcAvatar v-else :size="size" :url="avatarUrl" :showUserStatus="false"
|
||||
<NcAvatar v-if="actor.local"
|
||||
:size="size"
|
||||
:user="actor.preferredUsername"
|
||||
:display-name="actor.account"
|
||||
:disable-tooltip="true"
|
||||
:show-user-status="false" />
|
||||
<NcAvatar v-else
|
||||
:size="size"
|
||||
:url="avatarUrl"
|
||||
:show-user-status="false"
|
||||
:disable-tooltip="true" />
|
||||
</template>
|
||||
|
||||
|
@ -34,21 +41,21 @@ import { generateUrl } from '@nextcloud/router'
|
|||
export default {
|
||||
name: 'ActorAvatar',
|
||||
components: {
|
||||
NcAvatar
|
||||
NcAvatar,
|
||||
},
|
||||
props: {
|
||||
actor: { type: Object, default: () => {} },
|
||||
size: { type: Number, default: 32 }
|
||||
size: { type: Number, default: 32 },
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
followingText: t('social', 'Following')
|
||||
followingText: t('social', 'Following'),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatarUrl() {
|
||||
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.item.attributedTo)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -25,14 +25,16 @@
|
|||
<div class="new-post" data-id="">
|
||||
<input id="file-upload"
|
||||
ref="fileUploadInput"
|
||||
@change="handleFileChange($event)"
|
||||
multiple
|
||||
type="file"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
class="hidden-visually">
|
||||
class="hidden-visually"
|
||||
@change="handleFileChange($event)">
|
||||
<div class="new-post-author">
|
||||
<NcAvatar :user="currentUser.uid" :display-name="currentUser.displayName" :disable-tooltip="true"
|
||||
<NcAvatar :user="currentUser.uid"
|
||||
:display-name="currentUser.displayName"
|
||||
:disable-tooltip="true"
|
||||
:size="32" />
|
||||
<div class="post-author">
|
||||
<span class="post-author-name">
|
||||
|
@ -46,11 +48,12 @@
|
|||
<div v-if="replyTo" class="reply-to">
|
||||
<p class="reply-info">
|
||||
<span>{{ t('social', 'In reply to') }}</span>
|
||||
<actor-avatar :actor="replyTo.actor_info" :size="16" />
|
||||
<ActorAvatar :actor="replyTo.actor_info" :size="16" />
|
||||
<strong>{{ replyTo.actor_info.account }}</strong>
|
||||
<NcButton type="tertiary" class="close-button"
|
||||
@click="closeReply"
|
||||
:aria-label="t('social', 'Close reply')">
|
||||
<NcButton type="tertiary"
|
||||
class="close-button"
|
||||
:aria-label="t('social', 'Close reply')"
|
||||
@click="closeReply">
|
||||
<template #icon>
|
||||
<Close :size="20" />
|
||||
</template>
|
||||
|
@ -61,33 +64,42 @@
|
|||
</div>
|
||||
</div>
|
||||
<form class="new-post-form" @submit.prevent="createPost">
|
||||
<vue-tribute :options="tributeOptions">
|
||||
<VueTribute :options="tributeOptions">
|
||||
<!-- eslint-disable-next-line vue/valid-v-model -->
|
||||
<div ref="composerInput" v-contenteditable:post.dangerousHTML="canType && !loading" class="message"
|
||||
placeholder="What would you like to share?" :class="{'icon-loading': loading}" @keyup.prevent.enter="keyup"
|
||||
<div ref="composerInput"
|
||||
v-contenteditable:post.dangerousHTML="canType && !loading"
|
||||
class="message"
|
||||
placeholder="What would you like to share?"
|
||||
:class="{'icon-loading': loading}"
|
||||
@keyup.prevent.enter="keyup"
|
||||
@tribute-replaced="updatePostFromTribute" />
|
||||
</vue-tribute>
|
||||
</VueTribute>
|
||||
|
||||
<PreviewGrid :uploading="false" :uploadProgress="0.4" :miniatures="previewUrls" />
|
||||
<PreviewGrid :uploading="false"
|
||||
:upload-progress="0.4"
|
||||
:miniatures="previewUrls"
|
||||
@deleted="deletePreview" />
|
||||
|
||||
<div class="options">
|
||||
<NcButton type="tertiary"
|
||||
@click.prevent="clickImportInput"
|
||||
<NcButton v-tooltip="t('social', 'Add attachment')"
|
||||
type="tertiary"
|
||||
:aria-label="t('social', 'Add attachment')"
|
||||
v-tooltip="t('social', 'Add attachment')">
|
||||
@click.prevent="clickImportInput">
|
||||
<template #icon>
|
||||
<FileUpload :size="22" decorative title="" />
|
||||
</template>
|
||||
</NcButton>
|
||||
|
||||
<div class="new-post-form__emoji-picker">
|
||||
<NcEmojiPicker ref="emojiPicker" :search="search" :close-on-select="false"
|
||||
<NcEmojiPicker ref="emojiPicker"
|
||||
:search="search"
|
||||
:close-on-select="false"
|
||||
:container="container"
|
||||
@select="insert">
|
||||
<NcButton type="tertiary"
|
||||
<NcButton v-tooltip="t('social', 'Add emoji')"
|
||||
type="tertiary"
|
||||
:aria-haspopup="true"
|
||||
:aria-label="t('social', 'Add emoji')"
|
||||
v-tooltip="t('social', 'Add emoji')">
|
||||
:aria-label="t('social', 'Add emoji')">
|
||||
<template #icon>
|
||||
<EmoticonOutline :size="22" decorative title="" />
|
||||
</template>
|
||||
|
@ -96,17 +108,19 @@
|
|||
</div>
|
||||
|
||||
<div v-click-outside="hidePopoverMenu" class="popovermenu-parent">
|
||||
<NcButton type="tertiary"
|
||||
:class="currentVisibilityIconClass"
|
||||
@click.prevent="togglePopoverMenu"
|
||||
v-tooltip="t('social', 'Visibility')" />
|
||||
<NcButton v-tooltip="t('social', 'Visibility')"
|
||||
type="tertiary"
|
||||
:class="currentVisibilityIconClass"
|
||||
@click.prevent="togglePopoverMenu" />
|
||||
<div :class="{open: menuOpened}" class="popovermenu">
|
||||
<NcPopoverMenu :menu="visibilityPopover" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="emptySpace" />
|
||||
<NcButton :value="currentVisibilityPostLabel" :disabled="!canPost" type="primary"
|
||||
<NcButton :value="currentVisibilityPostLabel"
|
||||
:disabled="!canPost"
|
||||
type="primary"
|
||||
@click.prevent="createPost">
|
||||
<template #icon>
|
||||
<Send title="" :size="22" decorative />
|
||||
|
@ -162,8 +176,8 @@ export default {
|
|||
type: localStorage.getItem('social.lastPostType') || 'followers',
|
||||
loading: false,
|
||||
post: '',
|
||||
miniatures: [], // miniatures of images stored in postAttachments
|
||||
postAttachments: [], // The toot's attachments
|
||||
miniatures: [], // miniatures of images stored in postAttachments
|
||||
postAttachments: [], // The toot's attachments
|
||||
previewUrls: [],
|
||||
canType: true,
|
||||
search: '',
|
||||
|
@ -173,45 +187,45 @@ export default {
|
|||
collection: [
|
||||
{
|
||||
trigger: '@',
|
||||
lookup: function(item) {
|
||||
lookup(item) {
|
||||
return item.key + item.value
|
||||
},
|
||||
menuItemTemplate: function(item) {
|
||||
menuItemTemplate(item) {
|
||||
return '<img src="' + item.original.avatar + '" /><div>'
|
||||
+ '<span class="displayName">' + item.original.key + '</span>'
|
||||
+ '<span class="account">' + item.original.value + '</span>'
|
||||
+ '</div>'
|
||||
},
|
||||
selectTemplate: function(item) {
|
||||
selectTemplate(item) {
|
||||
return '<span class="mention" contenteditable="false">'
|
||||
+ '<a href="' + item.original.url + '" target="_blank"><img src="' + item.original.avatar + '" />@' + item.original.value + '</a></span>'
|
||||
},
|
||||
values: (text, cb) => {
|
||||
let users = []
|
||||
const users = []
|
||||
|
||||
if (text.length < 1) {
|
||||
cb(users)
|
||||
}
|
||||
this.remoteSearchAccounts(text).then((result) => {
|
||||
for (var i in result.data.result.accounts) {
|
||||
let user = result.data.result.accounts[i]
|
||||
for (const i in result.data.result.accounts) {
|
||||
const user = result.data.result.accounts[i]
|
||||
users.push({
|
||||
key: user.preferredUsername,
|
||||
value: user.account,
|
||||
url: user.url,
|
||||
avatar: user.local ? generateUrl(`/avatar/${user.preferredUsername}/32`) : generateUrl(`apps/social/api/v1/global/actor/avatar?id=${user.id}`)
|
||||
avatar: user.local ? generateUrl(`/avatar/${user.preferredUsername}/32`) : generateUrl(`apps/social/api/v1/global/actor/avatar?id=${user.id}`),
|
||||
})
|
||||
}
|
||||
cb(users)
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: '#',
|
||||
menuItemTemplate: function(item) {
|
||||
menuItemTemplate(item) {
|
||||
return item.original.value
|
||||
},
|
||||
selectTemplate: function(item) {
|
||||
selectTemplate(item) {
|
||||
let tag = ''
|
||||
// item is undefined if selectTemplate is called from a noMatchTemplate menu
|
||||
if (typeof item === 'undefined') {
|
||||
|
@ -223,7 +237,7 @@ export default {
|
|||
+ '<a href="' + generateUrl('/timeline/tags/' + tag) + '" target="_blank">#' + tag + '</a></span>'
|
||||
},
|
||||
values: (text, cb) => {
|
||||
let tags = []
|
||||
const tags = []
|
||||
|
||||
if (text.length < 1) {
|
||||
cb(tags)
|
||||
|
@ -232,20 +246,20 @@ export default {
|
|||
if (result.data.result.exact) {
|
||||
tags.push({
|
||||
key: result.data.result.exact,
|
||||
value: result.data.result.exact
|
||||
value: result.data.result.exact,
|
||||
})
|
||||
}
|
||||
for (var i in result.data.result.tags) {
|
||||
let tag = result.data.result.tags[i]
|
||||
for (const i in result.data.result.tags) {
|
||||
const tag = result.data.result.tags[i]
|
||||
tags.push({
|
||||
key: tag.hashtag,
|
||||
value: tag.hashtag
|
||||
value: tag.hashtag,
|
||||
})
|
||||
}
|
||||
cb(tags)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
noMatchTemplate() {
|
||||
if (this.current.collection.trigger === '#') {
|
||||
|
@ -255,23 +269,24 @@ export default {
|
|||
return '<li data-index="0">#' + this.current.mentionText + '</li>'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
menuOpened: false
|
||||
menuOpened: false,
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
postTo() {
|
||||
switch (this.type) {
|
||||
case 'public':
|
||||
case 'unlisted':
|
||||
return t('social', 'Post')
|
||||
case 'followers':
|
||||
return t('social', 'Post to followers')
|
||||
case 'direct':
|
||||
return t('social', 'Post to mentioned users')
|
||||
case 'public':
|
||||
case 'unlisted':
|
||||
return t('social', 'Post')
|
||||
case 'followers':
|
||||
return t('social', 'Post to followers')
|
||||
case 'direct':
|
||||
return t('social', 'Post to mentioned users')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
currentVisibilityIconClass() {
|
||||
return this.visibilityIconClass(this.type)
|
||||
|
@ -331,7 +346,7 @@ export default {
|
|||
icon: this.visibilityIconClass('public'),
|
||||
active: this.activeState('public'),
|
||||
text: t('social', 'Public'),
|
||||
longtext: t('social', 'Post to public timelines')
|
||||
longtext: t('social', 'Post to public timelines'),
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
|
@ -340,7 +355,7 @@ export default {
|
|||
icon: this.visibilityIconClass('unlisted'),
|
||||
active: this.activeState('unlisted'),
|
||||
text: t('social', 'Unlisted'),
|
||||
longtext: t('social', 'Do not post to public timelines')
|
||||
longtext: t('social', 'Do not post to public timelines'),
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
|
@ -349,7 +364,7 @@ export default {
|
|||
icon: this.visibilityIconClass('followers'),
|
||||
active: this.activeState('followers'),
|
||||
text: t('social', 'Followers'),
|
||||
longtext: t('social', 'Post to followers only')
|
||||
longtext: t('social', 'Post to followers only'),
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
|
@ -358,8 +373,8 @@ export default {
|
|||
icon: this.visibilityIconClass('direct'),
|
||||
active: this.activeState('direct'),
|
||||
text: t('social', 'Direct'),
|
||||
longtext: t('social', 'Post to mentioned users only')
|
||||
}
|
||||
longtext: t('social', 'Post to mentioned users only'),
|
||||
},
|
||||
]
|
||||
},
|
||||
container() {
|
||||
|
@ -370,10 +385,10 @@ export default {
|
|||
},
|
||||
canPost() {
|
||||
if (this.previewUrls.length > 0) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
return this.post.length !== 0 && this.post !== '<br>'
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$root.$on('composer-reply', (data) => {
|
||||
|
@ -398,9 +413,9 @@ export default {
|
|||
},
|
||||
insert(emoji) {
|
||||
if (typeof emoji === 'object') {
|
||||
let category = Object.keys(emoji)[0]
|
||||
let emojis = emoji[category]
|
||||
let firstEmoji = Object.keys(emojis)[0]
|
||||
const category = Object.keys(emoji)[0]
|
||||
const emojis = emoji[category]
|
||||
const firstEmoji = Object.keys(emojis)[0]
|
||||
emoji = emojis[firstEmoji]
|
||||
}
|
||||
this.post += this.$twemoji.parse(emoji) + ' '
|
||||
|
@ -419,16 +434,16 @@ export default {
|
|||
localStorage.setItem('social.lastPostType', type)
|
||||
},
|
||||
getPostData() {
|
||||
let element = this.$refs.composerInput.cloneNode(true)
|
||||
const element = this.$refs.composerInput.cloneNode(true)
|
||||
Array.from(element.getElementsByClassName('emoji')).forEach((emoji) => {
|
||||
var em = document.createTextNode(emoji.getAttribute('alt'))
|
||||
const em = document.createTextNode(emoji.getAttribute('alt'))
|
||||
emoji.replaceWith(em)
|
||||
})
|
||||
|
||||
let contentHtml = element.innerHTML
|
||||
const contentHtml = element.innerHTML
|
||||
|
||||
// Extract mentions from content and create an array out of them
|
||||
let to = []
|
||||
const to = []
|
||||
const mentionRegex = /<span class="mention"[^>]+><a[^>]+><img[^>]+>@([\w-_.]+@[\w-.]+)/g
|
||||
let match = null
|
||||
do {
|
||||
|
@ -445,7 +460,7 @@ export default {
|
|||
|
||||
// Extract hashtags from content and create an array ot of them
|
||||
const hashtagRegex = />#([^<]+)</g
|
||||
let hashtags = []
|
||||
const hashtags = []
|
||||
match = null
|
||||
do {
|
||||
match = hashtagRegex.exec(contentHtml)
|
||||
|
@ -458,7 +473,7 @@ export default {
|
|||
let content = contentHtml.replace(/<(?!\/div)[^>]+>/gi, '').replace(/<\/div>/gi, '\n').trim()
|
||||
content = he.decode(content)
|
||||
|
||||
let formData = new FormData()
|
||||
const formData = new FormData()
|
||||
formData.append('content', content)
|
||||
formData.append('to', to)
|
||||
formData.append('hashtags', hashtags)
|
||||
|
@ -483,17 +498,17 @@ export default {
|
|||
// Trick to let vue-contenteditable know that tribute replaced a mention or hashtag
|
||||
this.$refs.composerInput.oninput(event)
|
||||
},
|
||||
createPost: async function(event) {
|
||||
async createPost(event) {
|
||||
|
||||
let postData = this.getPostData()
|
||||
const postData = this.getPostData()
|
||||
|
||||
// Trick to validate last mention when the user directly clicks on the "post" button without validating it.
|
||||
let regex = /@([-\w]+)$/
|
||||
let lastMention = postData.get('content').match(regex)
|
||||
const regex = /@([-\w]+)$/
|
||||
const lastMention = postData.get('content').match(regex)
|
||||
if (lastMention) {
|
||||
|
||||
// Ask the server for matching accounts, and wait for the results
|
||||
let result = await this.remoteSearchAccounts(lastMention[1])
|
||||
const result = await this.remoteSearchAccounts(lastMention[1])
|
||||
|
||||
// Validate the last mention only when it matches a single account
|
||||
if (result.data.result.accounts.length === 1) {
|
||||
|
@ -504,8 +519,8 @@ export default {
|
|||
|
||||
// Abort if the post is a direct message and no valid mentions were found
|
||||
// if (this.type === 'direct' && postData.get('to').length === 0) {
|
||||
// OC.Notification.showTemporary(t('social', 'Error while trying to post your message: Could not find any valid recipients.'), { type: 'error' })
|
||||
// return
|
||||
// OC.Notification.showTemporary(t('social', 'Error while trying to post your message: Could not find any valid recipients.'), { type: 'error' })
|
||||
// return
|
||||
// }
|
||||
|
||||
// Post message
|
||||
|
@ -530,8 +545,11 @@ export default {
|
|||
},
|
||||
remoteSearchHashtags(text) {
|
||||
return axios.get(generateUrl('apps/social/api/v1/global/tags/search?search=' + text))
|
||||
}
|
||||
}
|
||||
},
|
||||
deletePreview(index) {
|
||||
this.previewUrls.splice(index, 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
|
||||
<template>
|
||||
<div class="upload-form">
|
||||
<div class="upload-progress" v-if="false">
|
||||
<div v-if="false" class="upload-progress">
|
||||
<div class="upload-progress__icon">
|
||||
<FileUpload :size="32" />
|
||||
</div>
|
||||
|
@ -19,14 +19,18 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
</div>
|
||||
</div>
|
||||
<div class="preview-grid">
|
||||
<PreviewGridItem v-for="(item, index) in miniatures" :key="index" :preview="item" :index="index" @delete="deletePreview" />
|
||||
<PreviewGridItem v-for="(item, index) in miniatures"
|
||||
:key="index"
|
||||
:preview="item"
|
||||
:index="index"
|
||||
@delete="deletePreview" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PreviewGridItem from './PreviewGridItem'
|
||||
import FileUpload from 'vue-material-design-icons/FileUpload'
|
||||
import PreviewGridItem from './PreviewGridItem.vue'
|
||||
import FileUpload from 'vue-material-design-icons/FileUpload.vue'
|
||||
|
||||
export default {
|
||||
name: 'PreviewGrid',
|
||||
|
@ -50,9 +54,8 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
deletePreview(index) {
|
||||
console.debug("rjeoijreo")
|
||||
this.miniatures.splice(index, 1)
|
||||
}
|
||||
$emit('deleted', index)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -4,62 +4,54 @@
|
|||
<div class="preview-item__actions">
|
||||
<NcButton type="tertiary-no-background" @click="$emit('delete', index)">
|
||||
<template #icon>
|
||||
<Close :size="16" fillColor="white" />
|
||||
<Close :size="16" fill-color="white" />
|
||||
</template>
|
||||
<span>{{ t('social', 'Delete') }}</span>
|
||||
</NcButton>
|
||||
<!--
|
||||
<NcButton type="tertiary-no-background" @click="showModal">
|
||||
<template #icon>
|
||||
<Edit :size="16" fillColor="white" />
|
||||
<Edit :size="16" fill-color="white" />
|
||||
</template>
|
||||
<span>{{ t('social', 'Edit') }}</span>
|
||||
</NcButton>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="description-warning" v-if="preview.description.length === 0">
|
||||
<!--
|
||||
<div v-if="preview.description.length === 0" class="description-warning">
|
||||
{{ t('social', 'No description added') }}
|
||||
</div>
|
||||
|
||||
<NcModal v-if="modal" @close="closeModal" size="small">
|
||||
<NcModal v-if="modal" size="small" @close="closeModal">
|
||||
<div class="modal__content">
|
||||
<label :for="`image-description-${index}`">
|
||||
{{ t('social', 'Describe for the visually impaired') }}
|
||||
</label>
|
||||
<textarea :id="`image-description-${index}`" v-model="preview.description">
|
||||
</textarea>
|
||||
<NcButton type="primary" @click="closeModal">{{ t('social', 'Close') }}</NcButton>
|
||||
<textarea :id="`image-description-${index}`" v-model="preview.description" />
|
||||
<NcButton type="primary" @click="closeModal">
|
||||
{{ t('social', 'Close') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</NcModal>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Close from 'vue-material-design-icons/Close.vue'
|
||||
import Edit from 'vue-material-design-icons/Pencil.vue'
|
||||
// import Edit from 'vue-material-design-icons/Pencil.vue'
|
||||
// import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
|
||||
export default {
|
||||
name: 'PreviewGridItem',
|
||||
components: {
|
||||
Close,
|
||||
Edit,
|
||||
// Edit,
|
||||
// NcModal,
|
||||
NcButton,
|
||||
NcModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showModal() {
|
||||
this.modal = true
|
||||
},
|
||||
closeModal() {
|
||||
this.modal = false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
preview: {
|
||||
|
@ -71,6 +63,11 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundStyle() {
|
||||
return {
|
||||
|
@ -78,6 +75,14 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showModal() {
|
||||
this.modal = true
|
||||
},
|
||||
closeModal() {
|
||||
this.modal = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<img class="emoji" draggable="false" :alt="emoji"
|
||||
<img class="emoji"
|
||||
draggable="false"
|
||||
:alt="emoji"
|
||||
:src="emojiUrl">
|
||||
</template>
|
||||
|
||||
|
@ -14,9 +16,9 @@ const UFE0Fg = /\uFE0F/g
|
|||
export default {
|
||||
name: 'Emoji',
|
||||
props: {
|
||||
emoji: { type: String, default: '' }
|
||||
emoji: { type: String, default: '' },
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
|
@ -28,8 +30,8 @@ export default {
|
|||
},
|
||||
emojiUrl() {
|
||||
return generateFilePath('social', 'img', 'twemoji/' + this.icon + '.svg')
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
|
|
@ -36,13 +36,13 @@
|
|||
export default {
|
||||
name: 'EmptyContent',
|
||||
props: {
|
||||
item: { type: Object, default: () => {} }
|
||||
item: { type: Object, default: () => {} },
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return OC.linkTo('social', this.item.image)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
|
|
@ -23,12 +23,16 @@
|
|||
<template>
|
||||
<!-- Show button only if user is authenticated and she is not the same as the account viewed -->
|
||||
<div v-if="!serverData.public && accountInfo && accountInfo.viewerLink!='viewer'">
|
||||
<button v-if="isCurrentUserFollowing" :class="{'icon-loading-small': followLoading}"
|
||||
<button v-if="isCurrentUserFollowing"
|
||||
:class="{'icon-loading-small': followLoading}"
|
||||
@click="unfollow()"
|
||||
@mouseover="followingText=t('social', 'Unfollow')" @mouseleave="followingText=t('social', 'Following')">
|
||||
@mouseover="followingText=t('social', 'Unfollow')"
|
||||
@mouseleave="followingText=t('social', 'Following')">
|
||||
<span><span class="icon-checkmark" />{{ followingText }}</span>
|
||||
</button>
|
||||
<button v-else :class="{'icon-loading-small': followLoading}" class="primary"
|
||||
<button v-else
|
||||
:class="{'icon-loading-small': followLoading}"
|
||||
class="primary"
|
||||
@click="follow">
|
||||
<span>{{ t('social', 'Follow') }}</span>
|
||||
</button>
|
||||
|
@ -43,21 +47,21 @@ export default {
|
|||
name: 'FollowButton',
|
||||
mixins: [
|
||||
accountMixins,
|
||||
currentUser
|
||||
currentUser,
|
||||
],
|
||||
props: {
|
||||
account: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
uid: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
followingText: t('social', 'Following')
|
||||
followingText: t('social', 'Following'),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -66,7 +70,7 @@ export default {
|
|||
},
|
||||
isCurrentUserFollowing() {
|
||||
return this.$store.getters.isFollowingUser(this.account)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
follow() {
|
||||
|
@ -74,8 +78,8 @@ export default {
|
|||
},
|
||||
unfollow() {
|
||||
this.$store.dispatch('unfollowAccount', { currentAccount: this.cloudId, accountToUnfollow: this.account })
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,8 +3,12 @@
|
|||
<div v-for="(item, index) in attachments" :key="index">
|
||||
<img :src="imageUrl(item)" @click="showModal(index)">
|
||||
</div>
|
||||
<NcModal v-show="modal" :has-previous="current > 0" :has-next="current < (attachments.length - 1)"
|
||||
size="full" @close="closeModal" @previous="showPrevious"
|
||||
<NcModal v-show="modal"
|
||||
:has-previous="current > 0"
|
||||
:has-next="current < (attachments.length - 1)"
|
||||
size="full"
|
||||
@close="closeModal"
|
||||
@previous="showPrevious"
|
||||
@next="showNext">
|
||||
<div class="modal__content">
|
||||
<canvas ref="modalCanvas" />
|
||||
|
@ -22,21 +26,21 @@ import { generateUrl } from '@nextcloud/router'
|
|||
export default {
|
||||
name: 'PostAttachment',
|
||||
components: {
|
||||
NcModal
|
||||
NcModal,
|
||||
},
|
||||
mixins: [
|
||||
serverData
|
||||
serverData,
|
||||
],
|
||||
props: {
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: Array
|
||||
}
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: false,
|
||||
current: ''
|
||||
current: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -44,7 +48,7 @@ export default {
|
|||
* @function imageUrl
|
||||
* @description Returns the URL where to get a resized version of the attachement
|
||||
* @param {object} item - The attachment
|
||||
* @returns {string} The URL
|
||||
* @return {string} The URL
|
||||
*/
|
||||
imageUrl(item) {
|
||||
if (this.serverData.public) {
|
||||
|
@ -58,12 +62,12 @@ export default {
|
|||
* @description Displays the currently selected attachment's image
|
||||
*/
|
||||
displayImage() {
|
||||
var canvas = this.$refs.modalCanvas
|
||||
var ctx = canvas.getContext('2d')
|
||||
var img = new Image()
|
||||
const canvas = this.$refs.modalCanvas
|
||||
const ctx = canvas.getContext('2d')
|
||||
const img = new Image()
|
||||
img.onload = function() {
|
||||
var width = img.width
|
||||
var height = img.height
|
||||
let width = img.width
|
||||
let height = img.height
|
||||
if (width > window.innerWidth) {
|
||||
height = height * (window.innerWidth / width)
|
||||
width = window.innerWidth
|
||||
|
@ -93,7 +97,7 @@ export default {
|
|||
showNext() {
|
||||
this.current++
|
||||
this.displayImage()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -22,9 +22,13 @@
|
|||
|
||||
<template>
|
||||
<div v-if="profileAccount && accountInfo" class="user-profile">
|
||||
<NcAvatar v-if="accountInfo.local" :user="localUid" :disable-tooltip="true"
|
||||
<NcAvatar v-if="accountInfo.local"
|
||||
:user="localUid"
|
||||
:disable-tooltip="true"
|
||||
:size="128" />
|
||||
<NcAvatar v-else :url="avatarUrl" :disable-tooltip="true"
|
||||
<NcAvatar v-else
|
||||
:url="avatarUrl"
|
||||
:disable-tooltip="true"
|
||||
:size="128" />
|
||||
<h2>{{ displayName }}</h2>
|
||||
<!-- TODO: we have no details, timeline and follower list for non-local accounts for now -->
|
||||
|
@ -51,13 +55,77 @@
|
|||
{{ accountInfo.website.value }}
|
||||
</a>
|
||||
</p>
|
||||
<follow-button :account="accountInfo.account" :uid="uid" />
|
||||
<FollowButton :account="accountInfo.account" :uid="uid" />
|
||||
<NcButton v-if="serverData.public" class="primary" @click="followRemote">
|
||||
{{ t('social', 'Follow') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import accountMixins from '../mixins/accountMixins.js'
|
||||
import serverData from '../mixins/serverData.js'
|
||||
import currentUser from '../mixins/currentUserMixin.js'
|
||||
import follow from '../mixins/follow.js'
|
||||
import FollowButton from './FollowButton.vue'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export default {
|
||||
name: 'ProfileInfo',
|
||||
components: {
|
||||
FollowButton,
|
||||
NcAvatar,
|
||||
NcButton,
|
||||
},
|
||||
mixins: [
|
||||
accountMixins,
|
||||
currentUser,
|
||||
serverData,
|
||||
follow,
|
||||
],
|
||||
props: {
|
||||
uid: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
followingText: t('social', 'Following'),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
localUid() {
|
||||
// Returns only the local part of a username
|
||||
return (this.uid.indexOf('@') === -1) ? this.uid : this.uid.slice(0, this.uid.indexOf('@'))
|
||||
},
|
||||
displayName() {
|
||||
if (typeof this.accountInfo.name !== 'undefined' && this.accountInfo.name !== '') {
|
||||
return this.accountInfo.name
|
||||
}
|
||||
if (typeof this.accountInfo.preferredUsername !== 'undefined' && this.accountInfo.preferredUsername !== '') {
|
||||
return this.accountInfo.preferredUsername
|
||||
}
|
||||
return this.profileAccount
|
||||
},
|
||||
getCount() {
|
||||
const account = this.accountInfo
|
||||
return (field) => account.details.count ? account.details.count[field] : ''
|
||||
},
|
||||
avatarUrl() {
|
||||
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.accountInfo.id)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
followRemote() {
|
||||
window.open(generateUrl('/apps/social/api/v1/ostatus/followRemote/' + encodeURI(this.localUid)), 'followRemote', 'width=433,height=600toolbar=no,menubar=no,scrollbars=yes,resizable=yes')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.user-profile {
|
||||
display: flex;
|
||||
|
@ -94,67 +162,3 @@
|
|||
border-bottom: 1px solid var(--color-main-text);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import accountMixins from '../mixins/accountMixins.js'
|
||||
import serverData from '../mixins/serverData.js'
|
||||
import currentUser from '../mixins/currentUserMixin.js'
|
||||
import follow from '../mixins/follow.js'
|
||||
import FollowButton from './FollowButton.vue'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export default {
|
||||
name: 'ProfileInfo',
|
||||
components: {
|
||||
FollowButton,
|
||||
NcAvatar,
|
||||
NcButton,
|
||||
},
|
||||
mixins: [
|
||||
accountMixins,
|
||||
currentUser,
|
||||
serverData,
|
||||
follow
|
||||
],
|
||||
props: {
|
||||
uid: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
followingText: t('social', 'Following')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
localUid() {
|
||||
// Returns only the local part of a username
|
||||
return (this.uid.indexOf('@') === -1) ? this.uid : this.uid.slice(0, this.uid.indexOf('@'))
|
||||
},
|
||||
displayName() {
|
||||
if (typeof this.accountInfo.name !== 'undefined' && this.accountInfo.name !== '') {
|
||||
return this.accountInfo.name
|
||||
}
|
||||
if (typeof this.accountInfo.preferredUsername !== 'undefined' && this.accountInfo.preferredUsername !== '') {
|
||||
return this.accountInfo.preferredUsername
|
||||
}
|
||||
return this.profileAccount
|
||||
},
|
||||
getCount() {
|
||||
let account = this.accountInfo
|
||||
return (field) => account.details.count ? account.details.count[field] : ''
|
||||
},
|
||||
avatarUrl() {
|
||||
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.accountInfo.id)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
followRemote() {
|
||||
window.open(generateUrl('/apps/social/api/v1/ostatus/followRemote/' + encodeURI(this.localUid)), 'followRemote', 'width=433,height=600toolbar=no,menubar=no,scrollbars=yes,resizable=yes')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -33,15 +33,17 @@
|
|||
</div>
|
||||
<div v-else>
|
||||
<h3>{{ t('social', 'Searching for') }} {{ decodeURIComponent(term) }}</h3>
|
||||
<user-entry v-for="result in allResults" :key="result.id" :item="result" />
|
||||
<UserEntry v-for="result in allResults" :key="result.id" :item="result" />
|
||||
<div v-if="hashtags.length > 0">
|
||||
<li v-for="tag in hashtags" :key="tag.hashtag" class="tag">
|
||||
<router-link :to="{ name: 'tags', params: {tag: tag.hashtag } }">
|
||||
<span>#{{ tag.hashtag }}</span>
|
||||
<trend
|
||||
:data="trendData(tag.trend)"
|
||||
:gradient="['#17adff', '#0082c9']" :smooth="true" :width="150"
|
||||
:height="44" stroke-width="2" />
|
||||
<Trend :data="trendData(tag.trend)"
|
||||
:gradient="['#17adff', '#0082c9']"
|
||||
:smooth="true"
|
||||
:width="150"
|
||||
:height="44"
|
||||
stroke-width="2" />
|
||||
</router-link>
|
||||
</li>
|
||||
</div>
|
||||
|
@ -49,33 +51,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-entry {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: -3px;
|
||||
margin-left: 47px;
|
||||
}
|
||||
.tag {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid var(--color-background-dark);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 12px;
|
||||
font-weight: 300;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import UserEntry from './UserEntry.vue'
|
||||
|
@ -87,13 +62,13 @@ export default {
|
|||
name: 'Search',
|
||||
components: {
|
||||
UserEntry,
|
||||
Trend
|
||||
Trend,
|
||||
},
|
||||
props: {
|
||||
term: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -101,7 +76,7 @@ export default {
|
|||
loading: false,
|
||||
remoteLoading: false,
|
||||
match: null,
|
||||
hashtags: []
|
||||
hashtags: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -113,12 +88,12 @@ export default {
|
|||
return this.results.accounts.result
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
term(val) {
|
||||
this.search(val)
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.search(this.term)
|
||||
|
@ -130,7 +105,7 @@ export default {
|
|||
Math.max(0, trend['3d'] - trend['1d']),
|
||||
Math.max(0, trend['1d'] - trend['12h']),
|
||||
Math.max(0, trend['12h'] - trend['1h']),
|
||||
Math.max(0, trend['1h'])
|
||||
Math.max(0, trend['1h']),
|
||||
]
|
||||
return data
|
||||
},
|
||||
|
@ -162,7 +137,34 @@ export default {
|
|||
},
|
||||
remoteSearch(term) {
|
||||
return axios.get(generateUrl('apps/social/api/v1/global/account/info?account=' + term))
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-entry {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: -3px;
|
||||
margin-left: 47px;
|
||||
}
|
||||
.tag {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid var(--color-background-dark);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 12px;
|
||||
font-weight: 300;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -24,12 +24,11 @@
|
|||
{{ boosted }}
|
||||
</div>
|
||||
</template>
|
||||
<user-entry v-if="item.type === 'SocialAppNotification' && item.details.actor" :key="item.details.actor.id" :item="item.details.actor" />
|
||||
<UserEntry v-if="item.type === 'SocialAppNotification' && item.details.actor" :key="item.details.actor.id" :item="item.details.actor" />
|
||||
<template v-else>
|
||||
<div class="wrapper">
|
||||
<timeline-avatar :item="entryContent" />
|
||||
<timeline-post
|
||||
class="message"
|
||||
<TimelineAvatar :item="entryContent" />
|
||||
<TimelinePost class="message"
|
||||
:item="entryContent"
|
||||
:parent-announce="isBoost" />
|
||||
</div>
|
||||
|
@ -47,12 +46,12 @@ export default {
|
|||
components: {
|
||||
TimelinePost,
|
||||
TimelineAvatar,
|
||||
UserEntry
|
||||
UserEntry,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => {},
|
||||
},
|
||||
isProfilePage: {
|
||||
type: Boolean,
|
||||
|
@ -88,13 +87,13 @@ export default {
|
|||
actionSummary() {
|
||||
|
||||
let summary = this.item.summary
|
||||
for (var key in this.item.details) {
|
||||
for (const key in this.item.details) {
|
||||
|
||||
let keyword = '{' + key + '}'
|
||||
const keyword = '{' + key + '}'
|
||||
if (typeof this.item.details[key] !== 'string' && this.item.details[key].length > 1) {
|
||||
|
||||
let concatination = ''
|
||||
for (var stringKey in this.item.details[key]) {
|
||||
for (const stringKey in this.item.details[key]) {
|
||||
|
||||
if (this.item.details[key].length > 3 && stringKey === '3') {
|
||||
// ellipses the actors' list to 3 actors when it's big
|
||||
|
@ -115,13 +114,13 @@ export default {
|
|||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
userDisplayName(actorInfo) {
|
||||
return actorInfo.name !== '' ? actorInfo.name : actorInfo.preferredUsername
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
<template>
|
||||
<div class="social__timeline">
|
||||
<transition-group name="list" tag="div">
|
||||
<timeline-entry v-for="entry in timeline" :key="entry.id" :item="entry" />
|
||||
<TimelineEntry v-for="entry in timeline" :key="entry.id" :item="entry" />
|
||||
</transition-group>
|
||||
<infinite-loading ref="infiniteLoading" @infinite="infiniteHandler">
|
||||
<InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
|
||||
<div slot="spinner">
|
||||
<div class="icon-loading" />
|
||||
</div>
|
||||
|
@ -33,12 +33,126 @@
|
|||
<div class="list-end" />
|
||||
</div>
|
||||
<div slot="no-results">
|
||||
<empty-content v-if="timeline.length === 0" :item="emptyContentData" />
|
||||
<EmptyContent v-if="timeline.length === 0" :item="emptyContentData" />
|
||||
</div>
|
||||
</infinite-loading>
|
||||
</InfiniteLoading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfiniteLoading from 'vue-infinite-loading'
|
||||
import TimelineEntry from './TimelineEntry.vue'
|
||||
import CurrentUserMixin from './../mixins/currentUserMixin.js'
|
||||
import EmptyContent from './EmptyContent.vue'
|
||||
import Logger from '../logger.js'
|
||||
|
||||
export default {
|
||||
name: 'TimelineList',
|
||||
components: {
|
||||
TimelineEntry,
|
||||
InfiniteLoading,
|
||||
EmptyContent,
|
||||
},
|
||||
mixins: [CurrentUserMixin],
|
||||
props: {
|
||||
type: { type: String, default: () => 'home' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
state: [],
|
||||
emptyContent: {
|
||||
default: {
|
||||
image: 'img/undraw/posts.svg',
|
||||
title: t('social', 'No posts found'),
|
||||
description: t('social', 'Posts from people you follow will show up here'),
|
||||
},
|
||||
direct: {
|
||||
image: 'img/undraw/direct.svg',
|
||||
title: t('social', 'No direct messages found'),
|
||||
description: t('social', 'Posts directed to you will show up here'),
|
||||
},
|
||||
timeline: {
|
||||
image: 'img/undraw/local.svg',
|
||||
title: t('social', 'No local posts found'),
|
||||
description: t('social', 'Posts from other people on this instance will show up here'),
|
||||
},
|
||||
notifications: {
|
||||
image: 'img/undraw/notifications.svg',
|
||||
title: t('social', 'No notifications found'),
|
||||
description: t('social', 'You have not received any notifications yet'),
|
||||
},
|
||||
federated: {
|
||||
image: 'img/undraw/global.svg',
|
||||
title: t('social', 'No global posts found'),
|
||||
description: t('social', 'Posts from federated instances will show up here'),
|
||||
},
|
||||
liked: {
|
||||
image: 'img/undraw/likes.svg',
|
||||
title: t('social', 'No liked posts found'),
|
||||
},
|
||||
profile: {
|
||||
image: 'img/undraw/profile.svg',
|
||||
title: t('social', 'You have not tooted yet'),
|
||||
},
|
||||
tags: {
|
||||
image: 'img/undraw/profile.svg',
|
||||
title: t('social', 'No posts found for this tag'),
|
||||
},
|
||||
'single-post': {
|
||||
title: t('social', 'No replies found'),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emptyContentData() {
|
||||
if (typeof this.emptyContent[this.$route.params.type] !== 'undefined') {
|
||||
return this.emptyContent[this.$route.params.type]
|
||||
}
|
||||
|
||||
if (typeof this.emptyContent[this.$route.name] !== 'undefined') {
|
||||
const content = this.emptyContent[this.$route.name]
|
||||
// Change text on profile page when accessed by another user or a public (non-authenticated) user
|
||||
if (this.$route.name === 'profile' && (this.serverData.public || this.$route.params.account !== this.currentUser.uid)) {
|
||||
content.title = this.$route.params.account + ' ' + t('social', 'hasn\'t tooted yet')
|
||||
}
|
||||
return this.$route.name === 'timeline' ? this.emptyContent.default : content
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Logger.log('Did not find any empty content for this route', { routeType: this.$route.params.type, routeName: this.$route.name })
|
||||
return this.emptyContent.default
|
||||
},
|
||||
timeline() {
|
||||
return this.$store.getters.getTimeline
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler($state) {
|
||||
this.$store.dispatch('fetchTimeline', {
|
||||
account: this.currentUser.uid,
|
||||
}).then((response) => {
|
||||
if (response.status === -1) {
|
||||
OC.Notification.showTemporary('Failed to load more timeline entries')
|
||||
console.error('Failed to load more timeline entries', response)
|
||||
$state.complete()
|
||||
return
|
||||
}
|
||||
response.result.length > 0 ? $state.loaded() : $state.complete()
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to load more timeline entries')
|
||||
console.error('Failed to load more timeline entries', error)
|
||||
$state.complete()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list-item {
|
||||
}
|
||||
|
@ -54,117 +168,3 @@
|
|||
transform: translateX(-100px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import InfiniteLoading from 'vue-infinite-loading'
|
||||
import TimelineEntry from './TimelineEntry.vue'
|
||||
import CurrentUserMixin from './../mixins/currentUserMixin.js'
|
||||
import EmptyContent from './EmptyContent.vue'
|
||||
import Logger from '../logger.js'
|
||||
|
||||
export default {
|
||||
name: 'Timeline',
|
||||
components: {
|
||||
TimelineEntry,
|
||||
InfiniteLoading,
|
||||
EmptyContent
|
||||
},
|
||||
mixins: [CurrentUserMixin],
|
||||
props: {
|
||||
type: { type: String, default: () => 'home' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
state: [],
|
||||
emptyContent: {
|
||||
default: {
|
||||
image: 'img/undraw/posts.svg',
|
||||
title: t('social', 'No posts found'),
|
||||
description: t('social', 'Posts from people you follow will show up here')
|
||||
},
|
||||
direct: {
|
||||
image: 'img/undraw/direct.svg',
|
||||
title: t('social', 'No direct messages found'),
|
||||
description: t('social', 'Posts directed to you will show up here')
|
||||
},
|
||||
timeline: {
|
||||
image: 'img/undraw/local.svg',
|
||||
title: t('social', 'No local posts found'),
|
||||
description: t('social', 'Posts from other people on this instance will show up here')
|
||||
},
|
||||
notifications: {
|
||||
image: 'img/undraw/notifications.svg',
|
||||
title: t('social', 'No notifications found'),
|
||||
description: t('social', 'You have not received any notifications yet')
|
||||
},
|
||||
federated: {
|
||||
image: 'img/undraw/global.svg',
|
||||
title: t('social', 'No global posts found'),
|
||||
description: t('social', 'Posts from federated instances will show up here')
|
||||
},
|
||||
liked: {
|
||||
image: 'img/undraw/likes.svg',
|
||||
title: t('social', 'No liked posts found')
|
||||
},
|
||||
profile: {
|
||||
image: 'img/undraw/profile.svg',
|
||||
title: t('social', 'You have not tooted yet')
|
||||
},
|
||||
tags: {
|
||||
image: 'img/undraw/profile.svg',
|
||||
title: t('social', 'No posts found for this tag')
|
||||
},
|
||||
'single-post': {
|
||||
title: t('social', 'No replies found')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emptyContentData() {
|
||||
if (typeof this.emptyContent[this.$route.params.type] !== 'undefined') {
|
||||
return this.emptyContent[this.$route.params.type]
|
||||
}
|
||||
|
||||
if (typeof this.emptyContent[this.$route.name] !== 'undefined') {
|
||||
let content = this.emptyContent[this.$route.name]
|
||||
// Change text on profile page when accessed by another user or a public (non-authenticated) user
|
||||
if (this.$route.name === 'profile' && (this.serverData.public || this.$route.params.account !== this.currentUser.uid)) {
|
||||
content.title = this.$route.params.account + ' ' + t('social', 'hasn\'t tooted yet')
|
||||
}
|
||||
return this.$route.name === 'timeline' ? this.emptyContent['default'] : content
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Logger.log('Did not find any empty content for this route', { 'routeType': this.$route.params.type, 'routeName': this.$route.name })
|
||||
return this.emptyContent.default
|
||||
},
|
||||
timeline: function() {
|
||||
return this.$store.getters.getTimeline
|
||||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler($state) {
|
||||
this.$store.dispatch('fetchTimeline', {
|
||||
account: this.currentUser.uid
|
||||
}).then((response) => {
|
||||
if (response.status === -1) {
|
||||
OC.Notification.showTemporary('Failed to load more timeline entries')
|
||||
console.error('Failed to load more timeline entries', response)
|
||||
$state.complete()
|
||||
return
|
||||
}
|
||||
response.result.length > 0 ? $state.loaded() : $state.complete()
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to load more timeline entries')
|
||||
console.error('Failed to load more timeline entries', error)
|
||||
$state.complete()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -33,34 +33,34 @@
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-else class="post-message" v-html="item.actor_info.summary" />
|
||||
<div v-if="hasAttachments" class="post-attachments">
|
||||
<post-attachment :attachments="item.attachment" />
|
||||
<PostAttachment :attachments="item.attachment" />
|
||||
</div>
|
||||
<div v-if="this.$route && this.$route.params.type !== 'notifications' && !serverData.public" class="post-actions">
|
||||
<NcButton type="tertiary-no-background"
|
||||
v-tooltip="t('social', 'Reply')"
|
||||
<div v-if="$route && $route.params.type !== 'notifications' && !serverData.public" class="post-actions">
|
||||
<NcButton v-tooltip="t('social', 'Reply')"
|
||||
type="tertiary-no-background"
|
||||
@click="reply">
|
||||
<template #icon>
|
||||
<Reply :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton type="tertiary-no-background"
|
||||
v-tooltip="t('social', 'Boost')"
|
||||
<NcButton v-tooltip="t('social', 'Boost')"
|
||||
type="tertiary-no-background"
|
||||
@click="boost">
|
||||
<template #icon>
|
||||
<Repeat :size="20" :fill-color="isBoosted ? 'blue' : 'var(--color-main-text)'" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton v-if="!isLiked"
|
||||
type="tertiary-no-background"
|
||||
v-tooltip="t('social', 'Like')"
|
||||
type="tertiary-no-background"
|
||||
@click="like">
|
||||
<template #icon>
|
||||
<HeartOutline :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton v-if="isLiked"
|
||||
type="tertiary-no-background"
|
||||
v-tooltip="t('social', 'Undo Like')"
|
||||
type="tertiary-no-background"
|
||||
@click="like">
|
||||
<template #icon>
|
||||
<Heart :size="20" :fill-color="'var(--color-error)'" />
|
||||
|
@ -68,8 +68,8 @@
|
|||
</NcButton>
|
||||
<NcActions>
|
||||
<NcActionButton v-if="item.actor_info.account === cloudId"
|
||||
@click="remove()"
|
||||
icon="icon-delete">
|
||||
icon="icon-delete"
|
||||
@click="remove()">
|
||||
{{ t('social', 'Delete') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
|
@ -79,9 +79,11 @@
|
|||
|
||||
<script>
|
||||
import * as linkify from 'linkifyjs'
|
||||
// eslint-disable-next-line
|
||||
import pluginMention from 'linkifyjs/plugins/mention'
|
||||
// eslint-disable-next-line
|
||||
import 'linkifyjs/string'
|
||||
import currentUser from './../mixins/currentUserMixin'
|
||||
import currentUser from './../mixins/currentUserMixin.js'
|
||||
import PostAttachment from './PostAttachment.vue'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
|
@ -90,8 +92,7 @@ import Repeat from 'vue-material-design-icons/Repeat.vue'
|
|||
import Reply from 'vue-material-design-icons/Reply.vue'
|
||||
import Heart from 'vue-material-design-icons/Heart.vue'
|
||||
import HeartOutline from 'vue-material-design-icons/HeartOutline.vue'
|
||||
import Logger from '../logger'
|
||||
import MessageContent from './MessageContent'
|
||||
import logger from '../services/logger.js'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import RichText from '@nextcloud/vue-richtext'
|
||||
|
@ -102,7 +103,6 @@ export default {
|
|||
name: 'TimelinePost',
|
||||
components: {
|
||||
PostAttachment,
|
||||
MessageContent,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcButton,
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
mixins: [currentUser],
|
||||
props: {
|
||||
item: { type: Object, default: () => {} },
|
||||
parentAnnounce: { type: Object, default: () => {} }
|
||||
parentAnnounce: { type: Object, default: () => {} },
|
||||
},
|
||||
computed: {
|
||||
relativeTimestamp() {
|
||||
|
@ -157,6 +157,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param e
|
||||
* @function getSinglePostTimeline
|
||||
* @description Opens the timeline of the post clicked
|
||||
*/
|
||||
|
@ -168,16 +169,17 @@ export default {
|
|||
} else if (this.item.type === 'Announce') {
|
||||
window.open(this.item.object)
|
||||
} else {
|
||||
Logger.warn("Don't know what to do with posts of type " + this.item.type, { post: this.item })
|
||||
logger.warn("Don't know what to do with posts of type " + this.item.type, { post: this.item })
|
||||
}
|
||||
} else {
|
||||
this.$router.push({ name: 'single-post',
|
||||
this.$router.push({
|
||||
name: 'single-post',
|
||||
params: {
|
||||
account: this.item.actor_info.preferredUsername,
|
||||
id: this.item.id,
|
||||
localId: this.item.id.split('/')[this.item.id.split('/').length - 1],
|
||||
type: 'single-post'
|
||||
}
|
||||
type: 'single-post',
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -189,9 +191,9 @@ export default {
|
|||
this.$root.$emit('composer-reply', this.item)
|
||||
},
|
||||
boost() {
|
||||
let params = {
|
||||
const params = {
|
||||
post: this.item,
|
||||
parentAnnounce: this.parentAnnounce
|
||||
parentAnnounce: this.parentAnnounce,
|
||||
}
|
||||
if (this.isBoosted) {
|
||||
this.$store.dispatch('postUnBoost', params)
|
||||
|
@ -203,17 +205,17 @@ export default {
|
|||
this.$store.dispatch('postDelete', this.item)
|
||||
},
|
||||
like() {
|
||||
let params = {
|
||||
const params = {
|
||||
post: this.item,
|
||||
parentAnnounce: this.parentAnnounce
|
||||
parentAnnounce: this.parentAnnounce,
|
||||
}
|
||||
if (this.isLiked) {
|
||||
this.$store.dispatch('postUnlike', params)
|
||||
} else {
|
||||
this.$store.dispatch('postLike', params)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
<div v-if="item" class="user-entry">
|
||||
<div class="entry-content">
|
||||
<div class="user-avatar">
|
||||
<NcAvatar v-if="item.local" :size="32" :user="item.preferredUsername"
|
||||
<NcAvatar v-if="item.local"
|
||||
:size="32"
|
||||
:user="item.preferredUsername"
|
||||
:disable-tooltip="true" />
|
||||
<NcAvatar v-else :url="avatarUrl" />
|
||||
</div>
|
||||
|
@ -37,7 +39,9 @@
|
|||
{{ item.account }}
|
||||
</span>
|
||||
</router-link>
|
||||
<a v-else :href="item.id" target="_blank"
|
||||
<a v-else
|
||||
:href="item.id"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<span class="post-author">
|
||||
{{ item.name }}
|
||||
|
@ -49,7 +53,7 @@
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<p v-html="item.summary" />
|
||||
</div>
|
||||
<follow-button :account="item.account" />
|
||||
<FollowButton :account="item.account" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -69,14 +73,14 @@ export default {
|
|||
},
|
||||
mixins: [
|
||||
follow,
|
||||
currentUser
|
||||
currentUser,
|
||||
],
|
||||
props: {
|
||||
item: { type: Object, default: () => {} }
|
||||
item: { type: Object, default: () => {} },
|
||||
},
|
||||
data: function() {
|
||||
data() {
|
||||
return {
|
||||
followingText: t('social', 'Following')
|
||||
followingText: t('social', 'Following'),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -88,8 +92,8 @@ export default {
|
|||
},
|
||||
avatarUrl() {
|
||||
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.id)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
|
|
@ -27,7 +27,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
OCA.Dashboard.register('social_notifications', (el, { widget }) => {
|
||||
const View = Vue.extend(Dashboard)
|
||||
new View({
|
||||
propsData: { title: widget.title }
|
||||
propsData: { title: widget.title },
|
||||
}).$mount(el)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
bind: function(el) {
|
||||
bind(el) {
|
||||
Vue.nextTick(() => {
|
||||
el.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
12
src/main.js
12
src/main.js
|
@ -24,12 +24,12 @@ import Vue from 'vue'
|
|||
import { sync } from 'vuex-router-sync'
|
||||
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
import store from './store/index.js'
|
||||
import router from './router.js'
|
||||
import vuetwemoji from 'vue-twemoji'
|
||||
import contenteditableDirective from 'vue-contenteditable-directive'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import VTooltip from '@nextcloud/vue/dist/Directives/Tooltip'
|
||||
import VTooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
|
||||
|
||||
sync(store, router)
|
||||
|
||||
|
@ -54,12 +54,12 @@ Vue.use(vuetwemoji, {
|
|||
baseUrl: OC.linkTo('social', 'img/'), // can set to local folder of emojis. default: https://twemoji.maxcdn.com/
|
||||
extension: '.svg', // .svg, .png
|
||||
className: 'emoji', // custom className for image output
|
||||
size: 'twemoji' // image size
|
||||
size: 'twemoji', // image size
|
||||
})
|
||||
|
||||
/* eslint-disable-next-line no-new */
|
||||
new Vue({
|
||||
router: router,
|
||||
router,
|
||||
render: h => h(App),
|
||||
store: store
|
||||
store,
|
||||
}).$mount('#content')
|
||||
|
|
|
@ -24,11 +24,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import serverData from './serverData'
|
||||
import serverData from './serverData.js'
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
serverData
|
||||
serverData,
|
||||
],
|
||||
computed: {
|
||||
/** @function Returns the complete account name */
|
||||
|
@ -39,10 +39,12 @@ export default {
|
|||
accountInfo() {
|
||||
return this.$store.getters.getAccount(this.profileAccount)
|
||||
},
|
||||
/** @function Somewhat duplicate with accountInfo(), but needed (for some reason) to avoid glitches
|
||||
* where components would first show "user not found" before display an account's account info */
|
||||
/**
|
||||
* @function Somewhat duplicate with accountInfo(), but needed (for some reason) to avoid glitches
|
||||
* where components would first show "user not found" before display an account's account info
|
||||
*/
|
||||
accountLoaded() {
|
||||
return this.$store.getters.accountLoaded(this.profileAccount)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import serverData from './serverData'
|
||||
import serverData from './serverData.js'
|
||||
export default {
|
||||
mixins: [
|
||||
serverData
|
||||
serverData,
|
||||
],
|
||||
computed: {
|
||||
currentUser() {
|
||||
|
@ -34,6 +34,6 @@ export default {
|
|||
},
|
||||
cloudId() {
|
||||
return this.currentUser.uid + '@' + this.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class UnfollowException {
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
followLoading: false
|
||||
followLoading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -66,6 +66,6 @@ export default {
|
|||
OC.Notification.showTemporary(`Failed to unfollow user ${this.item.account}`)
|
||||
console.error(`Failed to unfollow user ${this.item.account}`, error.response.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu'
|
||||
import NcPopoverMenu from '@nextcloud/vue/dist/Components/NcPopoverMenu.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PopoverMenu
|
||||
NcPopoverMenu,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuOpened: false
|
||||
menuOpened: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -37,6 +37,6 @@ export default {
|
|||
},
|
||||
hidePopoverMenu() {
|
||||
this.menuOpened = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -26,15 +26,16 @@
|
|||
|
||||
export default {
|
||||
computed: {
|
||||
/** @description Returns the serverData object
|
||||
* @property {String} account - The account that the user wants to follow (Only in 'OStatus.vue')
|
||||
/**
|
||||
* @description Returns the serverData object
|
||||
* @property {string} account - The account that the user wants to follow (Only in 'OStatus.vue')
|
||||
* @property cliUrl
|
||||
* @property cloudAddress
|
||||
* @property firstrun
|
||||
* @property isAdmin
|
||||
* @property {String} local - The local part of the account that the user wants to follow
|
||||
* @property {boolean} public - False when the page is accessed by an authenticated user. True otherwise
|
||||
* @property setup
|
||||
* @property {string} local - The local part of the account that the user wants to follow
|
||||
* @property {boolean} public - False when the page is accessed by an authenticated user. True otherwise
|
||||
* @property setup
|
||||
*/
|
||||
serverData() {
|
||||
if (!this.$store) {
|
||||
|
@ -46,6 +47,6 @@ export default {
|
|||
const url = document.createElement('a')
|
||||
url.setAttribute('href', this.serverData.cloudAddress)
|
||||
return url.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import store from './store'
|
||||
import store from './store/index.js'
|
||||
import OStatus from './views/OStatus.vue'
|
||||
|
||||
// eslint-disable-next-line
|
||||
|
@ -37,5 +37,5 @@ Vue.prototype.OCA = OCA
|
|||
/* eslint-disable-next-line no-new */
|
||||
new Vue({
|
||||
render: h => h(OStatus),
|
||||
store: store
|
||||
store,
|
||||
}).$mount('#content')
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = OC.linkTo('social', 'js/')
|
||||
|
||||
import ProfilePageIntegration from './views/ProfilePageIntegration.vue'
|
||||
import Vue from 'vue'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
|
||||
if (!OCA?.Core?.ProfileSections) {
|
||||
exit();
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = generateFilePath('social', '', 'js/')
|
||||
|
||||
if (OCA?.Core?.ProfileSections) {
|
||||
Vue.prototype.t = translate
|
||||
Vue.prototype.n = translatePlural
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.OCA = OCA
|
||||
|
||||
const View = Vue.extend(ProfilePageIntegration)
|
||||
|
||||
OCA.Core.ProfileSections.registerSection((el, userId) => {
|
||||
return View
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.t = t
|
||||
Vue.prototype.n = n
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.OCA = OCA
|
||||
|
||||
const View = Vue.extend(ProfilePageIntegration)
|
||||
|
||||
OCA.Core.ProfileSections.registerSection((el, userId) => {
|
||||
return View
|
||||
})
|
||||
|
|
|
@ -43,35 +43,35 @@ export default new Router({
|
|||
routes: [
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/',
|
||||
redirect: { name: 'timeline' }
|
||||
redirect: { name: 'timeline' },
|
||||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/timeline/:type?',
|
||||
components: {
|
||||
default: Timeline
|
||||
default: Timeline,
|
||||
},
|
||||
props: true,
|
||||
name: 'timeline',
|
||||
children: [
|
||||
{
|
||||
path: 'tags/:tag',
|
||||
name: 'tags'
|
||||
}
|
||||
]
|
||||
name: 'tags',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/@:account/:localId',
|
||||
components: {
|
||||
default: TimelineSinglePost
|
||||
default: TimelineSinglePost,
|
||||
},
|
||||
props: true,
|
||||
name: 'single-post'
|
||||
name: 'single-post',
|
||||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/@:account',
|
||||
components: {
|
||||
default: Profile,
|
||||
details: ProfileTimeline
|
||||
details: ProfileTimeline,
|
||||
},
|
||||
props: true,
|
||||
children: [
|
||||
|
@ -79,33 +79,33 @@ export default new Router({
|
|||
path: '',
|
||||
name: 'profile',
|
||||
components: {
|
||||
details: ProfileTimeline
|
||||
}
|
||||
details: ProfileTimeline,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'followers',
|
||||
name: 'profile.followers',
|
||||
components: {
|
||||
details: ProfileFollowers
|
||||
}
|
||||
details: ProfileFollowers,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'following',
|
||||
name: 'profile.following',
|
||||
|
||||
components: {
|
||||
details: ProfileFollowers
|
||||
}
|
||||
}
|
||||
]
|
||||
details: ProfileFollowers,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/ostatus/follow',
|
||||
components: {
|
||||
default: Profile,
|
||||
details: ProfileTimeline
|
||||
details: ProfileTimeline,
|
||||
},
|
||||
props: true
|
||||
}
|
||||
]
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @copyright 2022 Louis Chemineau <mlouis@chmn.me>
|
||||
*
|
||||
* @author Louis Chemineau <mlouis@chmn.me>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* 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 { getCurrentUser } from '@nextcloud/auth'
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
const getLogger = user => {
|
||||
if (user === null) {
|
||||
return getLoggerBuilder()
|
||||
.setApp('social')
|
||||
.build()
|
||||
}
|
||||
return getLoggerBuilder()
|
||||
.setApp('social')
|
||||
.setUid(user.uid)
|
||||
.build()
|
||||
}
|
||||
|
||||
export default getLogger(getCurrentUser())
|
|
@ -27,7 +27,7 @@ import { generateUrl } from '@nextcloud/router'
|
|||
const state = {
|
||||
currentAccount: {},
|
||||
accounts: {},
|
||||
accountIdMap: {}
|
||||
accountIdMap: {},
|
||||
}
|
||||
const addAccount = (state, { actorId, data }) => {
|
||||
Vue.set(state.accounts, actorId, Object.assign({ followersList: [], followingList: [], details: { following: false, follower: false } }, state.accounts[actorId], data))
|
||||
|
@ -43,25 +43,25 @@ const mutations = {
|
|||
addAccount(state, { actorId, data })
|
||||
},
|
||||
addFollowers(state, { account, data }) {
|
||||
let users = []
|
||||
for (var index in data) {
|
||||
const users = []
|
||||
for (const index in data) {
|
||||
const actor = data[index].actor_info
|
||||
addAccount(state, {
|
||||
actorId: actor.id,
|
||||
data: actor
|
||||
data: actor,
|
||||
})
|
||||
}
|
||||
Vue.set(state.accounts[_getActorIdForAccount(account)], 'followersList', users)
|
||||
},
|
||||
addFollowing(state, { account, data }) {
|
||||
let users = []
|
||||
for (var index in data) {
|
||||
const users = []
|
||||
for (const index in data) {
|
||||
const actor = data[index].actor_info
|
||||
if (typeof actor !== 'undefined' && account !== actor.account) {
|
||||
users.push(actor.id)
|
||||
addAccount(state, {
|
||||
actorId: actor.id,
|
||||
data: actor
|
||||
data: actor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ const mutations = {
|
|||
},
|
||||
unfollowAccount(state, accountToUnfollow) {
|
||||
Vue.set(state.accounts[_getActorIdForAccount(accountToUnfollow)].details, 'following', false)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const getters = {
|
||||
|
@ -101,10 +101,10 @@ const getters = {
|
|||
},
|
||||
isFollowingUser(state) {
|
||||
return (followingAccount) => {
|
||||
let account = state.accounts[_getActorIdForAccount(followingAccount)]
|
||||
const account = state.accounts[_getActorIdForAccount(followingAccount)]
|
||||
return account && account.details ? account.details.following : false
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
|
@ -169,7 +169,7 @@ const actions = {
|
|||
axios.get(generateUrl(`apps/social/api/v1/account/${uid}/following`)).then((response) => {
|
||||
context.commit('addFollowing', { account, data: response.data.result })
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default { state, mutations, getters, actions }
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import timeline from './timeline'
|
||||
import account from './account'
|
||||
import settings from './settings'
|
||||
import timeline from './timeline.js'
|
||||
import account from './account.js'
|
||||
import settings from './settings.js'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
|
@ -35,7 +35,7 @@ export default new Vuex.Store({
|
|||
modules: {
|
||||
timeline,
|
||||
account,
|
||||
settings
|
||||
settings,
|
||||
},
|
||||
strict: debug
|
||||
strict: debug,
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
const state = {
|
||||
serverData: {}
|
||||
serverData: {},
|
||||
}
|
||||
const mutations = {
|
||||
setServerData(state, data) {
|
||||
|
@ -29,12 +29,12 @@ const mutations = {
|
|||
},
|
||||
setServerDataEntry(state, key, value) {
|
||||
state.serverData[key] = value
|
||||
}
|
||||
},
|
||||
}
|
||||
const getters = {
|
||||
getServerData(state) {
|
||||
return state.serverData
|
||||
}
|
||||
},
|
||||
}
|
||||
const actions = {}
|
||||
|
||||
|
|
|
@ -23,17 +23,17 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import Logger from '../logger'
|
||||
import logger from '../services/logger.js'
|
||||
import axios from '@nextcloud/axios'
|
||||
import Vue from 'vue'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
/**
|
||||
* @property {object} timeline - The posts' collection
|
||||
* @property {int} since - Time (EPOCH) of the most recent post
|
||||
* @property {string} type - Timeline's type: 'home', 'single-post',...
|
||||
* @property {object} params - Timeline's parameters
|
||||
* @property {string} account -
|
||||
* @property {object} timeline - The posts' collection
|
||||
* @property {int} since - Time (EPOCH) of the most recent post
|
||||
* @property {string} type - Timeline's type: 'home', 'single-post',...
|
||||
* @property {object} params - Timeline's parameters
|
||||
* @property {string} account -
|
||||
*/
|
||||
const state = {
|
||||
timeline: {},
|
||||
|
@ -41,10 +41,10 @@ const state = {
|
|||
type: 'home',
|
||||
/**
|
||||
* @namespace params
|
||||
* @property {string} account ???
|
||||
* @property {string} id
|
||||
* @property {string} localId
|
||||
* @property {string} type ???
|
||||
* @property {string} account ???
|
||||
* @property {string} id
|
||||
* @property {string} localId
|
||||
* @property {string} type ???
|
||||
*/
|
||||
params: {},
|
||||
account: '',
|
||||
|
@ -52,11 +52,11 @@ const state = {
|
|||
* It's up to the view to honor this status or not.
|
||||
* @member {boolean}
|
||||
*/
|
||||
composerDisplayStatus: false
|
||||
composerDisplayStatus: false,
|
||||
}
|
||||
const mutations = {
|
||||
addToTimeline(state, data) {
|
||||
for (let item in data) {
|
||||
for (const item in data) {
|
||||
state.since = data[item].publishedTime
|
||||
Vue.set(state.timeline, data[item].id, data[item])
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ const mutations = {
|
|||
if (typeof parentAnnounce.id !== 'undefined') {
|
||||
Vue.set(state.timeline[parentAnnounce.id].cache[parentAnnounce.object].object.action.values, 'boosted', false)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
const getters = {
|
||||
getComposerDisplayStatus(state) {
|
||||
|
@ -127,10 +127,10 @@ const getters = {
|
|||
if (typeof state.timeline[postId] !== 'undefined') {
|
||||
return state.timeline[postId]
|
||||
} else {
|
||||
Logger.warn('Could not find post in timeline', { postId: postId })
|
||||
logger.warn('Could not find post in timeline', { postId })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
const actions = {
|
||||
changeTimelineType(context, { type, params }) {
|
||||
|
@ -148,22 +148,22 @@ const actions = {
|
|||
try {
|
||||
const { data } = await axios.post(generateUrl('apps/social/api/v1/post'), post, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
Logger.info('Post created with token ' + data.result.token)
|
||||
} catch (error) {
|
||||
OC.Notification.showTemporary('Failed to create a post')
|
||||
Logger.error('Failed to create a post', { 'error': error.response })
|
||||
logger.info('Post created with token ' + data.result.token)
|
||||
} catch (error) {
|
||||
OC.Notification.showTemporary('Failed to create a post')
|
||||
logger.error('Failed to create a post', { error: error.response })
|
||||
}
|
||||
},
|
||||
postDelete(context, post) {
|
||||
return axios.delete(generateUrl(`apps/social/api/v1/post?id=${post.id}`)).then((response) => {
|
||||
context.commit('removePost', post)
|
||||
Logger.info('Post deleted with token ' + response.data.result.token)
|
||||
logger.info('Post deleted with token ' + response.data.result.token)
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to delete the post')
|
||||
Logger.error('Failed to delete the post', { 'error': error })
|
||||
logger.error('Failed to delete the post', { error })
|
||||
})
|
||||
},
|
||||
postLike(context, { post, parentAnnounce }) {
|
||||
|
@ -173,7 +173,7 @@ const actions = {
|
|||
resolve(response)
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to like post')
|
||||
Logger.error('Failed to like post', { 'error': error.response })
|
||||
logger.error('Failed to like post', { error: error.response })
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
@ -187,18 +187,18 @@ const actions = {
|
|||
}
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to unlike post')
|
||||
Logger.error('Failed to unlike post', { 'error': error })
|
||||
logger.error('Failed to unlike post', { error })
|
||||
})
|
||||
},
|
||||
postBoost(context, { post, parentAnnounce }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(generateUrl(`apps/social/api/v1/post/boost?postId=${post.id}`)).then((response) => {
|
||||
context.commit('boostPost', { post, parentAnnounce })
|
||||
Logger.info('Post boosted with token ' + response.data.result.token)
|
||||
logger.info('Post boosted with token ' + response.data.result.token)
|
||||
resolve(response)
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to create a boost post')
|
||||
Logger.error('Failed to create a boost post', { 'error': error.response })
|
||||
logger.error('Failed to create a boost post', { error: error.response })
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
@ -206,10 +206,10 @@ const actions = {
|
|||
postUnBoost(context, { post, parentAnnounce }) {
|
||||
return axios.delete(generateUrl(`apps/social/api/v1/post/boost?postId=${post.id}`)).then((response) => {
|
||||
context.commit('unboostPost', { post, parentAnnounce })
|
||||
Logger.info('Boost deleted with token ' + response.data.result.token)
|
||||
logger.info('Boost deleted with token ' + response.data.result.token)
|
||||
}).catch((error) => {
|
||||
OC.Notification.showTemporary('Failed to delete the boost')
|
||||
Logger.error('Failed to delete the boost', { 'error': error })
|
||||
logger.error('Failed to delete the boost', { error })
|
||||
})
|
||||
},
|
||||
refreshTimeline(context) {
|
||||
|
@ -248,7 +248,7 @@ const actions = {
|
|||
},
|
||||
addToTimeline(context, data) {
|
||||
context.commit('addToTimeline', data)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default { state, mutations, getters, actions }
|
||||
|
|
|
@ -26,8 +26,7 @@
|
|||
:show-more-text="title"
|
||||
:loading="state === 'loading'">
|
||||
<template #empty-content>
|
||||
<NcEmptyContent
|
||||
v-if="emptyContentMessage"
|
||||
<NcEmptyContent v-if="emptyContentMessage"
|
||||
:icon="emptyContentIcon">
|
||||
<template #desc>
|
||||
{{ emptyContentMessage }}
|
||||
|
@ -54,14 +53,14 @@ export default {
|
|||
|
||||
components: {
|
||||
NcDashboardWidget,
|
||||
NcEmptyContent
|
||||
NcEmptyContent,
|
||||
},
|
||||
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -71,7 +70,7 @@ export default {
|
|||
showMoreText: t('social', 'Social notifications'),
|
||||
loop: null,
|
||||
state: 'loading',
|
||||
appUrl: generateUrl('/apps/social')
|
||||
appUrl: generateUrl('/apps/social'),
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -85,7 +84,7 @@ export default {
|
|||
avatarUsername: this.getActorName(n),
|
||||
overlayIconUrl: this.getNotificationTypeImage(n),
|
||||
mainText: this.getMainText(n),
|
||||
subText: this.getSubline(n)
|
||||
subText: this.getSubline(n),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -109,7 +108,7 @@ export default {
|
|||
return 'icon-checkmark'
|
||||
}
|
||||
return 'icon-checkmark'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
|
@ -121,8 +120,8 @@ export default {
|
|||
fetchNotifications() {
|
||||
const req = {
|
||||
params: {
|
||||
limit: 10
|
||||
}
|
||||
limit: 10,
|
||||
},
|
||||
}
|
||||
const url = generateUrl('/apps/social/api/v1/stream/notifications')
|
||||
// TODO check why 'since' param is in fact 'until'
|
||||
|
@ -215,8 +214,8 @@ export default {
|
|||
return generateUrl('/svg/social/add_user')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -38,28 +38,6 @@
|
|||
<div v-else :class="{ 'icon-loading-dark': !account }" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h2, p {
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
p .icon {
|
||||
display: inline-block;
|
||||
}
|
||||
.avatardiv {
|
||||
vertical-align: -4px;
|
||||
margin-right: 3px;
|
||||
filter: drop-shadow(0 0 0.5rem #333);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
@ -69,18 +47,18 @@ import { loadState } from '@nextcloud/initial-state'
|
|||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
name: 'OStatus',
|
||||
components: {
|
||||
NcAvatar,
|
||||
},
|
||||
mixins: [
|
||||
accountMixins,
|
||||
currentuserMixin
|
||||
currentuserMixin,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
remote: '',
|
||||
account: {}
|
||||
account: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -99,9 +77,9 @@ export default {
|
|||
}
|
||||
|
||||
return (this.account.name ? this.account.name : this.account.preferredUsername)
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount: function() {
|
||||
beforeMount() {
|
||||
// importing server data into the store and fetching viewed account's information
|
||||
try {
|
||||
const serverData = loadState('social', 'serverData')
|
||||
|
@ -136,7 +114,29 @@ export default {
|
|||
},
|
||||
close() {
|
||||
window.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2, p {
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
p .icon {
|
||||
display: inline-block;
|
||||
}
|
||||
.avatardiv {
|
||||
vertical-align: -4px;
|
||||
margin-right: 3px;
|
||||
filter: drop-shadow(0 0 0.5rem #333);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,12 +22,12 @@
|
|||
|
||||
<template>
|
||||
<div :class="{'icon-loading': !accountLoaded}" class="social__wrapper">
|
||||
<profile-info v-if="accountLoaded && accountInfo" :uid="uid" />
|
||||
<ProfileInfo v-if="accountLoaded && accountInfo" :uid="uid" />
|
||||
<!-- TODO: we have no details, timeline and follower list for non-local accounts for now -->
|
||||
<router-view v-if="accountLoaded && accountInfo && accountInfo.local" name="details" />
|
||||
<NcEmptyContent v-if="accountLoaded && !accountInfo"
|
||||
:title="t('social', 'User not found')"
|
||||
:description="t('social', 'Sorry, we could not find the account of {userId}', { userId: this.uid })">
|
||||
:description="t('social', 'Sorry, we could not find the account of {userId}', { userId: uid })">
|
||||
<template #icon>
|
||||
<img :src="emptyContentImage"
|
||||
class="icon-illustration"
|
||||
|
@ -37,14 +37,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.social__wrapper.icon-loading {
|
||||
margin-top: 50vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ProfileInfo from './../components/ProfileInfo.vue'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
|
@ -56,20 +48,20 @@ export default {
|
|||
name: 'Profile',
|
||||
components: {
|
||||
NcEmptyContent,
|
||||
ProfileInfo
|
||||
ProfileInfo,
|
||||
},
|
||||
mixins: [
|
||||
accountMixins,
|
||||
serverData
|
||||
serverData,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
state: [],
|
||||
uid: null
|
||||
uid: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
timeline: function() {
|
||||
timeline() {
|
||||
return this.$store.getters.getTimeline
|
||||
},
|
||||
emptyContentImage() {
|
||||
|
@ -93,6 +85,14 @@ export default {
|
|||
this.$store.dispatch(fetchMethod, this.profileAccount).then((response) => {
|
||||
this.uid = response.account
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.social__wrapper.icon-loading {
|
||||
margin-top: 50vh;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -22,10 +22,44 @@
|
|||
|
||||
<template>
|
||||
<div class="social__followers">
|
||||
<user-entry v-for="user in users" :key="user.id" :item="user" />
|
||||
<UserEntry v-for="user in users" :key="user.id" :item="user" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserEntry from '../components/UserEntry.vue'
|
||||
import serverData from '../mixins/serverData.js'
|
||||
|
||||
export default {
|
||||
name: 'ProfileFollowers',
|
||||
components: {
|
||||
UserEntry,
|
||||
},
|
||||
mixins: [
|
||||
serverData,
|
||||
],
|
||||
computed: {
|
||||
profileAccount() {
|
||||
return (this.$route.params.account.indexOf('@') === -1) ? this.$route.params.account + '@' + this.hostname : this.$route.params.account
|
||||
},
|
||||
users() {
|
||||
if (this.$route.name === 'profile.followers') {
|
||||
return this.$store.getters.getAccountFollowers(this.profileAccount)
|
||||
} else {
|
||||
return this.$store.getters.getAccountFollowing(this.profileAccount)
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.$route.name === 'profile.followers') {
|
||||
this.$store.dispatch('fetchAccountFollowers', this.profileAccount)
|
||||
} else {
|
||||
this.$store.dispatch('fetchAccountFollowing', this.profileAccount)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.social__followers {
|
||||
width: 100%;
|
||||
|
@ -40,37 +74,3 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import UserEntry from '../components/UserEntry.vue'
|
||||
import serverData from '../mixins/serverData'
|
||||
|
||||
export default {
|
||||
name: 'ProfileFollowers',
|
||||
components: {
|
||||
UserEntry
|
||||
},
|
||||
mixins: [
|
||||
serverData
|
||||
],
|
||||
computed: {
|
||||
profileAccount() {
|
||||
return (this.$route.params.account.indexOf('@') === -1) ? this.$route.params.account + '@' + this.hostname : this.$route.params.account
|
||||
},
|
||||
users: function() {
|
||||
if (this.$route.name === 'profile.followers') {
|
||||
return this.$store.getters.getAccountFollowers(this.profileAccount)
|
||||
} else {
|
||||
return this.$store.getters.getAccountFollowing(this.profileAccount)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.$route.name === 'profile.followers') {
|
||||
this.$store.dispatch('fetchAccountFollowers', this.profileAccount)
|
||||
} else {
|
||||
this.$store.dispatch('fetchAccountFollowing', this.profileAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,19 +2,24 @@
|
|||
<div>
|
||||
<h2>Social</h2>
|
||||
<transition-group name="list" tag="div">
|
||||
<TimelineEntry v-for="entry in timeline" :key="entry.id" :item="entry" :isProfilePage="true" />
|
||||
<TimelineEntry v-for="entry in timeline"
|
||||
:key="entry.id"
|
||||
:item="entry" />
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProfileInfo from './../components/ProfileInfo.vue'
|
||||
import TimelineEntry from './../components/TimelineEntry.vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import logger from './../services/logger.js'
|
||||
|
||||
export default {
|
||||
name: 'ProfilePageIntegration',
|
||||
components: {
|
||||
TimelineEntry,
|
||||
},
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
|
@ -27,33 +32,27 @@ export default {
|
|||
timeline: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ProfileInfo,
|
||||
TimelineEntry,
|
||||
},
|
||||
computed: {
|
||||
getCount() {
|
||||
let account = this.accountInfo
|
||||
const account = this.accountInfo
|
||||
return (field) => account.details.count ? account.details.count[field] : ''
|
||||
},
|
||||
},
|
||||
// Start fetching account information before mounting the component
|
||||
beforeMount() {
|
||||
let fetchMethod = 'fetchPublicAccountInfo'
|
||||
|
||||
let uid = this.userId
|
||||
const uid = this.userId
|
||||
|
||||
axios.get(generateUrl(`apps/social/api/v1/account/${uid}/info`)).then((response) => {
|
||||
this.accountInfo = response.data.result.account
|
||||
console.log(this.accountInfo)
|
||||
logger.log(this.accountInfo)
|
||||
})
|
||||
|
||||
const since = Math.floor(Date.now() / 1000) + 1
|
||||
|
||||
axios.get(generateUrl(`apps/social/api/v1/account/${uid}/stream?limit=25&since=${since}`)).then(({ data }) => {
|
||||
console.log(this.timeline)
|
||||
logger.log(this.timeline)
|
||||
this.timeline = data.result
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -21,26 +21,26 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<timeline-list />
|
||||
<TimelineList />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import TimelineList from './../components/TimelineList.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProfileTimeline',
|
||||
components: {
|
||||
TimelineList
|
||||
TimelineList,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeMount: function() {
|
||||
beforeMount() {
|
||||
this.$store.dispatch('changeTimelineTypeAccount', this.$route.params.account)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -16,19 +16,88 @@
|
|||
</p>
|
||||
<div v-show="!isFollowingNextcloudAccount" class="follow-nextcloud">
|
||||
<p>{{ t('social', 'Since you are new to Social, start by following the official Nextcloud account so you don\'t miss any news') }}</p>
|
||||
<input :value="t('social', 'Follow Nextcloud on mastodon.xyz')" type="button" class="primary"
|
||||
<input :value="t('social', 'Follow Nextcloud on mastodon.xyz')"
|
||||
type="button"
|
||||
class="primary"
|
||||
@click="followNextcloud">
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<composer v-if="type !== 'notifications' && type !== 'single-post'" />
|
||||
<Composer v-if="type !== 'notifications' && type !== 'single-post'" />
|
||||
<h2 v-if="type === 'tags'">
|
||||
#{{ this.$route.params.tag }}
|
||||
#{{ $route.params.tag }}
|
||||
</h2>
|
||||
<timeline-list :type="type" />
|
||||
<TimelineList :type="type" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Composer from './../components/Composer/Composer.vue'
|
||||
import CurrentUserMixin from './../mixins/currentUserMixin.js'
|
||||
import follow from './../mixins/follow.js'
|
||||
import TimelineList from './../components/TimelineList.vue'
|
||||
|
||||
export default {
|
||||
name: 'Timeline',
|
||||
components: {
|
||||
Composer,
|
||||
TimelineList,
|
||||
},
|
||||
mixins: [
|
||||
CurrentUserMixin,
|
||||
follow,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
nextcloudAccount: 'nextcloud@mastodon.xyz',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
params() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return { tag: this.$route.params.tag }
|
||||
} else if (this.$route.name === 'single-post') {
|
||||
return this.$route.params
|
||||
}
|
||||
return {}
|
||||
},
|
||||
type() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return 'tags'
|
||||
}
|
||||
if (this.$route.params.type) {
|
||||
return this.$route.params.type
|
||||
}
|
||||
return 'home'
|
||||
},
|
||||
showInfo() {
|
||||
return this.$store.getters.getServerData.firstrun && !this.infoHidden
|
||||
},
|
||||
isFollowingNextcloudAccount() {
|
||||
if (!this.$store.getters.accountLoaded(this.nextcloudAccount)) {
|
||||
return true
|
||||
}
|
||||
return this.$store.getters.isFollowingUser(this.nextcloudAccount)
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.$store.dispatch('changeTimelineType', { type: this.type, params: this.params })
|
||||
if (this.showInfo) {
|
||||
this.$store.dispatch('fetchAccountInfo', this.nextcloudAccount)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideInfo() {
|
||||
this.infoHidden = true
|
||||
},
|
||||
followNextcloud() {
|
||||
this.$store.dispatch('followAccount', { accountToFollow: this.nextcloudAccount })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.social__welcome {
|
||||
|
@ -88,70 +157,3 @@
|
|||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Composer from './../components/Composer/Composer.vue'
|
||||
import CurrentUserMixin from './../mixins/currentUserMixin.js'
|
||||
import follow from './../mixins/follow.js'
|
||||
import TimelineList from './../components/TimelineList.vue'
|
||||
|
||||
export default {
|
||||
name: 'Timeline',
|
||||
components: {
|
||||
Composer,
|
||||
TimelineList
|
||||
},
|
||||
mixins: [
|
||||
CurrentUserMixin,
|
||||
follow
|
||||
],
|
||||
data: function() {
|
||||
return {
|
||||
infoHidden: false,
|
||||
nextcloudAccount: 'nextcloud@mastodon.xyz'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
params() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return { tag: this.$route.params.tag }
|
||||
} else if (this.$route.name === 'single-post') {
|
||||
return this.$route.params
|
||||
}
|
||||
return {}
|
||||
},
|
||||
type() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return 'tags'
|
||||
}
|
||||
if (this.$route.params.type) {
|
||||
return this.$route.params.type
|
||||
}
|
||||
return 'home'
|
||||
},
|
||||
showInfo() {
|
||||
return this.$store.getters.getServerData.firstrun && !this.infoHidden
|
||||
},
|
||||
isFollowingNextcloudAccount() {
|
||||
if (!this.$store.getters.accountLoaded(this.nextcloudAccount)) {
|
||||
return true
|
||||
}
|
||||
return this.$store.getters.isFollowingUser(this.nextcloudAccount)
|
||||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
this.$store.dispatch('changeTimelineType', { type: this.type, params: this.params })
|
||||
if (this.showInfo) {
|
||||
this.$store.dispatch('fetchAccountInfo', this.nextcloudAccount)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideInfo() {
|
||||
this.infoHidden = true
|
||||
},
|
||||
followNextcloud() {
|
||||
this.$store.dispatch('followAccount', { accountToFollow: this.nextcloudAccount })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
<template>
|
||||
<div class="social__wrapper">
|
||||
<profile-info v-if="accountLoaded && accountInfo" :uid="uid" />
|
||||
<composer v-show="composerDisplayStatus" />
|
||||
<timeline-entry class="main-post" :item="mainPost" />
|
||||
<timeline-list v-if="timeline" :type="$route.params.type" />
|
||||
<ProfileInfo v-if="accountLoaded && accountInfo" :uid="uid" />
|
||||
<Composer v-show="composerDisplayStatus" />
|
||||
<TimelineEntry class="main-post" :item="mainPost" />
|
||||
<TimelineList v-if="timeline" :type="$route.params.type" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.social__timeline {
|
||||
max-width: 600px;
|
||||
margin: 15px auto;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Composer from '../components/Composer/Composer.vue'
|
||||
import ProfileInfo from '../components/ProfileInfo.vue'
|
||||
|
@ -36,43 +23,43 @@ export default {
|
|||
Composer,
|
||||
ProfileInfo,
|
||||
TimelineEntry,
|
||||
TimelineList
|
||||
TimelineList,
|
||||
},
|
||||
mixins: [
|
||||
accountMixins,
|
||||
currentUserMixin,
|
||||
serverData
|
||||
serverData,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
mainPost: {},
|
||||
uid: this.$route.params.account
|
||||
uid: this.$route.params.account,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* @description Tells whether Composer shall be displayed or not
|
||||
* @returns {boolean}
|
||||
* @return {boolean}
|
||||
*/
|
||||
composerDisplayStatus() {
|
||||
return this.$store.getters.getComposerDisplayStatus
|
||||
},
|
||||
/**
|
||||
* @description Extracts the viewed account name from the URL
|
||||
* @returns {String}
|
||||
* @return {string}
|
||||
*/
|
||||
account() {
|
||||
return window.location.href.split('/')[window.location.href.split('/').length - 2].slice(1)
|
||||
},
|
||||
/**
|
||||
* @description Returns the timeline currently loaded in the store
|
||||
* @returns {Object}
|
||||
* @return {object}
|
||||
*/
|
||||
timeline: function() {
|
||||
timeline() {
|
||||
return this.$store.getters.getTimeline
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount: function() {
|
||||
beforeMount() {
|
||||
|
||||
// Get data of post clicked on
|
||||
if (typeof this.$route.params.id === 'undefined') {
|
||||
|
@ -89,22 +76,35 @@ export default {
|
|||
})
|
||||
|
||||
// Fetch single post timeline
|
||||
let params = {
|
||||
const params = {
|
||||
account: this.account,
|
||||
id: window.location.href,
|
||||
localId: window.location.href.split('/')[window.location.href.split('/').length - 1],
|
||||
type: 'single-post'
|
||||
type: 'single-post',
|
||||
}
|
||||
this.$store.dispatch('changeTimelineType', {
|
||||
type: 'single-post',
|
||||
params: params
|
||||
params,
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.social__timeline {
|
||||
max-width: 600px;
|
||||
margin: 15px auto;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
Ładowanie…
Reference in New Issue