Let's not support oci yet

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
update-master-target-versions
Carl Schwan 2022-10-27 15:07:12 +02:00
rodzic ffe5f1bacb
commit db3cb63810
44 zmienionych plików z 893 dodań i 1222 usunięć

Wyświetl plik

@ -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
}
}
};

Wyświetl plik

@ -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

2
package-lock.json wygenerowano
Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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">

Wyświetl plik

@ -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>

Wyświetl plik

@ -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">

Wyświetl plik

@ -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>

Wyświetl plik

@ -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)
})
})

Wyświetl plik

@ -23,9 +23,9 @@
import Vue from 'vue'
export default {
bind: function(el) {
bind(el) {
Vue.nextTick(() => {
el.focus()
})
}
},
}

Wyświetl plik

@ -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')

Wyświetl plik

@ -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)
}
}
},
},
}

Wyświetl plik

@ -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
}
}
},
},
}

Wyświetl plik

@ -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)
})
}
}
},
},
}

Wyświetl plik

@ -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
}
}
},
},
}

Wyświetl plik

@ -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
}
}
},
},
}

Wyświetl plik

@ -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')

Wyświetl plik

@ -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
})

Wyświetl plik

@ -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,
},
],
})

Wyświetl plik

@ -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())

Wyświetl plik

@ -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 }

Wyświetl plik

@ -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,
})

Wyświetl plik

@ -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 = {}

Wyświetl plik

@ -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 }

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>