mirror of
https://github.com/nextcloud/richdocuments.git
synced 2025-12-17 21:12:14 +01:00
chore: refactor iframes to load collabora directly
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
parent
193640dbc6
commit
ba45233bff
19 changed files with 662 additions and 477 deletions
|
|
@ -32,6 +32,7 @@ return [
|
|||
['name' => 'document#remote', 'url' => 'remote', 'verb' => 'GET'],
|
||||
['name' => 'document#createFromTemplate', 'url' => 'indexTemplate', 'verb' => 'GET'],
|
||||
['name' => 'document#publicPage', 'url' => '/public', 'verb' => 'GET'],
|
||||
['name' => 'document#token', 'url' => '/token', 'verb' => 'POST'],
|
||||
|
||||
['name' => 'document#editOnline', 'url' => 'editonline', 'verb' => 'GET'],
|
||||
['name' => 'document#editOnlineTarget', 'url' => 'editonline/{fileId}/{target}', 'verb' => 'GET'],
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('Public sharing of office documents', function() {
|
|||
cy.spy(win, 'postMessage').as('postMessage')
|
||||
},
|
||||
})
|
||||
cy.waitForCollabora()
|
||||
cy.waitForCollabora(true)
|
||||
cy.get('@loleafletframe').within(() => {
|
||||
cy.get('#closebutton').click()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,57 +46,20 @@ use Psr\Log\LoggerInterface;
|
|||
class DirectViewController extends Controller {
|
||||
use DocumentTrait;
|
||||
|
||||
/** @var IRootFolder */
|
||||
private $rootFolder;
|
||||
|
||||
/** @var TokenManager */
|
||||
private $tokenManager;
|
||||
|
||||
/** @var DirectMapper */
|
||||
private $directMapper;
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
|
||||
/** @var TemplateManager */
|
||||
private $templateManager;
|
||||
|
||||
/** @var FederationService */
|
||||
private $federationService;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/** @var InitialStateService */
|
||||
private $initialState;
|
||||
|
||||
public function __construct(
|
||||
$appName,
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
IRootFolder $rootFolder,
|
||||
TokenManager $tokenManager,
|
||||
DirectMapper $directMapper,
|
||||
InitialStateService $initialState,
|
||||
IConfig $config,
|
||||
AppConfig $appConfig,
|
||||
TemplateManager $templateManager,
|
||||
FederationService $federationService,
|
||||
LoggerInterface $logger
|
||||
private IRootFolder $rootFolder,
|
||||
private TokenManager $tokenManager,
|
||||
private DirectMapper $directMapper,
|
||||
private InitialStateService $initialState,
|
||||
private IConfig $config,
|
||||
private AppConfig $appConfig,
|
||||
private TemplateManager $templateManager,
|
||||
private FederationService $federationService,
|
||||
private LoggerInterface $logger
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->directMapper = $directMapper;
|
||||
$this->initialState = $initialState;
|
||||
$this->config = $config;
|
||||
$this->appConfig = $appConfig;
|
||||
$this->templateManager = $templateManager;
|
||||
$this->federationService = $federationService;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,7 +120,7 @@ class DirectViewController extends Controller {
|
|||
return $response;
|
||||
}
|
||||
|
||||
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($item->getId(), null, $direct->getUid(), true);
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($item->getId(), null, $direct->getUid(), true);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Failed to generate token for existing file on direct editing', ['exception' => $e]);
|
||||
return $this->renderErrorPage('Failed to open the requested file.');
|
||||
|
|
@ -218,11 +181,11 @@ class DirectViewController extends Controller {
|
|||
'directGuest' => empty($direct->getUid()),
|
||||
];
|
||||
|
||||
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($node->getId(), $direct->getShare(), $direct->getUid(), true);
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($node->getId(), $direct->getShare(), $direct->getUid(), true);
|
||||
if (!empty($direct->getInitiatorHost())) {
|
||||
$this->tokenManager->upgradeFromDirectInitiator($direct, $wopi);
|
||||
}
|
||||
$params['token'] = $token;
|
||||
$params['token'] = $wopi->getToken();
|
||||
$params['token_ttl'] = $wopi->getExpiry();
|
||||
$params['urlsrc'] = $urlSrc;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,17 @@ use \OCP\AppFramework\Controller;
|
|||
use \OCP\AppFramework\Http\TemplateResponse;
|
||||
use \OCP\IConfig;
|
||||
use \OCP\IRequest;
|
||||
use Exception;
|
||||
use OC\User\NoUserException;
|
||||
use OCA\Richdocuments\Service\FederationService;
|
||||
use OCA\Richdocuments\Service\InitialStateService;
|
||||
use OCA\Richdocuments\TemplateManager;
|
||||
use OCA\Richdocuments\TokenManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\Constants;
|
||||
use OCP\Files\File;
|
||||
|
|
@ -41,59 +46,23 @@ class DocumentController extends Controller {
|
|||
|
||||
public const SESSION_FILE_TARGET = 'richdocuments_openfile_target';
|
||||
|
||||
/** @var ?string */
|
||||
private $uid;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var AppConfig */
|
||||
private $appConfig;
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
/** @var IManager */
|
||||
private $shareManager;
|
||||
/** @var TokenManager */
|
||||
private $tokenManager;
|
||||
/** @var ISession */
|
||||
private $session;
|
||||
/** @var IRootFolder */
|
||||
private $rootFolder;
|
||||
/** @var TemplateManager */
|
||||
private $templateManager;
|
||||
/** @var FederationService */
|
||||
private $federationService;
|
||||
/** @var InitialStateService */
|
||||
private $initialState;
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
$appName,
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
IConfig $config,
|
||||
AppConfig $appConfig,
|
||||
IManager $shareManager,
|
||||
TokenManager $tokenManager,
|
||||
IRootFolder $rootFolder,
|
||||
ISession $session,
|
||||
$UserId,
|
||||
LoggerInterface $logger,
|
||||
TemplateManager $templateManager,
|
||||
FederationService $federationService,
|
||||
InitialStateService $initialState,
|
||||
IURLGenerator $urlGenerator
|
||||
private IConfig $config,
|
||||
private AppConfig $appConfig,
|
||||
private IManager $shareManager,
|
||||
private TokenManager $tokenManager,
|
||||
private IRootFolder $rootFolder,
|
||||
private ISession $session,
|
||||
private ?string $userId,
|
||||
private LoggerInterface $logger,
|
||||
private TemplateManager $templateManager,
|
||||
private FederationService $federationService,
|
||||
private InitialStateService $initialState,
|
||||
private IURLGenerator $urlGenerator
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->uid = $UserId;
|
||||
$this->config = $config;
|
||||
$this->appConfig = $appConfig;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->session = $session;
|
||||
$this->logger = $logger;
|
||||
$this->templateManager = $templateManager;
|
||||
$this->federationService = $federationService;
|
||||
$this->initialState = $initialState;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,7 +75,7 @@ class DocumentController extends Controller {
|
|||
*
|
||||
* @return array access_token, urlsrc
|
||||
*/
|
||||
public function extAppGetData(int $fileId) {
|
||||
public function extAppGetData(int $fileId): array {
|
||||
$secretToken = $this->request->getParam('secret_token');
|
||||
$apps = array_filter(explode(',', $this->appConfig->getAppValue('external_apps')));
|
||||
foreach ($apps as $app) {
|
||||
|
|
@ -118,18 +87,18 @@ class DocumentController extends Controller {
|
|||
'fileId' => $fileId
|
||||
]);
|
||||
try {
|
||||
$folder = $this->rootFolder->getUserFolder($this->uid);
|
||||
$folder = $this->rootFolder->getUserFolder($this->userId);
|
||||
$item = $folder->getById($fileId)[0];
|
||||
if (!($item instanceof Node)) {
|
||||
throw new \Exception();
|
||||
throw new Exception();
|
||||
}
|
||||
list($urlSrc, $token) = $this->tokenManager->getToken($item->getId());
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($item->getId());
|
||||
return [
|
||||
'status' => 'success',
|
||||
'urlsrc' => $urlSrc,
|
||||
'token' => $token
|
||||
'token' => $wopi->getToken()
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +119,7 @@ class DocumentController extends Controller {
|
|||
*/
|
||||
public function index($fileId, ?string $path = null) {
|
||||
try {
|
||||
$folder = $this->rootFolder->getUserFolder($this->uid);
|
||||
$folder = $this->rootFolder->getUserFolder($this->userId);
|
||||
|
||||
if ($path !== null) {
|
||||
$item = $folder->get($path);
|
||||
|
|
@ -159,7 +128,7 @@ class DocumentController extends Controller {
|
|||
}
|
||||
|
||||
if (!($item instanceof File)) {
|
||||
throw new \Exception();
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -175,17 +144,17 @@ class DocumentController extends Controller {
|
|||
|
||||
$templateFile = $this->templateManager->getTemplateSource($item->getId());
|
||||
if ($templateFile) {
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($templateFile, $this->uid, $item->getId());
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($templateFile, $this->userId, $item->getId());
|
||||
$token = $wopi->getToken();
|
||||
} else {
|
||||
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($item->getId());
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($item->getId());
|
||||
}
|
||||
|
||||
$params = [
|
||||
'permissions' => $item->getPermissions(),
|
||||
'title' => $item->getName(),
|
||||
'fileId' => $item->getId() . '_' . $this->config->getSystemValue('instanceid'),
|
||||
'token' => $token,
|
||||
'token' => $wopi->getToken(),
|
||||
'token_ttl' => $wopi->getExpiry(),
|
||||
'urlsrc' => $urlSrc,
|
||||
'path' => $folder->getRelativePath($item->getPath()),
|
||||
|
|
@ -210,7 +179,7 @@ class DocumentController extends Controller {
|
|||
}
|
||||
|
||||
return $this->documentTemplateResponse($wopi, $params);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
return $this->renderErrorPage('Failed to open the requested file.');
|
||||
}
|
||||
|
|
@ -234,7 +203,7 @@ class DocumentController extends Controller {
|
|||
return new TemplateResponse('core', '403', [], 'guest');
|
||||
}
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->uid);
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
try {
|
||||
$folder = $userFolder->get($dir);
|
||||
} catch (NotFoundException $e) {
|
||||
|
|
@ -248,7 +217,7 @@ class DocumentController extends Controller {
|
|||
$file = $folder->newFile($fileName);
|
||||
|
||||
$template = $this->templateManager->get($templateId);
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($template, $this->uid, $file->getId());
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($template, $this->userId, $file->getId());
|
||||
|
||||
$wopiFileId = $wopi->getFileid() . '_' . $this->config->getSystemValue('instanceid');
|
||||
|
||||
|
|
@ -272,7 +241,7 @@ class DocumentController extends Controller {
|
|||
* @param string $shareToken
|
||||
* @param string $fileName
|
||||
* @return TemplateResponse|RedirectResponse
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function publicPage($shareToken, $fileName, $fileId) {
|
||||
try {
|
||||
|
|
@ -282,7 +251,7 @@ class DocumentController extends Controller {
|
|||
if (!$this->session->exists('public_link_authenticated')
|
||||
|| $this->session->get('public_link_authenticated') !== (string)$share->getId()
|
||||
) {
|
||||
throw new \Exception('Invalid password');
|
||||
throw new Exception('Invalid password');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +284,7 @@ class DocumentController extends Controller {
|
|||
if ($templateFile) {
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($templateFile, $share->getShareOwner(), $item->getId());
|
||||
} else {
|
||||
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($item->getId(), $shareToken, $this->uid);
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($item->getId(), $shareToken, $this->userId);
|
||||
}
|
||||
$params['token'] = $wopi->getToken();
|
||||
$params['token_ttl'] = $wopi->getExpiry();
|
||||
|
|
@ -324,7 +293,7 @@ class DocumentController extends Controller {
|
|||
|
||||
return $this->documentTemplateResponse($wopi, $params);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
return $this->renderErrorPage('Failed to open the requested file.');
|
||||
}
|
||||
|
|
@ -352,7 +321,7 @@ class DocumentController extends Controller {
|
|||
if (!$this->session->exists('public_link_authenticated')
|
||||
|| $this->session->get('public_link_authenticated') !== (string)$share->getId()
|
||||
) {
|
||||
throw new \Exception('Invalid password');
|
||||
throw new Exception('Invalid password');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,11 +335,11 @@ class DocumentController extends Controller {
|
|||
}
|
||||
|
||||
if ($node instanceof Node) {
|
||||
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($node->getId(), $shareToken, $this->uid);
|
||||
list($urlSrc, $wopi) = $this->tokenManager->getToken($node->getId(), $shareToken, $this->userId);
|
||||
|
||||
$remoteWopi = $this->federationService->getRemoteFileDetails($remoteServer, $remoteServerToken);
|
||||
if ($remoteWopi === null) {
|
||||
throw new \Exception('Invalid remote file details for ' . $remoteServerToken);
|
||||
throw new Exception('Invalid remote file details for ' . $remoteServerToken);
|
||||
}
|
||||
$this->tokenManager->upgradeToRemoteToken($wopi, $remoteWopi, $shareToken, $remoteServer, $remoteServerToken);
|
||||
|
||||
|
|
@ -383,7 +352,7 @@ class DocumentController extends Controller {
|
|||
'permissions' => $permissions,
|
||||
'title' => $node->getName(),
|
||||
'fileId' => $node->getId() . '_' . $this->config->getSystemValue('instanceid'),
|
||||
'token' => $token,
|
||||
'token' => $wopi->getToken(),
|
||||
'token_ttl' => $wopi->getExpiry(),
|
||||
'urlsrc' => $urlSrc,
|
||||
'path' => '/',
|
||||
|
|
@ -394,7 +363,7 @@ class DocumentController extends Controller {
|
|||
}
|
||||
} catch (ShareNotFound $e) {
|
||||
return new TemplateResponse('core', '404', [], 'guest');
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
return $this->renderErrorPage('Failed to open the requested file.');
|
||||
}
|
||||
|
|
@ -422,10 +391,10 @@ class DocumentController extends Controller {
|
|||
}
|
||||
|
||||
if ($userId === null) {
|
||||
$userId = $this->uid;
|
||||
$userId = $this->userId;
|
||||
}
|
||||
|
||||
if ($userId !== null && $userId !== $this->uid) {
|
||||
if ($userId !== null && $userId !== $this->userId) {
|
||||
return $this->renderErrorPage('You are trying to open a file from another user account than the one you are currently logged in with.');
|
||||
}
|
||||
|
||||
|
|
@ -462,12 +431,12 @@ class DocumentController extends Controller {
|
|||
* @UseSession
|
||||
*/
|
||||
public function editOnlineTarget(int $fileId, ?string $target = null) {
|
||||
if (!$this->uid) {
|
||||
if (!$this->userId) {
|
||||
return $this->renderErrorPage('File not found', Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->uid);
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
$files = $userFolder->getById($fileId);
|
||||
$file = array_shift($files);
|
||||
if (!$file) {
|
||||
|
|
@ -482,11 +451,32 @@ class DocumentController extends Controller {
|
|||
}
|
||||
$redirectUrl = $this->urlGenerator->getAbsoluteURL('/index.php/f/' . $file->getId());
|
||||
return new RedirectResponse($redirectUrl);
|
||||
} catch (NotFoundException $e) {
|
||||
} catch (NotPermittedException $e) {
|
||||
} catch (NoUserException $e) {
|
||||
} catch (NotFoundException|NotPermittedException|NoUserException) {
|
||||
}
|
||||
|
||||
return $this->renderErrorPage('File not found', Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function token(int $fileId, ?string $shareToken = null): DataResponse {
|
||||
try {
|
||||
// Get file and share
|
||||
$templateFile = $this->templateManager->getTemplateSource($fileId);
|
||||
if ($templateFile) {
|
||||
[$urlSrc, $wopi] = $this->tokenManager->getTokenForTemplate($templateFile, $share->getShareOwner(), $item->getId());
|
||||
} else {
|
||||
[$urlSrc, $wopi] = $this->tokenManager->getToken($fileId, $shareToken, $this->userId);
|
||||
}
|
||||
|
||||
return new DataResponse(array_merge(
|
||||
[ 'urlSrc' => $urlSrc ],
|
||||
$wopi->jsonSerialize(),
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Failed to generate token for file', [ 'exception' => $e ]);
|
||||
return new DataResponse('Failed to generate token', Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace OCA\Richdocuments\Controller;
|
||||
|
||||
use OCA\Richdocuments\AppConfig;
|
||||
use OCA\Richdocuments\Db\Wopi;
|
||||
use OCP\AppFramework\Http\FeaturePolicy;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
|
|
@ -9,7 +10,7 @@ use OCP\Collaboration\Reference\RenderReferenceEvent;
|
|||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
|
||||
trait DocumentTrait {
|
||||
private $appConfig;
|
||||
private AppConfig $appConfig;
|
||||
|
||||
private function documentTemplateResponse(Wopi $wopi, array $params): TemplateResponse {
|
||||
$eventDispatcher = \OC::$server->get(IEventDispatcher::class);
|
||||
|
|
|
|||
|
|
@ -510,9 +510,9 @@ class WopiController extends Controller {
|
|||
|
||||
if ($isPutRelative) {
|
||||
// generate a token for the new file (the user still has to be logged in)
|
||||
list(, $wopiToken) = $this->tokenManager->getToken((string)$file->getId(), null, $wopi->getEditorUid(), $wopi->getDirect());
|
||||
list(, $wopi) = $this->tokenManager->getToken((string)$file->getId(), null, $wopi->getEditorUid(), $wopi->getDirect());
|
||||
|
||||
$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
|
||||
$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopi->getToken();
|
||||
$url = $this->urlGenerator->getAbsoluteURL($wopi);
|
||||
|
||||
return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
|
||||
|
|
@ -684,9 +684,9 @@ class WopiController extends Controller {
|
|||
|
||||
// generate a token for the new file (the user still has to be
|
||||
// logged in)
|
||||
list(, $wopiToken) = $this->tokenManager->getToken((string)$file->getId(), null, $wopi->getEditorUid(), $wopi->getDirect());
|
||||
list(, $wopi) = $this->tokenManager->getToken((string)$file->getId(), null, $wopi->getEditorUid(), $wopi->getDirect());
|
||||
|
||||
$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopiToken;
|
||||
$wopi = 'index.php/apps/richdocuments/wopi/files/' . $file->getId() . '_' . $this->config->getSystemValue('instanceid') . '?access_token=' . $wopi->getToken();
|
||||
$url = $this->urlGenerator->getAbsoluteURL($wopi);
|
||||
|
||||
return new JSONResponse([ 'Name' => $file->getName(), 'Url' => $url ], Http::STATUS_OK);
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ class InitialStateService {
|
|||
$this->initialState->provideInitialState('hasDrawSupport', $this->capabilitiesService->hasDrawSupport());
|
||||
$this->initialState->provideInitialState('hasNextcloudBranding', $this->capabilitiesService->hasNextcloudBranding());
|
||||
|
||||
$this->provideOptions();
|
||||
|
||||
$this->hasProvidedCapabilities = true;
|
||||
}
|
||||
|
||||
|
|
@ -76,17 +78,8 @@ class InitialStateService {
|
|||
$this->initialState->provideInitialState('document', $this->prepareParams($params));
|
||||
|
||||
$this->initialState->provideInitialState('wopi', $wopi);
|
||||
$this->initialState->provideInitialState('theme', $this->config->getAppValue(Application::APPNAME, 'theme', 'nextcloud'));
|
||||
$this->initialState->provideInitialState('uiDefaults', [
|
||||
'UIMode' => $this->config->getAppValue(Application::APPNAME, 'uiDefaults-UIMode', 'notebookbar')
|
||||
]);
|
||||
$logoSet = $this->config->getAppValue('theming', 'logoheaderMime', '') !== '';
|
||||
if (!$logoSet) {
|
||||
$logoSet = $this->config->getAppValue('theming', 'logoMime', '') !== '';
|
||||
}
|
||||
$this->initialState->provideInitialState('theming-customLogo', ($logoSet ?
|
||||
\OC::$server->getURLGenerator()->getAbsoluteURL(\OC::$server->getThemingDefaults()->getLogo())
|
||||
: false));
|
||||
|
||||
$this->provideOptions();
|
||||
}
|
||||
|
||||
public function prepareParams(array $params): array {
|
||||
|
|
@ -108,4 +101,18 @@ class InitialStateService {
|
|||
|
||||
return array_merge($defaults, $params);
|
||||
}
|
||||
|
||||
private function provideOptions(): void {
|
||||
$this->initialState->provideInitialState('theme', $this->config->getAppValue(Application::APPNAME, 'theme', 'nextcloud'));
|
||||
$this->initialState->provideInitialState('uiDefaults', [
|
||||
'UIMode' => $this->config->getAppValue(Application::APPNAME, 'uiDefaults-UIMode', 'notebookbar')
|
||||
]);
|
||||
$logoSet = $this->config->getAppValue('theming', 'logoheaderMime', '') !== '';
|
||||
if (!$logoSet) {
|
||||
$logoSet = $this->config->getAppValue('theming', 'logoMime', '') !== '';
|
||||
}
|
||||
$this->initialState->provideInitialState('theming-customLogo', ($logoSet ?
|
||||
\OC::$server->getURLGenerator()->getAbsoluteURL(\OC::$server->getThemingDefaults()->getLogo())
|
||||
: false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ class TokenManager {
|
|||
$updatable = (bool)($share->getPermissions() & \OCP\Constants::PERMISSION_UPDATE);
|
||||
$hideDownload = $share->getHideDownload();
|
||||
$owneruid = $share->getShareOwner();
|
||||
$rootFolder = $this->rootFolder->getUserFolder($owneruid);
|
||||
|
||||
} elseif ($this->userId !== null) {
|
||||
try {
|
||||
$editoruid = $this->userId;
|
||||
|
|
@ -205,7 +207,6 @@ class TokenManager {
|
|||
|
||||
return [
|
||||
$this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'], // url src might not be found ehre
|
||||
$wopi->getToken(),
|
||||
$wopi
|
||||
];
|
||||
}
|
||||
|
|
@ -279,7 +280,7 @@ class TokenManager {
|
|||
|
||||
public function newInitiatorToken($sourceServer, Node $node = null, $shareToken = null, bool $direct = false, $userId = null): Wopi {
|
||||
if ($node !== null) {
|
||||
list($urlSrc, $token, $wopi) = $this->getToken($node->getId(), $shareToken, $userId, $direct);
|
||||
list($urlSrc, $wopi) = $this->getToken($node->getId(), $shareToken, $userId, $direct);
|
||||
$wopi->setServerHost($sourceServer);
|
||||
$wopi->setTokenType(Wopi::TOKEN_TYPE_INITIATOR);
|
||||
$this->wopiMapper->update($wopi);
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ const odfViewer = {
|
|||
$iframe.addClass('full')
|
||||
$('#content').addClass('full-height')
|
||||
$('footer').addClass('hidden')
|
||||
$('#imgframe').addClass('hidden')
|
||||
$('#controls').addClass('hidden')
|
||||
$('#content').addClass('loading')
|
||||
} else {
|
||||
|
|
@ -162,7 +161,6 @@ const odfViewer = {
|
|||
if (isPublic) {
|
||||
$('#content').removeClass('full-height')
|
||||
$('footer').removeClass('hidden')
|
||||
$('#imgframe').removeClass('hidden')
|
||||
$('.directLink').removeClass('hidden')
|
||||
$('.directDownload').removeClass('hidden')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const getUIDefaults = () => {
|
|||
}
|
||||
|
||||
const getCollaboraTheme = () => {
|
||||
return loadState('richdocuments', 'theme', '')
|
||||
return loadState('richdocuments', 'theme', 'nextcloud')
|
||||
}
|
||||
|
||||
const generateCSSVarTokens = () => {
|
||||
|
|
|
|||
|
|
@ -76,8 +76,17 @@ const splitPath = (path) => {
|
|||
return [directory, fileName]
|
||||
}
|
||||
|
||||
const getRandomId = (length = 5) => {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.slice(0, length || 5)
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
languageToBCP47,
|
||||
getNextcloudVersion,
|
||||
splitPath,
|
||||
getRandomId,
|
||||
}
|
||||
|
|
|
|||
91
src/mixins/openLocal.js
Normal file
91
src/mixins/openLocal.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getRootUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getNextcloudUrl } from '../helpers/url.js'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
// FIXME: Migrate to vue component
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
openingLocally: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
unlockAndOpenLocally() {
|
||||
if (this.openingLocally) {
|
||||
this.unlockFile()
|
||||
.catch(_ => {}) // Unlocking failed, possibly because file was not locked, we want to proceed regardless.
|
||||
.then(() => {
|
||||
this.openLocally()
|
||||
})
|
||||
}
|
||||
},
|
||||
showOpenLocalConfirmation() {
|
||||
// FIXME: Migrate to vue
|
||||
window.OC.dialogs.confirmDestructive(
|
||||
t('richdocuments', 'When opening a file locally, the document will close for all users currently viewing the document.'),
|
||||
t('richdocuments', 'Open file locally'),
|
||||
{
|
||||
type: OC.dialogs.YES_NO_BUTTONS,
|
||||
confirm: t('richdocuments', 'Open locally'),
|
||||
confirmClasses: 'error',
|
||||
cancel: t('richdocuments', 'Continue editing online'),
|
||||
},
|
||||
(decision) => {
|
||||
if (!decision) {
|
||||
return
|
||||
}
|
||||
this.openingLocally = true
|
||||
this.sendPostMessage('Get_Views')
|
||||
})
|
||||
},
|
||||
|
||||
unlockFile() {
|
||||
const unlockUrl = getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + this.fileid
|
||||
const unlockConfig = {
|
||||
headers: { 'X-WOPI-Override': 'UNLOCK' },
|
||||
}
|
||||
return axios.post(unlockUrl, { access_token: this.formData.accessToken }, unlockConfig)
|
||||
},
|
||||
|
||||
openLocally() {
|
||||
if (this.openingLocally) {
|
||||
this.openingLocally = false
|
||||
|
||||
axios.post(
|
||||
OC.linkToOCS('apps/files/api/v1', 2) + 'openlocaleditor?format=json',
|
||||
{ path: this.filename }
|
||||
).then((result) => {
|
||||
const url = 'nc://open/'
|
||||
+ getCurrentUser()?.uid + '@' + getNextcloudUrl()
|
||||
+ OC.encodePath(this.filename)
|
||||
+ '?token=' + result.data.ocs.data.token
|
||||
|
||||
this.showOpenLocalConfirmation(url, window.top)
|
||||
window.location.href = url
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
84
src/mixins/pickLink.js
Normal file
84
src/mixins/pickLink.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js'
|
||||
|
||||
// FIXME: Migrate to vue component
|
||||
export default {
|
||||
methods: {
|
||||
async pickLink() {
|
||||
try {
|
||||
if (this.showLinkPicker) {
|
||||
return
|
||||
}
|
||||
this.showLinkPicker = true
|
||||
const link = await getLinkWithPicker(null, true)
|
||||
try {
|
||||
const url = new URL(link)
|
||||
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
||||
this.sendPostMessage('Action_InsertLink', { url: link })
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('error when parsing the link picker result')
|
||||
}
|
||||
this.sendPostMessage('Action_Paste', { Mimetype: 'text/plain', Data: link })
|
||||
} catch (e) {
|
||||
console.error('Link picker promise rejected :', e)
|
||||
} finally {
|
||||
this.showLinkPicker = false
|
||||
}
|
||||
},
|
||||
async resolveLink(url) {
|
||||
try {
|
||||
const result = await axios.get(generateOcsUrl('references/resolve', 2), {
|
||||
params: {
|
||||
reference: url,
|
||||
},
|
||||
})
|
||||
const resolvedLink = result.data.ocs.data.references[url]
|
||||
const title = resolvedLink?.openGraphObject?.name
|
||||
const thumbnailUrl = resolvedLink?.openGraphObject?.thumb
|
||||
if (thumbnailUrl) {
|
||||
try {
|
||||
const imageResponse = await axios.get(thumbnailUrl, { responseType: 'blob' })
|
||||
if (imageResponse?.status === 200 && imageResponse?.data) {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener('loadend', (e) => {
|
||||
const b64Image = e.target.result
|
||||
this.sendPostMessage('Action_GetLinkPreview_Resp', { url, title, image: b64Image })
|
||||
})
|
||||
reader.readAsDataURL(imageResponse.data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading the reference image', e)
|
||||
}
|
||||
} else {
|
||||
this.sendPostMessage('Action_GetLinkPreview_Resp', { url, title, image: null })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error resolving a reference', e)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
51
src/mixins/saveAs.js
Normal file
51
src/mixins/saveAs.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// FIXME: Migrate to vue component
|
||||
export default {
|
||||
methods: {
|
||||
async saveAs(format) {
|
||||
window.OC.dialogs.prompt(
|
||||
t('richdocuments', 'Please enter the filename to store the document as.'),
|
||||
t('richdocuments', 'Save As'),
|
||||
(result, value) => {
|
||||
if (result === true && value) {
|
||||
this.sendPostMessage('Action_SaveAs', { Filename: value, Notify: true })
|
||||
}
|
||||
},
|
||||
true,
|
||||
t('richdocuments', 'New filename'),
|
||||
false
|
||||
).then(() => {
|
||||
const $dialog = $('.oc-dialog:visible')
|
||||
const $buttons = $dialog.find('.oc-dialog-buttonrow button')
|
||||
$buttons.eq(0).text(t('richdocuments', 'Cancel'))
|
||||
$buttons.eq(1).text(t('richdocuments', 'Save'))
|
||||
const nameInput = $dialog.find('input')[0]
|
||||
nameInput.style.minWidth = '250px'
|
||||
nameInput.style.maxWidth = '400px'
|
||||
nameInput.value = format ? this.basename.substring(0, this.basename.lastIndexOf('.') + 1) + format : this.basename
|
||||
nameInput.selectionStart = 0
|
||||
nameInput.selectionEnd = this.basename.lastIndexOf('.')
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
49
src/mixins/uiMention.js
Normal file
49
src/mixins/uiMention.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
import Config from '../services/config.tsx'
|
||||
import { getNextcloudUrl } from '../helpers/url.js'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async uiMention(search) {
|
||||
let users = []
|
||||
|
||||
if (Config.get('userId') !== null) {
|
||||
try {
|
||||
const result = await axios.get(generateOcsUrl('core/autocomplete/get'), {
|
||||
params: { search },
|
||||
})
|
||||
users = result.data.ocs.data
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
const list = users.map((user) => {
|
||||
const profile = window.location.protocol + '//' + getNextcloudUrl() + '/index.php/u/' + user.id
|
||||
return { username: user.label, profile }
|
||||
})
|
||||
|
||||
this.sendPostMessage('Action_Mention', { list })
|
||||
},
|
||||
},
|
||||
}
|
||||
74
src/mixins/version.js
Normal file
74
src/mixins/version.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
import { generateRemoteUrl, getRootUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
versionToRestore: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
subscribe('files_versions:restore:requested', this.onRestoreRequested)
|
||||
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
unsubscribe('files_versions:restore:requested', this.onRestoreRequested)
|
||||
},
|
||||
|
||||
methods: {
|
||||
onRestoreRequested(eventState) {
|
||||
// Tell Collabora that we are about to restore a version
|
||||
this.sendPostMessage('Host_VersionRestore', {
|
||||
Status: 'Pre_Restore',
|
||||
})
|
||||
|
||||
this.versionToRestore = eventState.version
|
||||
|
||||
// Prevent files_versions own restore as we'd need to wait for Collabora to be ready
|
||||
eventState.preventDefault = true
|
||||
},
|
||||
async handlePreRestoreAck() {
|
||||
const restoreUrl = getRootUrl() + '/remote.php/dav/versions/' + getCurrentUser().uid
|
||||
+ '/versions/' + this.fileid + '/' + this.versionToRestore.fileVersion
|
||||
try {
|
||||
await axios({
|
||||
method: 'MOVE',
|
||||
url: restoreUrl,
|
||||
headers: {
|
||||
Destination: generateRemoteUrl('dav') + '/versions/' + getCurrentUser().uid + '/restore/target',
|
||||
},
|
||||
})
|
||||
emit('files_versions:restore:restored', this.versionToRestore)
|
||||
} catch (e) {
|
||||
showError(t('richdocuments', 'Failed to revert the document to older version'))
|
||||
}
|
||||
this.versionToRestore = null
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
|
|
@ -20,9 +20,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { generateUrl, generateRemoteUrl, getRootUrl } from '@nextcloud/router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { getCurrentDirectory } from '../helpers/filesApp.js'
|
||||
|
||||
const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
|
||||
|
|
@ -80,10 +79,6 @@ export default {
|
|||
this.getFileList().hideMask && this.getFileList().hideMask()
|
||||
this.getFileList().setPageTitle && this.getFileList().setPageTitle(this.fileName)
|
||||
}
|
||||
|
||||
if (!isPublic) {
|
||||
this.addVersionSidebarEvents()
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
|
|
@ -94,9 +89,6 @@ export default {
|
|||
this.updateFileInfo(undefined, Date.now())
|
||||
|
||||
this.fileModel = null
|
||||
if (!isPublic) {
|
||||
this.removeVersionSidebarEvents()
|
||||
}
|
||||
$('#richdocuments-header').remove()
|
||||
},
|
||||
|
||||
|
|
@ -131,9 +123,9 @@ export default {
|
|||
console.error('[FilesAppIntegration] Sharing is not supported')
|
||||
return
|
||||
}
|
||||
if (OCA.Files.Sidebar) {
|
||||
OCA.Files.Sidebar.open(this.filePath + '/' + this.fileName)
|
||||
}
|
||||
|
||||
OCA?.Files?.Sidebar?.open(this.filePath + '/' + this.fileName)
|
||||
OCA?.Files?.Sidebar?.setActiveTab('sharing')
|
||||
},
|
||||
|
||||
rename(newName) {
|
||||
|
|
@ -427,115 +419,17 @@ export default {
|
|||
avatardiv.append(popover)
|
||||
},
|
||||
|
||||
addVersionSidebarEvents() {
|
||||
$(document.querySelector('#content')).on('click.revisions', '.app-sidebar .preview-container', this.showVersionPreview.bind(this))
|
||||
$(document.querySelector('#content')).on('click.revisions', '.app-sidebar .downloadVersion', this.showVersionPreview.bind(this))
|
||||
$(document.querySelector('#content')).on('mousedown.revisions', '.app-sidebar .revertVersion', this.restoreVersion.bind(this))
|
||||
$(document.querySelector('#content')).on('click.revisionsTab', '.app-sidebar [data-tabid=versionsTabView]', this.addCurrentVersion.bind(this))
|
||||
},
|
||||
|
||||
removeVersionSidebarEvents() {
|
||||
$(document.querySelector('#content')).off('click.revisions')
|
||||
$(document.querySelector('#content')).off('click.revisions')
|
||||
$(document.querySelector('#content')).off('mousedown.revisions')
|
||||
$(document.querySelector('#content')).off('click.revisionsTab')
|
||||
},
|
||||
|
||||
addCurrentVersion() {
|
||||
$('#lastSavedVersion').remove()
|
||||
$('#currentVersion').remove()
|
||||
if (this.getFileModel()) {
|
||||
const preview = OC.MimeType.getIconUrl(this.getFileModel().get('mimetype'))
|
||||
const mtime = this.getFileModel().get('mtime')
|
||||
$('.tab.versionsTabView').prepend('<ul id="lastSavedVersion"><li data-revision="0"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
|
||||
+ '<div><a class="downloadVersion">' + t('richdocuments', 'Last saved version') + ' (<span class="versiondate has-tooltip live-relative-timestamp" data-timestamp="' + mtime + '"></span>)</div></div></li></ul>')
|
||||
$('.tab.versionsTabView').prepend('<ul id="currentVersion"><li data-revision="" class="active"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
|
||||
+ '<div><a class="downloadVersion">' + t('richdocuments', 'Current version (unsaved changes)') + '</a></div></div></li></ul>')
|
||||
$('.live-relative-timestamp').each(function() {
|
||||
$(this).text(moment(parseInt($(this).attr('data-timestamp'), 10)).fromNow())
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
showRevHistory() {
|
||||
if (this.handlers.showRevHistory && this.handlers.showRevHistory(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.getFileList()) {
|
||||
this.getFileList()
|
||||
.showDetailsView(this.fileName, 'versionsTabView')
|
||||
this.addCurrentVersion()
|
||||
if (isPublic || !this.getFileList()) {
|
||||
console.error('[FilesAppIntegration] Versions are not supported')
|
||||
return
|
||||
}
|
||||
},
|
||||
|
||||
showVersionPreview(e) {
|
||||
e.preventDefault()
|
||||
let element = e.currentTarget.parentElement.parentElement
|
||||
if ($(e.currentTarget).hasClass('downloadVersion')) {
|
||||
element = e.currentTarget.parentElement.parentElement.parentElement.parentElement
|
||||
}
|
||||
const version = element.dataset.revision
|
||||
const fileId = this.fileId
|
||||
const title = this.fileName
|
||||
console.debug('[FilesAppIntegration] showVersionPreview', version, fileId, title)
|
||||
this.sendPostMessage('Action_loadRevViewer', { fileId, title, version })
|
||||
$(element.parentElement.parentElement).find('li').removeClass('active')
|
||||
$(element).addClass('active')
|
||||
},
|
||||
|
||||
restoreVersion(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
this.sendPostMessage('Host_VersionRestore', { Status: 'Pre_Restore' })
|
||||
|
||||
const version = e.currentTarget.parentElement.parentElement.dataset.revision
|
||||
|
||||
this._restoreVersionCallback = () => {
|
||||
this._restoreDAV(version)
|
||||
this._restoreVersionCallback = null
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
restoreVersionExecute() {
|
||||
if (this._restoreVersionCallback !== null) {
|
||||
this._restoreVersionCallback()
|
||||
}
|
||||
},
|
||||
|
||||
restoreVersionAbort() {
|
||||
this._restoreVersionCallback = null
|
||||
},
|
||||
|
||||
_restoreSuccess(response) {
|
||||
if (response.status === 'error') {
|
||||
OC.Notification.showTemporary(t('richdocuments', 'Failed to revert the document to older version'))
|
||||
}
|
||||
// Reload the document frame to get the new file
|
||||
// TODO: ideally we should have a post messsage that can be sent to collabora to just reload the file once the restore is finished
|
||||
document.getElementById('richdocumentsframe').src = document.getElementById('richdocumentsframe').src
|
||||
OC.Apps.hideAppSidebar()
|
||||
},
|
||||
|
||||
_restoreError() {
|
||||
OC.Notification.showTemporary(t('richdocuments', 'Failed to revert the document to older version'))
|
||||
},
|
||||
|
||||
_restoreDAV(version) {
|
||||
const restoreUrl = getRootUrl() + '/remote.php/dav/versions/' + getCurrentUser().uid
|
||||
+ '/versions/' + this.fileId + '/' + version
|
||||
$.ajax({
|
||||
type: 'MOVE',
|
||||
url: restoreUrl,
|
||||
headers: {
|
||||
Destination: generateRemoteUrl('dav') + '/versions/' + getCurrentUser().uid + '/restore/target',
|
||||
},
|
||||
success: this._restoreSuccess.bind(this),
|
||||
error: this._restoreError.bind(this),
|
||||
})
|
||||
OCA?.Files?.Sidebar?.open(this.filePath + '/' + this.fileName)
|
||||
OCA?.Files?.Sidebar?.setActiveTab('version_vue')
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,78 +21,91 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<transition name="fade" appear>
|
||||
<div class="office-viewer">
|
||||
<div v-if="showLoadingIndicator"
|
||||
class="office-viewer__loading-overlay"
|
||||
:class="{ debug: debug }">
|
||||
<NcEmptyContent v-if="!error" :title="t('richdocuments', 'Loading {filename} …', { filename: basename }, 1, {escape: false})">
|
||||
<template #icon>
|
||||
<NcLoadingIcon />
|
||||
</template>
|
||||
<template #action>
|
||||
<NcButton @click="close">
|
||||
{{ t('richdocuments', 'Cancel') }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<NcEmptyContent v-else :title="t('richdocuments', 'Document loading failed')" :description="errorMessage">
|
||||
<template #icon>
|
||||
<AlertOctagonOutline />
|
||||
</template>
|
||||
<template #action>
|
||||
<NcButton @click="close">
|
||||
{{ t('richdocuments', 'Close') }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</div>
|
||||
<div v-show="!useNativeHeader && showIframe" class="office-viewer__header">
|
||||
<div class="avatars">
|
||||
<NcAvatar v-for="view in avatarViews"
|
||||
:key="view.ViewId"
|
||||
:user="view.UserId"
|
||||
:display-name="view.UserName"
|
||||
:show-user-status="false"
|
||||
:show-user-status-compact="false"
|
||||
:style="viewColor(view)" />
|
||||
</div>
|
||||
<NcActions>
|
||||
<NcActionButton icon="office-viewer__header__icon-menu-sidebar" @click="share" />
|
||||
</NcActions>
|
||||
</div>
|
||||
<iframe id="collaboraframe"
|
||||
ref="documentFrame"
|
||||
data-cy="documentframe"
|
||||
class="office-viewer__iframe"
|
||||
:style="{visibility: showIframe ? 'visible' : 'hidden' }"
|
||||
:src="src" />
|
||||
|
||||
<ZoteroHint :show.sync="showZotero" @submit="reload" />
|
||||
<div class="office-viewer">
|
||||
<div v-if="showLoadingIndicator"
|
||||
class="office-viewer__loading-overlay"
|
||||
:class="{ debug: debug }">
|
||||
<NcEmptyContent v-if="!error" :title="t('richdocuments', 'Loading {filename} …', { filename: basename }, 1, {escape: false})">
|
||||
<template #icon>
|
||||
<NcLoadingIcon />
|
||||
</template>
|
||||
<template #action>
|
||||
<NcButton @click="close">
|
||||
{{ t('richdocuments', 'Cancel') }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<NcEmptyContent v-else :title="t('richdocuments', 'Document loading failed')" :description="errorMessage">
|
||||
<template #icon>
|
||||
<AlertOctagonOutline />
|
||||
</template>
|
||||
<template #action>
|
||||
<NcButton @click="close">
|
||||
{{ t('richdocuments', 'Close') }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</div>
|
||||
</transition>
|
||||
<form ref="form"
|
||||
:target="iframeId"
|
||||
:action="formData.action"
|
||||
method="post">
|
||||
<input name="access_token" :value="formData.accessToken" type="hidden">
|
||||
<input name="access_token_ttl" :value="formData.accessTokenTTL" type="hidden">
|
||||
<input name="ui_defaults" :value="formData.uiDefaults" type="hidden">
|
||||
<input name="css_variables" :value="formData.cssVariables" type="hidden">
|
||||
<input name="theme" :value="formData.theme" type="hidden">
|
||||
<input name="buy_product" value="https://nextcloud.com/pricing" type="hidden">
|
||||
</form>
|
||||
<iframe :id="iframeId"
|
||||
ref="documentFrame"
|
||||
:name="iframeId"
|
||||
data-cy="documentframe"
|
||||
scrolling="no"
|
||||
allowfullscreen
|
||||
class="office-viewer__iframe"
|
||||
:style="{visibility: showIframe ? 'visible' : 'hidden' }"
|
||||
:src="iframeSrc" />
|
||||
|
||||
<ZoteroHint :show.sync="showZotero" @submit="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NcAvatar, NcButton, NcActions, NcActionButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
||||
import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
||||
import AlertOctagonOutline from 'vue-material-design-icons/AlertOctagonOutline.vue'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
import ZoteroHint from '../components/Modal/ZoteroHint.vue'
|
||||
import { basename, dirname } from 'path'
|
||||
import { getDocumentUrlForFile, getDocumentUrlForPublicFile } from '../helpers/url.js'
|
||||
import { getRandomId } from '../helpers/index.js'
|
||||
import {
|
||||
getNextcloudUrl,
|
||||
getWopiUrl,
|
||||
} from '../helpers/url.js'
|
||||
import PostMessageService from '../services/postMessage.tsx'
|
||||
import FilesAppIntegration from './FilesAppIntegration.js'
|
||||
import { LOADING_ERROR, checkCollaboraConfiguration, checkProxyStatus } from '../services/collabora.js'
|
||||
import { enableScrollLock, disableScrollLock } from '../helpers/safariFixer.js'
|
||||
import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import {
|
||||
generateUrl,
|
||||
imagePath,
|
||||
} from '@nextcloud/router'
|
||||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import {
|
||||
generateCSSVarTokens,
|
||||
getCollaboraTheme,
|
||||
getUIDefaults,
|
||||
} from '../helpers/coolParameters.js'
|
||||
import Config from '../services/config.tsx'
|
||||
import openLocal from '../mixins/openLocal.js'
|
||||
import pickLink from '../mixins/pickLink.js'
|
||||
import saveAs from '../mixins/saveAs.js'
|
||||
import uiMention from '../mixins/uiMention.js'
|
||||
import version from '../mixins/version.js'
|
||||
|
||||
const FRAME_DOCUMENT = 'FRAME_DOCUMENT'
|
||||
const PostMessages = new PostMessageService({
|
||||
FRAME_DOCUMENT: () => document.getElementById('collaboraframe').contentWindow,
|
||||
})
|
||||
|
||||
const LOADING_STATE = {
|
||||
LOADING: 0,
|
||||
|
|
@ -105,14 +118,14 @@ export default {
|
|||
name: 'Office',
|
||||
components: {
|
||||
AlertOctagonOutline,
|
||||
NcAvatar,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
NcLoadingIcon,
|
||||
ZoteroHint,
|
||||
},
|
||||
mixins: [
|
||||
openLocal, pickLink, saveAs, uiMention, version,
|
||||
],
|
||||
props: {
|
||||
filename: {
|
||||
type: String,
|
||||
|
|
@ -127,36 +140,35 @@ export default {
|
|||
required: false,
|
||||
default: () => false,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
src: null,
|
||||
postMessage: null,
|
||||
iframeId: 'collaboraframe_' + getRandomId(),
|
||||
iframeSrc: null,
|
||||
loading: LOADING_STATE.LOADING,
|
||||
loadingTimeout: null,
|
||||
error: null,
|
||||
views: [],
|
||||
|
||||
showZotero: false,
|
||||
showLinkPicker: false,
|
||||
showZotero: false,
|
||||
|
||||
formData: {
|
||||
action: null,
|
||||
accessToken: null,
|
||||
accessTokenTTL: null,
|
||||
uiDefaults: getUIDefaults(),
|
||||
cssVariables: generateCSSVarTokens(),
|
||||
theme: getCollaboraTheme(),
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
basename() {
|
||||
return basename(this.filename)
|
||||
},
|
||||
useNativeHeader() {
|
||||
return true
|
||||
},
|
||||
avatarViews() {
|
||||
return this.views
|
||||
},
|
||||
viewColor() {
|
||||
return view => ({
|
||||
'border-color': '#' + ('000000' + Number(view.Color).toString(16)).slice(-6),
|
||||
'border-width': '2px',
|
||||
'border-style': 'solid',
|
||||
})
|
||||
},
|
||||
showIframe() {
|
||||
return this.loading >= LOADING_STATE.FRAME_READY
|
||||
},
|
||||
|
|
@ -176,8 +188,17 @@ export default {
|
|||
debug() {
|
||||
return !!window.TESTING
|
||||
},
|
||||
isPublic() {
|
||||
return document.getElementById('isPublic')?.value === '1'
|
||||
},
|
||||
shareToken() {
|
||||
return document.getElementById('sharingToken')?.value
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.postMessage = new PostMessageService({
|
||||
FRAME_DOCUMENT: () => document.getElementById(this.iframeId).contentWindow,
|
||||
})
|
||||
try {
|
||||
await checkCollaboraConfiguration()
|
||||
await checkProxyStatus()
|
||||
|
|
@ -187,99 +208,77 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
const fileList = OCA?.Files?.App?.getCurrentFileList?.()
|
||||
FilesAppIntegration.init({
|
||||
fileName: basename(this.filename),
|
||||
fileId: this.fileid,
|
||||
filePath: dirname(this.filename),
|
||||
fileList,
|
||||
fileModel: fileList?.getModelForFile(basename(this.filename)),
|
||||
sendPostMessage: (msgId, values) => {
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
|
||||
},
|
||||
})
|
||||
PostMessages.registerPostMessageHandler(this.postMessageHandler)
|
||||
if (this.fileid) {
|
||||
const fileList = OCA?.Files?.App?.getCurrentFileList?.()
|
||||
FilesAppIntegration.init({
|
||||
fileName: basename(this.filename),
|
||||
fileId: this.fileid,
|
||||
filePath: dirname(this.filename),
|
||||
fileList,
|
||||
fileModel: fileList?.getModelForFile(basename(this.filename)),
|
||||
sendPostMessage: (msgId, values) => {
|
||||
this.postMessage.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
|
||||
},
|
||||
})
|
||||
}
|
||||
this.postMessage.registerPostMessageHandler(this.postMessageHandler)
|
||||
|
||||
this.load()
|
||||
},
|
||||
beforeDestroy() {
|
||||
PostMessages.unregisterPostMessageHandler(this.postMessageHandler)
|
||||
this.postMessage.unregisterPostMessageHandler(this.postMessageHandler)
|
||||
},
|
||||
methods: {
|
||||
async load() {
|
||||
const fileid = this.fileid ?? basename(dirname(this.source))
|
||||
const version = this.fileid ? 0 : basename(this.source)
|
||||
|
||||
enableScrollLock()
|
||||
const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
|
||||
this.src = getDocumentUrlForFile(this.filename, this.fileid) + '&path=' + encodeURIComponent(this.filename)
|
||||
if (isPublic) {
|
||||
this.src = getDocumentUrlForPublicFile(this.filename, this.fileid)
|
||||
}
|
||||
|
||||
// Generate WOPI token
|
||||
const { data } = await axios.post(generateUrl('/apps/richdocuments/token'), {
|
||||
fileId: fileid, shareToken: this.shareToken,
|
||||
})
|
||||
Config.update('urlsrc', data.urlSrc)
|
||||
|
||||
// Generate form and submit to the iframe
|
||||
const action = getWopiUrl({
|
||||
fileId: fileid + '_' + loadState('richdocuments', 'instanceId', 'instanceid') + (version > 0 ? '_' + version : ''),
|
||||
title: this.filename,
|
||||
readOnly: version > 0,
|
||||
revisionHistory: !this.isPublic,
|
||||
closeButton: !Config.get('hideCloseButton'),
|
||||
})
|
||||
this.$set(this.formData, 'action', action)
|
||||
this.$set(this.formData, 'accessToken', data.token)
|
||||
this.$nextTick(() => this.$refs.form.submit())
|
||||
|
||||
this.loading = LOADING_STATE.LOADING
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
console.error('FAILED')
|
||||
console.error('Document loading failed due to timeout: Please check for failing network requests')
|
||||
this.loading = LOADING_STATE.FAILED
|
||||
this.error = t('richdocuments', 'Failed to load {productName} - please try again later', { productName: loadState('richdocuments', 'productName', 'Nextcloud Office') })
|
||||
}, (OC.getCapabilities().richdocuments.config.timeout * 1000 || 15000))
|
||||
}, (getCapabilities().richdocuments.config.timeout * 1000 || 15000))
|
||||
},
|
||||
sendPostMessage(msgId, values = {}) {
|
||||
this.postMessage.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
|
||||
},
|
||||
documentReady() {
|
||||
this.loading = LOADING_STATE.DOCUMENT_READY
|
||||
clearTimeout(this.loadingTimeout)
|
||||
this.sendPostMessage('Host_PostmessageReady')
|
||||
this.sendPostMessage('Insert_Button', {
|
||||
id: 'Open_Local_Editor',
|
||||
imgurl: window.location.protocol + '//' + getNextcloudUrl() + imagePath('richdocuments', 'launch.svg'),
|
||||
mobile: false,
|
||||
label: t('richdocuments', 'Open in local editor'),
|
||||
hint: t('richdocuments', 'Open in local editor'),
|
||||
insertBefore: 'print',
|
||||
})
|
||||
},
|
||||
async share() {
|
||||
FilesAppIntegration.share()
|
||||
},
|
||||
async pickLink() {
|
||||
try {
|
||||
if (this.showLinkPicker) {
|
||||
return
|
||||
}
|
||||
this.showLinkPicker = true
|
||||
const link = await getLinkWithPicker(null, true)
|
||||
try {
|
||||
const url = new URL(link)
|
||||
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'Action_InsertLink', { url: link })
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('error when parsing the link picker result')
|
||||
}
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'Action_Paste', { Mimetype: 'text/plain', Data: link })
|
||||
} catch (e) {
|
||||
console.error('Link picker promise rejected :', e)
|
||||
} finally {
|
||||
this.showLinkPicker = false
|
||||
}
|
||||
},
|
||||
async resolveLink(url) {
|
||||
try {
|
||||
const result = await axios.get(generateOcsUrl('references/resolve', 2), {
|
||||
params: {
|
||||
reference: url,
|
||||
},
|
||||
})
|
||||
const resolvedLink = result.data.ocs.data.references[url]
|
||||
const title = resolvedLink?.openGraphObject?.name
|
||||
const thumbnailUrl = resolvedLink?.openGraphObject?.thumb
|
||||
if (thumbnailUrl) {
|
||||
try {
|
||||
const imageResponse = await axios.get(thumbnailUrl, { responseType: 'blob' })
|
||||
if (imageResponse?.status === 200 && imageResponse?.data) {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener('loadend', (e) => {
|
||||
const b64Image = e.target.result
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'Action_GetLinkPreview_Resp', { url, title, image: b64Image })
|
||||
})
|
||||
reader.readAsDataURL(imageResponse.data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading the reference image', e)
|
||||
}
|
||||
} else {
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'Action_GetLinkPreview_Resp', { url, title, image: null })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error resolving a reference', e)
|
||||
}
|
||||
},
|
||||
close() {
|
||||
FilesAppIntegration.close()
|
||||
disableScrollLock()
|
||||
|
|
@ -288,19 +287,14 @@ export default {
|
|||
reload() {
|
||||
this.loading = LOADING_STATE.LOADING
|
||||
this.load()
|
||||
this.$refs.documentFrame.contentWindow.location.replace(this.src)
|
||||
this.$refs.documentFrame.contentWindow.location.replace(this.iframeSrc)
|
||||
},
|
||||
postMessageHandler({ parsed, data }) {
|
||||
if (data === 'NC_ShowNamePicker') {
|
||||
this.documentReady()
|
||||
return
|
||||
} else if (data === 'loading') {
|
||||
this.loading = LOADING_STATE.LOADING
|
||||
postMessageHandler({ parsed }) {
|
||||
const { msgId, args, deprecated } = parsed
|
||||
console.debug('[viewer] Received post message', msgId, args, deprecated)
|
||||
if (deprecated) {
|
||||
return
|
||||
}
|
||||
console.debug('[viewer] Received post message', parsed)
|
||||
const { msgId, args, deprecated } = parsed
|
||||
if (deprecated) { return }
|
||||
|
||||
switch (msgId) {
|
||||
case 'App_LoadingStatus':
|
||||
|
|
@ -309,8 +303,7 @@ export default {
|
|||
this.loading = LOADING_STATE.FRAME_READY
|
||||
this.$emit('update:loaded', true)
|
||||
FilesAppIntegration.initAfterReady()
|
||||
}
|
||||
if (args.Status === 'Document_Loaded') {
|
||||
} else if (args.Status === 'Document_Loaded') {
|
||||
this.documentReady()
|
||||
} else if (args.Status === 'Failed') {
|
||||
this.loading = LOADING_STATE.FAILED
|
||||
|
|
@ -325,14 +318,16 @@ export default {
|
|||
this.loading = LOADING_STATE.FAILED
|
||||
}
|
||||
break
|
||||
case 'loading':
|
||||
break
|
||||
case 'close':
|
||||
case 'UI_Close':
|
||||
this.close()
|
||||
break
|
||||
case 'Get_Views_Resp':
|
||||
case 'Views_List':
|
||||
this.views = args
|
||||
this.unlockAndOpenLocally()
|
||||
break
|
||||
case 'UI_SaveAs':
|
||||
this.saveAs(args.format)
|
||||
break
|
||||
case 'Action_Save_Resp':
|
||||
if (args.fileName !== this.filename) {
|
||||
|
|
@ -341,9 +336,15 @@ export default {
|
|||
break
|
||||
case 'UI_InsertGraphic':
|
||||
FilesAppIntegration.insertGraphic((filename, url) => {
|
||||
PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'postAsset', { FileName: filename, Url: url })
|
||||
this.postMessage.sendWOPIPostMessage(FRAME_DOCUMENT, 'Action_InsertGraphic', {
|
||||
filename,
|
||||
url,
|
||||
})
|
||||
})
|
||||
break
|
||||
case 'UI_Mention':
|
||||
this.uiMention(parsed.args.text)
|
||||
break
|
||||
case 'UI_CreateFile':
|
||||
FilesAppIntegration.createNewFile(args.DocumentType)
|
||||
break
|
||||
|
|
@ -351,12 +352,11 @@ export default {
|
|||
FilesAppIntegration.rename(args.NewName)
|
||||
break
|
||||
case 'UI_FileVersions':
|
||||
case 'rev-history':
|
||||
FilesAppIntegration.showRevHistory()
|
||||
break
|
||||
case 'App_VersionRestore':
|
||||
if (args.Status === 'Pre_Restore_Ack') {
|
||||
FilesAppIntegration.restoreVersionExecute()
|
||||
this.handlePreRestoreAck()
|
||||
}
|
||||
break
|
||||
case 'UI_Share':
|
||||
|
|
@ -371,28 +371,31 @@ export default {
|
|||
case 'Action_GetLinkPreview':
|
||||
this.resolveLink(args.url)
|
||||
break
|
||||
case 'Clicked_Button':
|
||||
this.buttonClicked(args)
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
async buttonClicked(args) {
|
||||
if (args?.Id === 'Open_Local_Editor') {
|
||||
this.showOpenLocalConfirmation()
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.office-viewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: 100000;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-main-background);
|
||||
transition: opacity .25s;
|
||||
|
||||
&__loading-overlay {
|
||||
&__loading-overlay:not(.viewer__file--hidden) {
|
||||
border-top: 3px solid var(--color-primary-element);
|
||||
position: absolute;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
@ -414,48 +417,16 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: absolute;
|
||||
right: 44px;
|
||||
top: 3px;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
|
||||
::v-deep .avatardiv {
|
||||
margin-left: -15px;
|
||||
box-shadow: 0 0 3px var(--color-box-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon-menu-sidebar {
|
||||
background-image: var(--icon-menu-sidebar-000) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__iframe {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
::v-deep .fade-enter-active,
|
||||
::v-deep .fade-leave-active {
|
||||
transition: opacity .25s;
|
||||
}
|
||||
|
||||
::v-deep .fade-enter,
|
||||
::v-deep .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.viewer .office-viewer {
|
||||
.viewer .office-viewer:not(.viewer__file--hidden) {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
top: -50px;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ if (OCA.Viewer) {
|
|||
mimes: supportedMimes,
|
||||
component: Office,
|
||||
theme: 'light',
|
||||
canCompare: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue