mirror of https://github.com/nextcloud/bookmarks
Display archived content in full-page overlay
next to sidebar; only display if available Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
parent
2e2533f2ab
commit
a228f07b20
|
@ -43,6 +43,7 @@ use OCP\AppFramework\Http\NotFoundResponse;
|
|||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use OCP\IURLGenerator;
|
||||
|
@ -114,9 +115,13 @@ class BookmarkController extends ApiController {
|
|||
* @var FolderService
|
||||
*/
|
||||
private $folders;
|
||||
/**
|
||||
* @var IRootFolder
|
||||
*/
|
||||
private $rootFolder;
|
||||
|
||||
public function __construct(
|
||||
$appName, $request, IL10N $l10n, BookmarkMapper $bookmarkMapper, TagMapper $tagMapper, FolderMapper $folderMapper, TreeMapper $treeMapper, PublicFolderMapper $publicFolderMapper, ITimeFactory $timeFactory, LoggerInterface $logger, IURLGenerator $url, HtmlExporter $htmlExporter, Authorizer $authorizer, BookmarkService $bookmarks, FolderService $folders
|
||||
$appName, $request, IL10N $l10n, BookmarkMapper $bookmarkMapper, TagMapper $tagMapper, FolderMapper $folderMapper, TreeMapper $treeMapper, PublicFolderMapper $publicFolderMapper, ITimeFactory $timeFactory, LoggerInterface $logger, IURLGenerator $url, HtmlExporter $htmlExporter, Authorizer $authorizer, BookmarkService $bookmarks, FolderService $folders, IRootFolder $rootFolder
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->request = $request;
|
||||
|
@ -133,6 +138,7 @@ class BookmarkController extends ApiController {
|
|||
$this->authorizer = $authorizer;
|
||||
$this->bookmarks = $bookmarks;
|
||||
$this->folders = $folders;
|
||||
$this->rootFolder = $rootFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +162,13 @@ class BookmarkController extends ApiController {
|
|||
if (!isset($array['tags'])) {
|
||||
$array['tags'] = $this->tagMapper->findByBookmark($bookmark->getId());
|
||||
}
|
||||
if ($array['archivedFile'] !== 0) {
|
||||
$results = $this->rootFolder->getById($array['archivedFile']);
|
||||
if (count($results)) {
|
||||
$array['archivedFilePath'] = $results[0]->getPath();
|
||||
$array['archivedFileType'] = $results[0]->getMimePart();
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ class WebViewController extends Controller {
|
|||
$policy->addAllowedWorkerSrcDomain("'self'");
|
||||
$policy->addAllowedScriptDomain("'self'");
|
||||
$policy->addAllowedConnectDomain("'self'");
|
||||
$policy->addAllowedFrameDomain("'self'");
|
||||
$res->setContentSecurityPolicy($policy);
|
||||
|
||||
// Provide complete folder hierarchy
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<!--
|
||||
- Copyright (c) 2021. The Nextcloud Bookmarks contributors.
|
||||
-
|
||||
- This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="isActive && hasMinLength" class="bookmark-content">
|
||||
<template v-if="archivedFile">
|
||||
<div class="content iframe">
|
||||
<iframe :src="archivedFileUrl" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="bookmark.textContent" class="content" v-html="content" />
|
||||
<div v-else>
|
||||
<EmptyContent icon="icon-download">
|
||||
{{ t('bookmarks', 'Content pending') }}
|
||||
<template #desc>
|
||||
{{ t('bookmarks', ' This content is being downloaded for offline use. Please check back later.') }}
|
||||
</template>
|
||||
</EmptyContent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
import { generateUrl, generateRemoteUrl } from '@nextcloud/router'
|
||||
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
|
||||
|
||||
const MIN_TEXT_LENGTH = 350
|
||||
|
||||
export default {
|
||||
name: 'BookmarkContent',
|
||||
components: { EmptyContent },
|
||||
computed: {
|
||||
isActive() {
|
||||
if (!this.$store.state.sidebar) return false
|
||||
return this.$store.state.sidebar.type === 'bookmark'
|
||||
},
|
||||
bookmark() {
|
||||
if (!this.isActive) return
|
||||
return this.$store.getters.getBookmark(this.$store.state.sidebar.id)
|
||||
},
|
||||
hasMinLength() {
|
||||
return !this.bookmark.textContent || this.bookmark.textContent.length >= MIN_TEXT_LENGTH
|
||||
},
|
||||
content() {
|
||||
return sanitizeHtml(this.bookmark.htmlContent, {
|
||||
allowProtocolRelative: false,
|
||||
})
|
||||
},
|
||||
archivedFileUrl() {
|
||||
// remove `/username/files/`
|
||||
const barePath = this.bookmark.archivedFilePath.split('/').slice(3).join('/')
|
||||
return generateRemoteUrl(`webdav/${barePath}`)
|
||||
},
|
||||
archivedFile() {
|
||||
if (this.bookmark.archivedFile) {
|
||||
return generateUrl(`/apps/files/?fileid=${this.bookmark.archivedFile}`)
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bookmark-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--color-main-background);
|
||||
z-index: 110;
|
||||
display: flex;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
.bookmark-content .content {
|
||||
padding: 30px 30px;
|
||||
font-size: 15px;
|
||||
text-align: justify;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.bookmark-content .content.iframe {
|
||||
margin: 0 -30px;
|
||||
margin-bottom: -30px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bookmark-content .content iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bookmark-content h1, .bookmark-content h2, .bookmark-content h3, .bookmark-content h4, .bookmark-content h5, .bookmark-content p {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
.bookmark-content a:link,
|
||||
.bookmark-content a[href] {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
</style>
|
|
@ -1,66 +0,0 @@
|
|||
<!--
|
||||
- Copyright (c) 2020. The Nextcloud Bookmarks contributors.
|
||||
-
|
||||
- This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Modal :title="title" @close="$emit('close', $event)">
|
||||
<div class="content-modal">
|
||||
<h1 class="title">
|
||||
{{ bookmark.title }}
|
||||
</h1>
|
||||
<div v-html="content" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
export default {
|
||||
name: 'ContentModal',
|
||||
components: {
|
||||
Modal,
|
||||
},
|
||||
props: {
|
||||
bookmark: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.bookmark.title
|
||||
},
|
||||
content() {
|
||||
return sanitizeHtml(this.bookmark.htmlContent, {
|
||||
allowProtocolRelative: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.content-modal {
|
||||
overflow-y: scroll;
|
||||
padding: 44px;
|
||||
text-wrap: normal;
|
||||
height: 700px;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.content-modal .title {
|
||||
font-size: 2em;
|
||||
margin: 22px 0;
|
||||
}
|
||||
|
||||
.content-modal h1, .content-modal h2, .content-modal h3, .content-modal h4, .content-modal h5, .content-modal p {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
.content-modal a:link,
|
||||
.content-modal a[href] {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
</style>
|
|
@ -30,56 +30,57 @@
|
|||
icon="icon-info"
|
||||
:order="0">
|
||||
<div>
|
||||
<div v-if="!editingUrl" class="bookmark-details__line">
|
||||
<span class="bookmark-details__url">{{ bookmark.url }}</span>
|
||||
<Actions v-if="isEditable" class="bookmark-details__action">
|
||||
<div v-if="!editingUrl" class="details__line">
|
||||
<span class="icon-external" :aria-label="t('bookmarks', 'Link')" :title="t('bookmarks', 'Link')" />
|
||||
<span class="details__url">{{ bookmark.url }}</span>
|
||||
<Actions v-if="isEditable" class="details__action">
|
||||
<ActionButton icon="icon-rename" @click="onEditUrl" />
|
||||
</Actions>
|
||||
</div>
|
||||
<div v-else class="bookmark-details__line">
|
||||
<input v-model="url" class="bookmark-details__url">
|
||||
<Actions class="bookmark-details__action">
|
||||
<div v-else class="details__line">
|
||||
<span class="icon-external" :aria-label="t('bookmarks', 'Link')" :title="t('bookmarks', 'Link')" />
|
||||
<input v-model="url" class="details__url">
|
||||
<Actions class="details__action">
|
||||
<ActionButton icon="icon-confirm" @click="onEditUrlSubmit" />
|
||||
</Actions>
|
||||
<Actions class="bookmark-details__action">
|
||||
<Actions class="details__action">
|
||||
<ActionButton icon="icon-close" @click="onEditUrlCancel" />
|
||||
</Actions>
|
||||
</div>
|
||||
<div class="details__line">
|
||||
<span class="icon-tag" :aria-label="t('bookmarks', 'Tags')" :title="t('bookmarks', 'Tags')" />
|
||||
<Multiselect
|
||||
class="tags"
|
||||
:value="tags"
|
||||
:auto-limit="false"
|
||||
:limit="7"
|
||||
:options="allTags"
|
||||
:multiple="true"
|
||||
:taggable="true"
|
||||
:placeholder="t('bookmarks', 'Select tags and create new ones')"
|
||||
:disabled="!isEditable"
|
||||
@input="onTagsChange"
|
||||
@tag="onAddTag" />
|
||||
</div>
|
||||
<div class="details__line">
|
||||
<span class="icon-edit"
|
||||
role="figure"
|
||||
:aria-label="t('bookmarks', 'Notes')"
|
||||
:title="t('bookmarks', 'Notes')" />
|
||||
<RichContenteditable
|
||||
:value.sync="bookmark.description"
|
||||
:contenteditable="isEditable"
|
||||
:auto-complete="() => {}"
|
||||
:placeholder="t('bookmarks', 'Notes for this bookmark …')"
|
||||
:multiline="true"
|
||||
class="notes"
|
||||
@update:value="onNotesChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="archivedFile">
|
||||
<h3><ArchiveArrowDownIcon slot="icon" :size="18" /> {{ t('bookmarks', 'Archived file') }}</h3>
|
||||
<a :href="archivedFile" class="button">{{ t('bookmarks', 'Open archived file') }}</a>
|
||||
</div>
|
||||
<div v-else-if="bookmark.textContent">
|
||||
<h3><ArchiveArrowDownIcon slot="icon" :size="18" /> {{ t('bookmarks', 'Archived content') }}</h3>
|
||||
<blockquote v-text="bookmark.textContent.substr(0, 250)+'...'" />
|
||||
<a href="javascript:void(0)" class="button" @click="showContentModal = true">{{ t('bookmarks', 'Read more') }}</a>
|
||||
<ContentModal v-if="showContentModal" :bookmark="bookmark" @close="showContentModal = false" />
|
||||
</div>
|
||||
<div>
|
||||
<h3><span class="icon-tag" /> {{ t('bookmarks', 'Tags') }}</h3>
|
||||
<Multiselect
|
||||
class="sidebar__tags"
|
||||
:value="tags"
|
||||
:auto-limit="false"
|
||||
:limit="7"
|
||||
:options="allTags"
|
||||
:multiple="true"
|
||||
:taggable="true"
|
||||
:placeholder="t('bookmarks', 'Select tags and create new ones')"
|
||||
:disabled="!isEditable"
|
||||
@input="onTagsChange"
|
||||
@tag="onAddTag" />
|
||||
</div>
|
||||
<div>
|
||||
<h3><span class="icon-edit" /> {{ t('bookmarks', 'Notes') }}</h3>
|
||||
<RichContenteditable
|
||||
:value.sync="bookmark.description"
|
||||
:contenteditable="isEditable"
|
||||
:auto-complete="() => {}"
|
||||
:placeholder="t('bookmarks', 'Notes for this bookmark …')"
|
||||
:multiline="true"
|
||||
@update:value="onNotesChange" />
|
||||
<h3><FileDocumentIcon slot="icon" :size="18" /> {{ t('bookmarks', 'Archived file') }}</h3>
|
||||
<a class="button" :href="archivedFileUrl" target="_blank"><FileDocumentIcon :size="18" :fill-color="colorMainText" /> {{ t('bookmarks', 'Open File') }}</a>
|
||||
<a class="button" :href="archivedFile" target="_blank"><span class="icon-files-dark" /> {{ t('bookmarks', 'Open File location') }}</a>
|
||||
</div>
|
||||
</AppSidebarTab>
|
||||
</AppSidebar>
|
||||
|
@ -91,19 +92,18 @@ import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
|||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import RichContenteditable from '@nextcloud/vue/dist/Components/RichContenteditable'
|
||||
import ArchiveArrowDownIcon from 'vue-material-design-icons/ArchiveArrowDown'
|
||||
import FileDocumentIcon from 'vue-material-design-icons/FileDocument'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
|
||||
import humanizeDuration from 'humanize-duration'
|
||||
import { actions, mutations } from '../store/'
|
||||
import ContentModal from './ContentModal'
|
||||
|
||||
const MAX_RELATIVE_DATE = 1000 * 60 * 60 * 24 * 7 // one week
|
||||
|
||||
export default {
|
||||
name: 'SidebarBookmark',
|
||||
components: { ContentModal, AppSidebar, AppSidebarTab, Multiselect, Actions, ActionButton, RichContenteditable, ArchiveArrowDownIcon },
|
||||
components: { AppSidebar, AppSidebarTab, Multiselect, Actions, ActionButton, RichContenteditable, FileDocumentIcon },
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
|
@ -163,6 +163,11 @@ export default {
|
|||
}
|
||||
return null
|
||||
},
|
||||
archivedFileUrl() {
|
||||
// remove `/username/files/`
|
||||
const barePath = this.bookmark.archivedFilePath.split('/').slice(3).join('/')
|
||||
return generateRemoteUrl(`webdav/${barePath}`)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
},
|
||||
|
@ -229,36 +234,56 @@ export default {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.sidebar .details__line > span[class^='icon-'],
|
||||
.sidebar .details__line > .material-design-icon {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 11px;
|
||||
opacity: 0.5;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sidebar__tags {
|
||||
.sidebar .tags {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar__notes {
|
||||
min-height: 200px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.bookmark-details__line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bookmark-details__url {
|
||||
.sidebar .notes {
|
||||
flex-grow: 1;
|
||||
padding: 8px 0;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.bookmark-details__action {
|
||||
.sidebar .details__line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sidebar .details__line > * {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.sidebar blockquote {
|
||||
border-left: var(--color-placeholder-dark) 3px solid;
|
||||
padding-left: 10px;
|
||||
color: var(--color-text-lighter);
|
||||
margin: 10px 0;
|
||||
.sidebar .details__line > :nth-child(2) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sidebar .details__line .notes {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sidebar .details__url {
|
||||
flex-grow: 1;
|
||||
padding: 8px 0;
|
||||
text-overflow: ellipsis;
|
||||
height: 2em;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar .details__action {
|
||||
flex-grow: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<AppContent>
|
||||
<Controls />
|
||||
<BookmarksList :bookmarks="bookmarks" />
|
||||
<BookmarkContent />
|
||||
</AppContent>
|
||||
<SidebarBookmark />
|
||||
<SidebarFolder />
|
||||
|
@ -30,11 +31,13 @@ import MoveDialog from './MoveDialog'
|
|||
import { privateRoutes } from '../router'
|
||||
import { actions, mutations } from '../store/'
|
||||
import LoadingModal from './LoadingModal'
|
||||
import BookmarkContent from './BookmarkContent'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
export default {
|
||||
name: 'ViewPrivate',
|
||||
components: {
|
||||
BookmarkContent,
|
||||
LoadingModal,
|
||||
Navigation,
|
||||
Content,
|
||||
|
|
|
@ -29,6 +29,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
|
|||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
|
@ -188,10 +189,13 @@ class BookmarkControllerTest extends TestCase {
|
|||
$this->authorizer = OC::$server->get(Authorizer::class);
|
||||
$this->folders = OC::$server->get(FolderService::class);
|
||||
|
||||
$this->controller = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders);
|
||||
$this->otherController = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders);
|
||||
/** @var IRootFolder $rootFolder */
|
||||
$rootFolder = OC::$server->get(IRootFolder::class);
|
||||
|
||||
$this->publicController = new BookmarkController('bookmarks', $this->publicRequest, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders);
|
||||
$this->controller = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
$this->otherController = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
|
||||
$this->publicController = new BookmarkController('bookmarks', $this->publicRequest, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue