allow saving a PDF, add "view_comment" state

This adds a "view_comment" in addition to "view" and "edit" state
into discovery.xml. In case it is enabled, the filters let the
comment commands through to core.

In addition add "Save Comment" menu action to allow saving the
comments, which is enabled when in "read-only" with "view_comment"
mode.

Change-Id: I3ab3dbee93ee2167ae96adea7025fc0b385f8201
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/99473
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
feature/calc-canvas
Tomaž Vajngerl 2020-07-27 11:27:00 +02:00 committed by Tomaž Vajngerl
parent 65e3a8de12
commit ca00470722
10 changed files with 72 additions and 15 deletions

View File

@ -52,6 +52,7 @@ Session::Session(const std::shared_ptr<ProtocolHandlerInterface> &protocol,
_lastActivityTime(std::chrono::steady_clock::now()),
_isCloseFrame(false),
_isReadOnly(readOnly),
_isAllowChangeComments(false),
_docPassword(""),
_haveDocPassword(false),
_isDocPasswordProtected(false),

View File

@ -76,9 +76,15 @@ public:
const std::string& getName() const { return _name; }
bool isDisconnected() const { return _disconnected; }
virtual void setReadOnly(bool bVal = true) { _isReadOnly = bVal; }
virtual void setReadOnly(bool bValue = true) { _isReadOnly = bValue; }
bool isReadOnly() const { return _isReadOnly; }
void setAllowChangeComments(bool bValue = true)
{
_isAllowChangeComments = bValue;
}
bool isAllowChangeComments() const { return _isAllowChangeComments; }
/// overridden to prepend client ids on messages by the Kit
virtual bool sendBinaryFrame(const char* buffer, int length);
virtual bool sendTextFrame(const char* buffer, const int length);
@ -249,6 +255,9 @@ private:
/// Whether the session is opened as readonly
bool _isReadOnly;
/// If the session is read-only, are comments allowed
bool _isAllowChangeComments;
/// The actual URL, also in the child, even if the child never accesses that.
std::string _docURL;

View File

@ -306,7 +306,7 @@
<action name="view" ext="jpeg"/>
</app>
<app name="application/pdf">
<action name="view" ext="pdf"/>
<action name="view_comment" ext="pdf"/>
</app>
<app name="Capabilities">

View File

@ -254,6 +254,7 @@ L.Control.Menubar = L.Control.extend({
{name: _UNO('.uno:PickList', 'presentation'), id: 'file', type: 'menu', menu: [
{name: _UNO('.uno:Save', 'presentation'), id: 'save', type: 'action'},
{name: _UNO('.uno:SaveAs', 'presentation'), id: 'saveas', type: 'action'},
{name: _('Save Comments'), id: 'savecomments', type: 'action'},
{name: _('Share...'), id:'shareas', type: 'action'},
{name: _UNO('.uno:Print', 'presentation'), id: 'print', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
@ -725,15 +726,16 @@ L.Control.Menubar = L.Control.extend({
commandStates: {},
// Only these menu options will be visible in readonly mode
allowedReadonlyMenus: ['file', 'downloadas', 'view', 'help'],
allowedReadonlyMenus: ['file', 'downloadas', 'view', 'insert', 'help'],
allowedViewModeActions: [
'shareas', 'print', // file menu
'savecomments', 'shareas', 'print', // file menu
'downloadas-pdf', 'downloadas-odt', 'downloadas-doc', 'downloadas-docx', 'downloadas-rtf', 'downloadas-epub', // file menu
'downloadas-odp', 'downloadas-ppt', 'downloadas-pptx', 'downloadas-odg', 'print', // file menu
'downloadas-ods', 'downloadas-xls', 'downloadas-xlsx', 'closedocument', // file menu
'fullscreen', 'zoomin', 'zoomout', 'zoomreset', 'showresolved', // view menu
'about', 'keyboard-shortcuts', 'latest-updates', 'online-help', 'report-an-issue' // help menu
'about', 'keyboard-shortcuts', 'latest-updates', 'online-help', 'report-an-issue', // help menu
'insertcomment'
]
},
@ -1146,6 +1148,13 @@ L.Control.Menubar = L.Control.extend({
}
} else if (id === 'saveas') {
this._map.fire('postMessage', {msgId: 'UI_SaveAs'});
} else if (id === 'savecomments') {
if (this._map.isPermissionEditForComments()) {
this._map.fire('postMessage', {msgId: 'UI_Save'});
if (!this._map._disableDefaultAction['UI_Save']) {
this._map.save(false, false);
}
}
} else if (id === 'shareas') {
this._map.fire('postMessage', {msgId: 'UI_Share'});
} else if (id === 'print') {
@ -1365,6 +1374,13 @@ L.Control.Menubar = L.Control.extend({
}
}
if (this._map.isPermissionEdit()) {
switch (menuItem.id) {
case 'savecomments':
return false;
}
}
if (menuItem.type === 'action') {
if ((menuItem.id === 'rev-history' && !L.Params.revHistoryEnabled) ||
(menuItem.id === 'closedocument' && !L.Params.closeButtonEnabled) ||

View File

@ -158,7 +158,7 @@ L.Map.include({
sendUnoCommand: function (command, json) {
var isAllowedInReadOnly = false;
var allowedCommands = ['.uno:WordCountDialog', '.uno:EditAnnotation', '.uno:InsertAnnotation', '.uno:DeleteAnnotation'];
var allowedCommands = ['.uno:Save', '.uno:WordCountDialog', '.uno:EditAnnotation', '.uno:InsertAnnotation', '.uno:DeleteAnnotation'];
for (var i in allowedCommands) {
if (allowedCommands[i] === command) {
isAllowedInReadOnly = true;

View File

@ -469,7 +469,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
}
else if (tokens.equals(0, "save"))
{
if (isReadOnly())
if (isReadOnly() && !isAllowChangeComments())
{
LOG_WRN("The document is read-only, cannot save.");
}
@ -971,10 +971,16 @@ bool ClientSession::filterMessage(const std::string& message) const
}
else if (tokens.equals(0, "uno"))
{
if (tokens.size() > 1 && (tokens.equals(1, ".uno:ExecuteSearch")
|| tokens.equals(1, ".uno:EditAnnotation")
|| tokens.equals(1, ".uno:InsertAnnotation")
|| tokens.equals(1, ".uno:DeleteAnnotation")))
if (tokens.size() > 1 && (tokens.equals(1, ".uno:ExecuteSearch")))
{
allowed = true;
}
if (isAllowChangeComments()
&& tokens.size() > 1
&& (tokens.equals(1, ".uno:EditAnnotation")
|| tokens.equals(1, ".uno:InsertAnnotation")
|| tokens.equals(1, ".uno:DeleteAnnotation")))
{
allowed = true;
}

View File

@ -38,7 +38,8 @@ public:
void construct();
virtual ~ClientSession();
void setReadOnly(bool bVal = true) override;
void setReadOnly(bool bValue = true) override;
void setLockFailed(const std::string& sReason);
enum SessionState {

View File

@ -633,6 +633,8 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
{
LOG_DBG("Setting the session as readonly");
session->setReadOnly();
if (LOOLWSD::IsViewWithCommentsFileExtension(wopiStorage->getFileExtension()))
session->setAllowChangeComments();
}
// Construct a JSON containing relevant WOPI host properties
@ -719,6 +721,8 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
{
LOG_DBG("Setting the session as readonly");
session->setReadOnly();
if (LOOLWSD::IsViewWithCommentsFileExtension(localStorage->getFileExtension()))
session->setAllowChangeComments();
}
}
}

View File

@ -210,7 +210,8 @@ static int careerSpanMs = 0;
/// The timeout for a child to spawn, initially high, then reset to the default.
int ChildSpawnTimeoutMs = CHILD_TIMEOUT_MS * 4;
std::atomic<unsigned> LOOLWSD::NumConnections;
std::set<std::string> LOOLWSD::EditFileExtensions;
std::unordered_set<std::string> LOOLWSD::EditFileExtensions;
std::unordered_set<std::string> LOOLWSD::ViewWithCommentsFileExtensions;
#if MOBILEAPP
@ -3368,6 +3369,10 @@ private:
// Set the View extensions cache as well.
if (elem->getAttribute("name") == "edit")
LOOLWSD::EditFileExtensions.insert(elem->getAttribute("ext"));
else if (elem->getAttribute("name") == "view_comment")
{
LOOLWSD::ViewWithCommentsFileExtensions.insert(elem->getAttribute("ext"));
}
}
const auto& proofAttribs = GetProofKeyAttributes();

View File

@ -14,6 +14,7 @@
#include <chrono>
#include <map>
#include <set>
#include <unordered_set>
#include <string>
#include <utility>
@ -251,7 +252,8 @@ public:
#if !MOBILEAPP
static std::unique_ptr<ClipboardCache> SavedClipboards;
#endif
static std::set<std::string> EditFileExtensions;
static std::unordered_set<std::string> EditFileExtensions;
static std::unordered_set<std::string> ViewWithCommentsFileExtensions;
static unsigned MaxConnections;
static unsigned MaxDocuments;
static std::string OverrideWatermark;
@ -294,7 +296,7 @@ public:
#endif
}
/// Return true iff extension is marked as view action in discovery.xml.
/// Return true if extension is marked as view action in discovery.xml.
static bool IsViewFileExtension(const std::string& extension)
{
#if MOBILEAPP
@ -307,6 +309,19 @@ public:
#endif
}
/// Return true if extension is marked as view_comment action in discovery.xml.
static bool IsViewWithCommentsFileExtension(const std::string& extension)
{
#if MOBILEAPP
(void) extension;
return false; // mark everything editable on mobile
#else
std::string lowerCaseExtension = extension;
std::transform(lowerCaseExtension.begin(), lowerCaseExtension.end(), lowerCaseExtension.begin(), ::tolower);
return ViewWithCommentsFileExtensions.find(lowerCaseExtension) != ViewWithCommentsFileExtensions.end();
#endif
}
/// Returns the value of the specified application configuration,
/// or the default, if one doesn't exist.
template<typename T>