Compare commits

...

72 Commits

Author SHA1 Message Date
e198dd7e6c feature(nodejs): replace design for mobile version with new one 2024-04-05 14:59:53 +05:00
3375315cc0 feature(nodejs): show navigation bar at top even if there are no files in user directory 2024-04-04 13:08:47 +05:00
a070b6e82b nodejs: rename back (eda1876f59) 2024-04-03 14:27:57 +03:00
43d260cd7a Merge pull request #534 from ONLYOFFICE/feature/nodejs-forced-conversion
Feature/nodejs forced conversion
2024-04-03 14:05:24 +03:00
14bb9fc8a4 Merge pull request #556 from ONLYOFFICE/fix/wopi-mobile-default-action
fix(nodejs): mobileEdit default action for mobile wopi page. Fix Bug 65920
2024-04-03 13:07:02 +03:00
57ca15153f Merge pull request #551 from ONLYOFFICE/feature/fetching-formats
Feature/fetching formats
2024-04-03 12:31:53 +03:00
bbde4dc87b Merge pull request #525 from ONLYOFFICE/nodejs-update-dependencies
build(nodejs): updated dependencies
2024-04-03 12:19:33 +03:00
5b46195f72 fix(nodejs): mobileEdit default action for mobile wopi page. Fix Bug 65920 2024-04-02 12:55:33 +07:00
81c94c9fbd refactor(nodejs): load formats script and remove redundant variables 2024-04-01 12:24:14 +05:00
cc3c640868 refactor: merge format and format-manager files into formats file 2024-04-01 12:21:18 +05:00
20e6664a6b nodejs: absolute url in request to discovery (1e72167e7d)
Revert "nodejs: absolute host is not needed with urllib" (2460f2c7ac)
2024-03-28 13:35:50 +03:00
61af52a534 nodejs: fix favicon on wopi action 2024-03-28 10:53:12 +03:00
6236f90a50 Merge pull request #554 from ONLYOFFICE/feature/for-bug-59054
Feature/for bug 59054
2024-03-27 13:00:27 +03:00
5118352714 nodejs: debug mobile editors opening (Fix Bug 59054) 2024-03-27 12:57:58 +03:00
aa5a660001 nodejs: fix wopi editor size (Fix Bug 59054) 2024-03-27 12:57:52 +03:00
bf1f987333 nodejs: wopi refactoring 2024-03-27 12:56:08 +03:00
f1f834fb0c refactor: create and use new method for finding formats by extension 2024-03-26 17:57:52 +05:00
53c3e97b5c fix(nodejs): linter offence 2024-03-25 14:25:03 +05:00
eda1876f59 refactor(nodejs): rename convext variable and remove redundant internalext variable 2024-03-25 14:18:55 +05:00
64eab6c4a2 refactor(nodejs): add flag for keeping original file and replace conditional expression with it 2024-03-25 14:09:36 +05:00
acb9a0e4c9 refactor(java-spring): add formatslist dto object and replace jsonobject with it 2024-03-25 12:51:13 +05:00
909f638a92 Merge pull request #552 from ONLYOFFICE/fix/nodejs-wopi-putfile
fix(nodejs): handle document creation in putFile. Fix Bug 66817
2024-03-22 10:02:57 +03:00
6d28e3f8d8 Merge branch 'release/v8.1.0' into develop
# Conflicts:
#	CHANGELOG.md
2024-03-21 14:23:41 +03:00
1b8f60d5dd nodejs: update formats 2024-03-21 14:21:29 +03:00
0414319893 nodejs: fill permission in embedded mode 2024-03-21 14:05:31 +03:00
12ad0d9e7e fix(nodejs): handle document creation in putFile. Fix Bug 66817 2024-03-21 17:18:58 +07:00
f3b35a878c refactor(java-spring): move formats method to indexcontroller and change return type to responseentity 2024-03-21 13:23:31 +05:00
c712f596e1 Merge remote-tracking branch 'remotes/origin/release/v8.1.0' into develop
# Conflicts:
#	CHANGELOG.md
2024-03-20 12:52:55 +03:00
ad39fb7c19 nodejs: wopi formsubmit icon
# Conflicts:
#	CHANGELOG.md
2024-03-20 12:51:24 +03:00
7d2eb086ce Merge pull request #545 from ONLYOFFICE/feature/delete-all-files
Feature/delete all files
2024-03-20 11:01:40 +03:00
0a61708f67 Merge remote-tracking branch 'remotes/origin/develop' into feature/delete-all-files
# Conflicts:
#	CHANGELOG.md
2024-03-20 10:58:41 +03:00
389198aec6 Merge pull request #544 from ONLYOFFICE/feature/auto-conversion-error
Feature/auto conversion error
2024-03-20 10:02:53 +03:00
0d302ee8f6 nodejs: error message when error:1 2024-03-19 18:06:43 +03:00
d3a548bf3f fix(ruby): conversion error code to integer 2024-03-19 17:19:48 +07:00
fadae60e89 fix: linter offenses 2024-03-19 13:43:17 +05:00
426b15b8f1 Merge remote-tracking branch 'origin/develop' into feature/fetching-formats 2024-03-19 13:01:48 +05:00
601146c847 refactor(nodejs): change formats retrieval method on frontend 2024-03-19 12:59:54 +05:00
0bfb036be6 refactor(csharp-mvc): change formats retrieval method on frontend 2024-03-18 18:03:10 +05:00
88b36049d2 refactor(csharp): change formats retrieval method on frontend 2024-03-18 18:02:29 +05:00
e389cf41b8 fix(ruby): linter offence 2024-03-18 16:27:59 +05:00
b9113f93f6 fix: linter offenses 2024-03-18 16:03:00 +05:00
af778a8636 fix(ruby): linter offenses 2024-03-18 15:26:22 +05:00
47f4f022aa Merge remote-tracking branch 'remotes/origin/develop' into feature/delete-all-files
# Conflicts:
#	CHANGELOG.md
2024-03-15 16:48:58 +03:00
b2202111ac fix changelog 2024-03-15 16:47:29 +03:00
1da215b1eb Merge remote-tracking branch 'remotes/origin/develop' into feature/auto-conversion-error
# Conflicts:
#	CHANGELOG.md
2024-03-15 16:46:37 +03:00
8c8d14b48c refactor(java-spring): change formats retrieval method on frontend 2024-03-15 15:50:14 +05:00
5217a64e83 refactor(java): change formats retrieval method on frontend 2024-03-15 15:38:14 +05:00
456789191d build(nodejs): returned some dependencies versions with breaking changes 2024-03-15 17:33:00 +07:00
e1e9efa305 refactor(ruby): change formats retrieval method on frontend 2024-03-15 15:29:58 +05:00
abab30176f refactor(python): change formats retrieval method on frontend 2024-03-14 19:11:37 +05:00
1abcf78b85 refactor(php): change formats retrieval method on frontend 2024-03-14 19:08:12 +05:00
5258fd0674 feat(csharp-mvc): handling auto-conversion error(-9) 2024-03-13 15:39:42 +05:00
ca274bc465 feat(csharp): handling auto-conversion error(-9) 2024-03-13 15:39:07 +05:00
b1d66e16a5 feat(java-spring): handling auto-conversion error(-9) 2024-03-13 15:38:24 +05:00
359cda0f67 feat(java): handling auto-conversion error(-9) 2024-03-13 15:37:13 +05:00
24a3441b1e feat(ruby): handling auto-conversion error(-9) 2024-03-13 15:36:13 +05:00
a7fca1a53b feat(python): handling auto-conversion error(-9) 2024-03-13 15:35:06 +05:00
5b6a2ba318 refactor(csharp): remove console.log 2024-03-13 13:44:47 +05:00
bb4aed6efb fix(python): uncontrolled data used in path expression 2024-03-13 13:38:53 +05:00
fbe24234d2 refactor(python): create method for deleting user folder and call it inside remove http method 2024-03-12 12:15:22 +05:00
ac894171fb changelog: delete all files 2024-03-12 11:23:53 +05:00
fd21292dbd refactor(nodejs): change parameter name and remove redundant checking 2024-03-07 16:24:51 +05:00
d43294bd8a feat(csharp-mvc): implement delete all files method 2024-03-07 15:56:02 +05:00
81f5bdd528 feat(csharp): implement delete all files method 2024-03-07 15:42:23 +05:00
c1da8e14c3 feat(java-spring): implement delete all files method 2024-03-06 14:41:29 +05:00
767b5588ab feat(java): implement delete all files method 2024-03-06 14:27:21 +05:00
8858c3c256 feat(ruby): implement delete all files method 2024-03-06 14:24:52 +05:00
89f1c18d06 feat(python): implement delete all files method 2024-03-06 13:52:06 +05:00
81ad7f7a64 feat(php): implement delete all files method 2024-03-06 13:42:41 +05:00
672aae3791 refactor(nodejs): rename variables, ids, and classes regarding forced conversion 2024-03-01 16:29:04 +05:00
999e147539 fix(nodejs): disable file type buttons after selection 2024-03-01 15:25:14 +05:00
dcf2fb43e0 build(nodejs): updated dependencies (found 0 vulnerabilities) 2024-02-22 15:47:38 +07:00
88 changed files with 4670 additions and 3012 deletions

View File

@ -1,11 +1,11 @@
# Change Log
- fill permission in embedded mode
- delete all files
- handling conversion -9 error
- nodejs: wopi formsubmit icon
- php: handling conversion -9 error
- nodejs: tabs menu
- nodejs: delete all files
- change insert image
- nodejs: handling conversion -9 error
- different goback for users
- nodejs: converting function on index page
- nodejs: close editor

View File

@ -502,6 +502,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -230,6 +230,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -592,6 +619,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -741,6 +791,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -229,6 +229,10 @@ namespace OnlineEditorsExampleMVC.Helpers
switch (errorCode)
{
case -9:
// public const int c_nErrorConversionOutputFormatError = -9;
errorMessage = String.Format(errorMessageTemplate, "Error conversion output format");
break;
case -8:
// public const int c_nErrorFileVKey = -8;
errorMessage = String.Format(errorMessageTemplate, "Error document VKey");

View File

@ -157,7 +157,7 @@ namespace OnlineEditorsExampleMVC.Models
{ "download", !user.deniedPermissions.Contains("download") },
{ "edit", canEdit && (editorsMode == "edit" || editorsMode == "view" || editorsMode == "filter" || editorsMode == "blockcontent") },
{ "print", !user.deniedPermissions.Contains("print") },
{ "fillForms", editorsMode != "view" && editorsMode != "comment" && editorsMode != "embedded" && editorsMode != "blockcontent" },
{ "fillForms", editorsMode != "view" && editorsMode != "comment" && editorsMode != "blockcontent" },
{ "modifyFilter", editorsMode != "filter" },
{ "modifyContentControl", editorsMode != "blockcontent" },
{ "review", canEdit && (editorsMode == "edit" || editorsMode == "review") },

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch("webeditor.ashx?type=formats")
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.Name,
format.Type,
format.Actions,
format.Convert,
format.Mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery != "undefined") {
jq = jQuery.noConflict();
@ -87,7 +108,7 @@ if (typeof jQuery != "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer != null) {
clearTimeout(timer);
@ -103,7 +124,7 @@ if (typeof jQuery != "undefined") {
var posExt = fileName.lastIndexOf('.');
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (ConverExtList.indexOf(posExt) == -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -116,7 +137,7 @@ if (typeof jQuery != "undefined") {
contentType: "text/xml",
type: "post",
dataType: "json",
data: JSON.stringify({ filename: fileName, filePass: filePass }),
data: JSON.stringify({ filename: fileName, filePass: filePass, fileExt: fileType }),
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -132,6 +153,12 @@ if (typeof jQuery != "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")) {
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder", filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -143,7 +170,7 @@ if (typeof jQuery != "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -178,10 +205,10 @@ if (typeof jQuery != "undefined") {
jq("#beginView, #beginEmbedded").removeClass("disable");
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf('.');
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (EditedExtList.indexOf(posExt) != -1 || FillExtList.indexOf(posExt) != -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -213,6 +240,15 @@ if (typeof jQuery != "undefined") {
});
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var filePass = jq("#filePass").val();
if (filePass) {
@ -294,6 +330,23 @@ if (typeof jQuery != "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
var requestAddress = "webeditor.ashx"
+ "?type=remove";
jq.ajax({
async: true,
contentType: "text/xml",
url: requestAddress,
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -151,7 +151,14 @@
if (storedFiles.Any())
{ %>
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>
@ -295,6 +302,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -357,9 +373,6 @@
<%: Scripts.Render("~/bundles/jquery", "~/bundles/scripts") %>
<script language="javascript" type="text/javascript">
var FillExtList = '<%= string.Join(",", DocManagerHelper.FillFormExts.ToArray()) %>';
var ConverExtList = '<%= string.Join(",", DocManagerHelper.ConvertExts.ToArray()) %>';
var EditedExtList = '<%= string.Join(",", DocManagerHelper.EditedExts.ToArray()) %>';
var UrlConverter = '<%= Url.Content("~/webeditor.ashx?type=convert") %>';
var UrlEditor = '<%= Url.Action("editor", "Home") %>';
</script>

View File

@ -87,6 +87,9 @@ namespace OnlineEditorsExampleMVC
case "reference":
Reference(context);
break;
case "formats":
Formats(context);
break;
}
}
@ -240,7 +243,13 @@ namespace OnlineEditorsExampleMVC
var fileUri = DocManagerHelper.GetDownloadUrl(fileName);
var extension = (Path.GetExtension(fileName).ToLower() ?? "").Trim('.');
var internalExtension = "ooxml";
string conversionExtension = "ooxml";
object fileExt;
if (body.TryGetValue("fileExt", out fileExt) && !String.IsNullOrEmpty(fileExt.ToString()))
{
conversionExtension = fileExt.ToString();
}
// check if the file with such an extension can be converted
if (DocManagerHelper.ConvertExts.Contains("." + extension))
@ -258,7 +267,7 @@ namespace OnlineEditorsExampleMVC
// get the url and file type of the converted file
Dictionary<string, string> newFileData;
var result = ServiceConverter.GetConvertedData(downloadUri.ToString(), extension, internalExtension, key, true, out newFileData, filePass, lang);
var result = ServiceConverter.GetConvertedData(downloadUri.ToString(), extension, conversionExtension, key, true, out newFileData, filePass, lang);
if (result != 100)
{
context.Response.Write("{ \"step\" : \"" + result + "\", \"filename\" : \"" + fileName + "\"}");
@ -393,8 +402,17 @@ namespace OnlineEditorsExampleMVC
context.Response.ContentType = "text/plain";
try
{
var fileName = Path.GetFileName(context.Request["fileName"]);
Remove(fileName); // remove a file and its history if it exists
string fileName = context.Request["fileName"];
if (!String.IsNullOrEmpty(fileName))
{
fileName = Path.GetFileName(context.Request["fileName"]);
Remove(fileName); // remove a file and its history if it exists
}
else
{
RemoveUserDirectory(); // remove the user's directory
}
context.Response.Write("{ \"success\": true }");
}
@ -414,6 +432,14 @@ namespace OnlineEditorsExampleMVC
if (Directory.Exists(histDir)) Directory.Delete(histDir, true);
}
// remove the user's directory
private static void RemoveUserDirectory()
{
var path = DocManagerHelper.StoragePath("", null); // get the path to the user directory
if (Directory.Exists(path)) Directory.Delete(path, true);
}
// get files information
private static void Files(HttpContext context)
{
@ -951,6 +977,25 @@ namespace OnlineEditorsExampleMVC
return history;
}
// return all the supported formats
private static void Formats(HttpContext context)
{
try
{
Dictionary<string, object> data = new Dictionary<string, object>
{
{ "formats", FormatManager.All() }
};
context.Response.ContentType = "application/json";
var jss = new JavaScriptSerializer();
context.Response.Write(jss.Serialize(data));
}
catch (Exception e)
{
context.Response.Write("{ \"error\": \"" + e.Message + "\"}");
}
}
}
}

View File

@ -502,6 +502,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -230,6 +230,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -596,6 +623,29 @@ footer a:hover {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -745,6 +795,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -153,7 +153,14 @@
if (storedFiles.Any())
{ %>
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr >
@ -297,6 +304,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -364,12 +380,8 @@
<script language="javascript" type="text/javascript" src="script/jquery.iframe-transport.js"></script>
<script language="javascript" type="text/javascript" src="script/jquery.fileupload.js"></script>
<script language="javascript" type="text/javascript" src="script/jquery.dropdownToggle.js"></script>
<script language="javascript" type="text/javascript" src="script/formats.js"></script>
<script language="javascript" type="text/javascript" src="script/jscript.js"></script>
<script language="javascript" type="text/javascript">
var FillFormExtList = '<%= string.Join(",", FillFormsExts.ToArray()) %>';
var ConverExtList = '<%= string.Join(",", ConvertExts.ToArray()) %>';
var EditedExtList = '<%= string.Join(",", EditedExts.ToArray()) %>';
</script>
</body>
</html>

View File

@ -437,7 +437,14 @@ namespace OnlineEditorsExample
var lang = context.Request.Cookies.GetOrDefault("ulang", null);
var extension = (Path.GetExtension(_fileName).ToLower() ?? "").Trim('.');
var internalExtension = "ooxml";
string conversionExtension = "ooxml"; // set the default conversion extension as ooxml
object fileExt;
// change the conversion extension if it was provided in the request body
if (body.TryGetValue("fileExt", out fileExt) && !String.IsNullOrEmpty(fileExt.ToString()))
{
conversionExtension = fileExt.ToString();
}
// check if the file with such an extension can be converted
if (ConvertExts.Contains("." + extension))
@ -454,7 +461,7 @@ namespace OnlineEditorsExample
// get the url and file type of the converted file
Dictionary<string, string> newFileData;
var result = ServiceConverter.GetConvertedData(fileUrl.ToString() , extension, internalExtension, key, true, out newFileData, filePass, lang);
var result = ServiceConverter.GetConvertedData(fileUrl.ToString() , extension, conversionExtension, key, true, out newFileData, filePass, lang);
if (result != 100)
{
return "{ \"step\" : \"" + result + "\", \"filename\" : \"" + _fileName + "\"}";

View File

@ -225,7 +225,7 @@ namespace OnlineEditorsExample
{ "download", !user.deniedPermissions.Contains("download") },
{ "edit", canEdit && (editorsMode == "edit" || editorsMode =="view" || editorsMode == "filter" || editorsMode == "blockcontent") },
{ "print", !user.deniedPermissions.Contains("print") },
{ "fillForms", editorsMode != "view" && editorsMode != "comment" && editorsMode != "embedded" && editorsMode != "blockcontent" },
{ "fillForms", editorsMode != "view" && editorsMode != "comment" && editorsMode != "blockcontent" },
{ "modifyFilter", editorsMode != "filter" },
{ "modifyContentControl", editorsMode != "blockcontent" },
{ "review", canEdit && (editorsMode == "edit" || editorsMode == "review") },

View File

@ -231,6 +231,10 @@ namespace ASC.Api.DocumentConverter
switch (errorCode)
{
case -9:
// public const int c_nErrorConversionOutputFormatError = -9;
errorMessage = String.Format(errorMessageTemplate, "Error conversion output format");
break;
case -8:
// public const int c_nErrorFileVKey = -8;
errorMessage = String.Format(errorMessageTemplate, "Error document VKey");

View File

@ -87,6 +87,9 @@ namespace OnlineEditorsExample
case "reference":
Reference(context);
break;
case "formats":
Formats(context);
break;
}
}
@ -222,12 +225,22 @@ namespace OnlineEditorsExample
context.Response.ContentType = "text/plain";
try
{
var fileName = Path.GetFileName(context.Request["fileName"]);
var path = _Default.StoragePath(fileName, HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress)));
var histDir = _Default.HistoryDir(path);
string fileName = context.Request["fileName"];
string userAddress = HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress));
if (File.Exists(path)) File.Delete(path); // delete file
if (Directory.Exists(histDir)) Directory.Delete(histDir, true); // delete file history
if (!String.IsNullOrEmpty(fileName))
{
fileName = Path.GetFileName(fileName);
var path = _Default.StoragePath(fileName, userAddress);
var histDir = _Default.HistoryDir(path);
if (File.Exists(path)) File.Delete(path); // delete file
if (Directory.Exists(histDir)) Directory.Delete(histDir, true); // delete file history
} else
{
string userDir = _Default.StoragePath("", userAddress);
if (Directory.Exists(userDir)) Directory.Delete(userDir, true); // delete the user's directory
}
context.Response.Write("{ \"success\": true }");
}
@ -778,5 +791,25 @@ namespace OnlineEditorsExample
+ userAddress;
return fileUrl.ToString();
}
// return all the supported formats
private static void Formats(HttpContext context)
{
try
{
Dictionary<string, object> data = new Dictionary<string, object>
{
{ "formats", FormatManager.All() }
};
context.Response.ContentType = "application/json";
var jss = new JavaScriptSerializer();
context.Response.Write(jss.Serialize(data));
}
catch (Exception e)
{
context.Response.Write("{ \"error\": \"" + e.Message + "\"}");
}
}
}
}

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch("webeditor.ashx?type=formats")
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.Name,
format.Type,
format.Actions,
format.Convert,
format.Mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery != "undefined") {
jq = jQuery.noConflict();
@ -87,7 +108,7 @@ if (typeof jQuery != "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer != null) {
clearTimeout(timer);
@ -103,7 +124,7 @@ if (typeof jQuery != "undefined") {
var posExt = fileName.lastIndexOf('.');
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (ConverExtList.indexOf(posExt) == -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -116,7 +137,7 @@ if (typeof jQuery != "undefined") {
contentType: "text/xml",
type: "post",
dataType: "json",
data: JSON.stringify({ filename: fileName, filePass: filePass }),
data: JSON.stringify({ filename: fileName, filePass: filePass, fileExt: fileType }),
url: requestAddress,
complete: function (data) {
var responseText = data.responseText;
@ -132,6 +153,12 @@ if (typeof jQuery != "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")) {
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder", filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -143,7 +170,7 @@ if (typeof jQuery != "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -178,10 +205,10 @@ if (typeof jQuery != "undefined") {
jq("#beginView, #beginEmbedded").removeClass("disable");
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf('.');
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (EditedExtList.indexOf(posExt) != -1 || FillFormExtList.indexOf(posExt) != -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -213,6 +240,15 @@ if (typeof jQuery != "undefined") {
});
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var filePass = jq("#filePass").val();
if (filePass) {
@ -293,6 +329,24 @@ if (typeof jQuery != "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
var requestAddress = "webeditor.ashx"
+ "?type=remove";
jq.ajax({
async: true,
contentType: "text/xml",
url: requestAddress,
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -248,15 +248,15 @@ public class FileController {
// get document type (word, cell or slide)
DocumentType type = fileUtility.getDocumentType(fileName);
// convert to .ooxml
String internalFileExt = "ooxml";
// get an auto-conversion extension from the request body or set it to the ooxml extension
String conversionExtension = body.getFileExt() != null ? body.getFileExt() : "ooxml";
try {
// check if the file with such an extension can be converted
if (fileUtility.getConvertExts().contains(fileExt)) {
String key = serviceConverter.generateRevisionId(fileUri); // generate document key
ConvertedData response = serviceConverter // get the URL to the converted file
.getConvertedData(fileUri, fileExt, internalFileExt, key, filePass, true, lang);
.getConvertedData(fileUri, fileExt, conversionExtension, key, filePass, true, lang);
String newFileUri = response.getUri();
String newFileType = "." + response.getFileType();
@ -291,24 +291,33 @@ public class FileController {
return createUserMetadata(uid, fileName);
} catch (Exception e) {
e.printStackTrace();
// if the operation of file converting is unsuccessful, an error occurs
return "{ \"error\": \"" + e.getMessage() + "\"}";
}
// if the operation of file converting is unsuccessful, an error occurs
return "{ \"error\": \"" + "The file can't be converted.\"}";
}
@PostMapping("/delete")
@ResponseBody
public String delete(@RequestBody final Converter body) { // delete a file
try {
String fullFileName = fileUtility.getFileName(body.getFileName()); // get full file name
String filename = body.getFileName();
boolean success = false;
// delete a file from the storage and return the status of this operation (true or false)
boolean fileSuccess = storageMutator.deleteFile(fullFileName);
if (filename != null) {
String fullFileName = fileUtility.getFileName(filename); // get full file name
// delete file history and return the status of this operation (true or false)
boolean historySuccess = storageMutator.deleteFileHistory(fullFileName);
// delete a file from the storage and return the status of this operation (true or false)
boolean fileSuccess = storageMutator.deleteFile(fullFileName);
return "{ \"success\": \"" + (fileSuccess && historySuccess) + "\"}";
// delete file history and return the status of this operation (true or false)
boolean historySuccess = storageMutator.deleteFileHistory(fullFileName);
success = fileSuccess && historySuccess;
} else {
// delete the user's folder and return the boolean status
success = storageMutator.deleteUserFolder();
}
return "{ \"success\": \"" + (success) + "\"}";
} catch (Exception e) {
// if the operation of file deleting is unsuccessful, an error occurs
return "{ \"error\": \"" + e.getMessage() + "\"}";

View File

@ -22,8 +22,10 @@ import com.onlyoffice.integration.documentserver.storage.FileStorageMutator;
import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder;
import com.onlyoffice.integration.documentserver.util.Misc;
import com.onlyoffice.integration.documentserver.util.file.FileUtility;
import com.onlyoffice.integration.documentserver.util.service.FormatService;
import com.onlyoffice.integration.entities.User;
import com.onlyoffice.integration.services.UserServices;
import com.onlyoffice.integration.dto.FormatsList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
@ -33,6 +35,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.http.ResponseEntity;
import java.util.ArrayList;
import java.util.Arrays;
@ -61,6 +64,9 @@ public class IndexController {
@Autowired
private UserServices userService;
@Autowired
private FormatService formatService;
@Value("${files.docservice.url.site}")
private String docserviceSite;
@ -136,16 +142,16 @@ public class IndexController {
@ResponseBody
public HashMap<String, String> configParameters() { // get configuration parameters
HashMap<String, String> configuration = new HashMap<>();
configuration.put("FillExtList", String.join(",", fileUtility
.getFillExts())); // put a list of the extensions that can be filled to config
configuration.put("ConverExtList", String.join(",", fileUtility
.getConvertExts())); // put a list of the extensions that can be converted to config
configuration.put("EditedExtList", String.join(",", fileUtility
.getEditedExts())); // put a list of the extensions that can be edited to config
configuration.put("UrlConverter", urlConverter);
configuration.put("UrlEditor", urlEditor);
return configuration;
}
@GetMapping("/formats")
@ResponseBody
public ResponseEntity<FormatsList> formats() { // return all the supported formats
FormatsList list = new FormatsList(formatService.getFormats());
return ResponseEntity.ok(list);
}
}

View File

@ -27,7 +27,8 @@ public enum ConvertErrorType {
UNEXPECTED_GUID_ERROR(-5, "Error unexpected guid"),
DATABASE_ERROR(-6, "Error database"),
DOCUMENT_REQUEST_ERROR(-7, "Error document request"),
DOCUMENT_VKEY_ERROR(-8, "Error document VKey");
DOCUMENT_VKEY_ERROR(-8, "Error document VKey"),
DOCUMENT_CONVERSION_OUTPUT_ERROR(-9, "Error conversion output format");
private final int code;
private final String label;

View File

@ -31,6 +31,7 @@ public interface FileStorageMutator {
boolean createFile(Path path, InputStream stream); // create a new file if it does not exist
boolean deleteFile(String fileName); // delete a file
boolean deleteFileHistory(String fileName); // delete file history
boolean deleteUserFolder(); // delete the user's folder recursively
String updateFile(String fileName, byte[] bytes); // update a file
boolean writeToFile(String pathName, String payload); // write the payload to the file
boolean moveFile(Path source, Path destination); // move a file to the specified destination

View File

@ -202,6 +202,11 @@ public class LocalFileStorage implements FileStorageMutator, FileStoragePathBuil
return historyDeleted || historyWithoutExtDeleted;
}
// delete the user's folder recursively
public boolean deleteUserFolder() {
return FileSystemUtils.deleteRecursively(new File(getStorageLocation()));
}
// update a file
public String updateFile(final String fileName, final byte[] bytes) {
Path path = fileUtility

View File

@ -31,6 +31,8 @@ public class Converter {
private String fileName;
@JsonProperty("filePass")
private String filePass;
@JsonProperty("fileExt")
private String fileExt;
@JsonProperty("lang")
private String lang;
}

View File

@ -0,0 +1,32 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.onlyoffice.integration.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import com.onlyoffice.integration.documentserver.models.Format;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
public class FormatsList {
private List<Format> formats;
}

View File

@ -120,7 +120,6 @@ public class DefaultFileConfigurer implements FileConfigurer<DefaultFileWrapper>
userPermissions.setFillForms(
!action.equals(Action.view)
&& !action.equals(Action.comment)
&& !action.equals(Action.embedded)
&& !action.equals(Action.blockcontent)
);

View File

@ -489,6 +489,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -230,6 +230,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -595,6 +622,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -752,6 +802,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -16,18 +16,12 @@
*
*/
var ConverExtList;
var EditedExtList;
var UrlConverter;
var UrlEditor;
var FillExtList;
if (typeof jQuery !== "undefined") {
jQuery.post('/config',
function(data) {
FillExtList = data.FillExtList.split(',');
ConverExtList = data.ConverExtList.split(',');
EditedExtList = data.EditedExtList.split(',');
UrlConverter = data.UrlConverter;
UrlEditor = data.UrlEditor;
});

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch('/formats')
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery !== "undefined") {
jq = jQuery.noConflict();
@ -87,7 +108,7 @@ if (typeof jQuery !== "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer !== null) {
clearTimeout(timer);
@ -103,7 +124,7 @@ if (typeof jQuery !== "undefined") {
var posExt = fileName.lastIndexOf(".") + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (ConverExtList.includes(posExt) === -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -115,7 +136,7 @@ if (typeof jQuery !== "undefined") {
contentType: "application/json",
type: "post",
dataType: "json",
data: JSON.stringify({filename: fileName, filePass: filePass}),
data: JSON.stringify({filename: fileName, filePass: filePass, fileExt: fileType}),
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -131,6 +152,12 @@ if (typeof jQuery !== "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")){
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder",filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -142,7 +169,7 @@ if (typeof jQuery !== "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -180,7 +207,7 @@ if (typeof jQuery !== "undefined") {
var posExt = fileName.lastIndexOf(".") + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (EditedExtList.includes(posExt) !== -1 || FillExtList.includes(posExt) !== -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -218,6 +245,15 @@ if (typeof jQuery !== "undefined") {
}
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var pass = jq("#filePass").val();
if (pass) {
@ -299,6 +335,24 @@ if (typeof jQuery !== "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
jq.ajax({
async: true,
contentType: "application/json",
type: "post",
dataType: "json",
data: JSON.stringify({filename: null, filePass: null}),
url: "/delete",
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -123,7 +123,14 @@
</div>
<th:block th:if="${not #lists.isEmpty(files)}">
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>
@ -275,6 +282,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -341,6 +357,7 @@
<script type="text/javascript" src="scripts/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="scripts/jquery.fileupload.js"></script>
<script type="text/javascript" src="scripts/jquery.dropdownToggle.js"></script>
<script type="text/javascript" src="scripts/formats.js"></script>
<script type="text/javascript" src="scripts/jscript.js"></script>
<script type="text/javascript" src="scripts/converter.js"></script>
<script type="text/javascript">

View File

@ -29,6 +29,7 @@ import helpers.FileUtility;
import helpers.ServiceConverter;
import helpers.TrackManager;
import helpers.Users;
import format.FormatManager;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
@ -139,6 +140,9 @@ public class IndexServlet extends HttpServlet {
case "historydata":
historyData(request, response, writer);
break;
case "formats":
formats(request, response, writer);
break;
default:
break;
}
@ -283,7 +287,8 @@ public class IndexServlet extends HttpServlet {
String fileUri = DocumentManager.getDownloadUrl(fileName, true);
String fileExt = FileUtility.getFileExtension(fileName);
FileType fileType = FileUtility.getFileType(fileName);
String internalFileExt = "ooxml";
// get an auto-conversion extension from the request body or set it to the ooxml extension
String conversionExtension = body.get("fileExt") != null ? (String) body.get("fileExt") : "ooxml";
// check if the file with such an extension can be converted
if (DocumentManager.getConvertExts().contains(fileExt)) {
@ -292,7 +297,7 @@ public class IndexServlet extends HttpServlet {
// get the url and file type to the converted file
Map<String, String> newFileData = ServiceConverter
.getConvertedData(fileUri, fileExt, internalFileExt, key, filePass, true, lang);
.getConvertedData(fileUri, fileExt, conversionExtension, key, filePass, true, lang);
String newFileUri = newFileData.get("fileUrl");
String newFileType = "." + newFileData.get("fileType");
@ -416,17 +421,23 @@ public class IndexServlet extends HttpServlet {
final HttpServletResponse response,
final PrintWriter writer) {
try {
String fileName = FileUtility.getFileName(request.getParameter("filename"));
String path = DocumentManager.storagePath(fileName, null);
String fileName = request.getParameter("filename");
if (fileName != null && !fileName.isEmpty()) {
fileName = FileUtility.getFileName(fileName);
String path = DocumentManager.storagePath(fileName, null);
// delete file
File f = new File(path);
delete(f);
// delete file history
File hist = new File(DocumentManager.historyDir(path));
delete(hist);
// delete file
File f = new File(path);
delete(f);
// delete file history
File hist = new File(DocumentManager.historyDir(path));
delete(hist);
} else {
// delete the user's folder and all the containing files
File userFolder = new File(DocumentManager.storagePath(null, null));
delete(userFolder);
}
writer.write("{ \"success\": true }");
} catch (Exception e) {
writer.write("{ \"error\": \"" + e.getMessage() + "\"}");
@ -1039,6 +1050,16 @@ public class IndexServlet extends HttpServlet {
writer.write("{}");
}
private static void formats(final HttpServletRequest request,
final HttpServletResponse response,
final PrintWriter writer) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("formats", (new FormatManager()).getFormats());
response.setContentType("application/json");
Gson gson = new Gson();
writer.write(gson.toJson(data));
}
// process get request
@Override
protected void doGet(final HttpServletRequest request,

View File

@ -297,7 +297,7 @@ public class FileModel {
edit = canEdit && (modeParam.equals("edit") || modeParam.equals("view") || modeParam.equals("filter")
|| modeParam.equals("blockcontent"));
print = !user.getDeniedPermissions().contains("print");
fillForms = !modeParam.equals("view") && !modeParam.equals("comment") && !modeParam.equals("embedded")
fillForms = !modeParam.equals("view") && !modeParam.equals("comment")
&& !modeParam.equals("blockcontent");
modifyFilter = !modeParam.equals("filter");
modifyContentControl = !modeParam.equals("blockcontent");

View File

@ -27,7 +27,8 @@ public enum ConvertErrorType {
UNEXPECTED_GUID_ERROR(-5, "Error unexpected guid"),
DATABASE_ERROR(-6, "Error database"),
DOCUMENT_REQUEST_ERROR(-7, "Error document request"),
DOCUMENT_VKEY_ERROR(-8, "Error document VKey");
DOCUMENT_VKEY_ERROR(-8, "Error document VKey"),
CONVERSION_OUTPUT_FORMAT_ERROR(-9, "Error conversion output format");
private final int code;
private final String label;

View File

@ -488,6 +488,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -230,6 +230,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -595,6 +622,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -748,6 +798,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -146,7 +146,14 @@
</div>
<% if (files.length > 0) { %>
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>
@ -299,6 +306,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -365,12 +381,10 @@
<script type="text/javascript" src="scripts/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="scripts/jquery.fileupload.js"></script>
<script type="text/javascript" src="scripts/jquery.dropdownToggle.js"></script>
<script type="text/javascript" src="scripts/formats.js"></script>
<script type="text/javascript" src="scripts/jscript.js"></script>
<script language="javascript" type="text/javascript">
var FillExtList = "<%= String.join(",", DocumentManager.getFillExts()) %>".split(",");
var ConverExtList = "<%= String.join(",", DocumentManager.getConvertExts()) %>".split(",");
var EditedExtList = "<%= String.join(",", DocumentManager.getEditedExts()) %>".split(",");
var UrlConverter = "IndexServlet?type=convert";
var UrlEditor = "EditorServlet";

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch('IndexServlet?type=formats')
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery !== "undefined") {
jq = jQuery.noConflict();
@ -87,7 +108,7 @@ if (typeof jQuery !== "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer !== null) {
clearTimeout(timer);
@ -103,7 +124,7 @@ if (typeof jQuery !== "undefined") {
var posExt = fileName.lastIndexOf(".") + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (!ConverExtList.includes(posExt)) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -115,7 +136,7 @@ if (typeof jQuery !== "undefined") {
contentType: "text/xml",
type: "post",
dataType: "json",
data: JSON.stringify({filename: fileName, filePass: filePass}),
data: JSON.stringify({filename: fileName, filePass: filePass, fileExt: fileType}),
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -131,6 +152,12 @@ if (typeof jQuery !== "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")){
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder",filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -142,7 +169,7 @@ if (typeof jQuery !== "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -180,7 +207,7 @@ if (typeof jQuery !== "undefined") {
var posExt = fileName.lastIndexOf(".") + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (EditedExtList.includes(posExt) || FillExtList.includes(posExt)) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -212,6 +239,15 @@ if (typeof jQuery !== "undefined") {
});
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var pass = jq("#filePass").val();
if (pass) {
@ -290,6 +326,21 @@ if (typeof jQuery !== "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
jq.ajax({
async: true,
contentType: "text/xml",
url: "IndexServlet?type=remove",
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -93,8 +93,6 @@ app.get('/', (req, res) => { // define a handler for default page
res.render('index', { // render index template with the parameters specified
preloaderUrl: siteUrl + configServer.get('preloaderUrl'),
convertExts: fileUtility.getConvertExtensions(),
editedExts: fileUtility.getEditExtensions(),
fillExts: fileUtility.getFillExtensions(),
storedFiles: req.DocManager.getStoredFiles(),
params: req.DocManager.getCustomParams(),
@ -333,8 +331,8 @@ app.post('/convert', (req, res) => { // define a handler for converting files
const fileUri = req.DocManager.getDownloadUrl(fileName, true);
const fileExt = fileUtility.getFileExtension(fileName, true);
const internalFileExt = 'ooxml';
let convExt = req.body.fileExt ? req.body.fileExt : internalFileExt;
if (req.body.forceConv) convExt = req.body.forceConv;
const convExt = req.body.fileExt ? req.body.fileExt : internalFileExt;
const { keepOriginal } = req.body;
const response = res;
const writeResult = function writeResult(filename, step, error) {
@ -389,14 +387,14 @@ app.post('/convert', (req, res) => { // define a handler for converting files
return;
}
// remove file with the origin extension
if (!('fileExt' in req.body)) fileSystem.unlinkSync(req.DocManager.storagePath(fileName));
if (!keepOriginal) fileSystem.unlinkSync(req.DocManager.storagePath(fileName));
const userAddress = req.DocManager.curUserHostAddress();
const historyPath = req.DocManager.historyPath(fileName, userAddress, true);
// get the history path to the file with a new extension
const correctHistoryPath = req.DocManager.historyPath(correctName, userAddress, true);
if (!('fileExt' in req.body)) {
if (!keepOriginal) {
fileSystem.renameSync(historyPath, correctHistoryPath); // change the previous history path
fileSystem.renameSync(
@ -688,7 +686,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
}
} catch (ex) {
console.log(ex);
response.write('{"error":1}');
response.write(`{"error":1,"message":${JSON.stringify(ex)}}`);
response.end();
return;
}
@ -700,7 +698,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
// file saving process
const processSave = async function processSave(downloadUri, body, fileName, userAddress) {
if (!downloadUri) {
response.write('{"error":1}');
response.write('{"error":1,"message":"save uri is empty"}');
response.end();
return;
}
@ -811,7 +809,8 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
}
}
} catch (ex) {
response.write('{"error":1}');
console.log(ex);
response.write(`{"error":1,"message":${JSON.stringify(ex)}}`);
response.end();
return;
}
@ -823,7 +822,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
// file force saving process
const processForceSave = async function processForceSave(downloadUri, body, fileName, userAddress) {
if (!downloadUri) {
response.write('{"error":1}');
response.write('{"error":1,"message":"forcesave uri is empty"}');
response.end();
return;
}
@ -915,7 +914,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
}
}
if (!body) {
res.write('{"error":1}');
res.write('{"error":1,"message":"body is empty"}');
res.end();
return;
}
@ -1067,7 +1066,7 @@ app.get('/editor', (req, res) => { // define a handler for editing document
chat: userid !== 'uid-0',
coEditing: mode === 'view' && userid === 'uid-0' ? { mode: 'strict', change: false } : null,
comment: mode !== 'view' && mode !== 'fillForms' && mode !== 'embedded' && mode !== 'blockcontent',
fillForms: mode !== 'view' && mode !== 'comment' && mode !== 'embedded' && mode !== 'blockcontent',
fillForms: mode !== 'view' && mode !== 'comment' && mode !== 'blockcontent',
modifyFilter: mode !== 'filter',
modifyContentControl: mode !== 'blockcontent',
copy: !user.deniedPermissions.includes('copy'),
@ -1200,7 +1199,9 @@ app.post('/historyObj', (req, res) => {
app.get('/formats', (req, res) => {
try {
const formats = fileUtility.getFormats();
res.json(formats);
res.json({
formats,
});
} catch (ex) {
console.log(ex); // display error message in the console
res.status(500); // write status parameter to the response

View File

@ -187,13 +187,12 @@ const putFile = function putFile(wopi, req, res, userHost) {
const userAddress = req.DocManager.curUserHostAddress(userHost);
const storagePath = req.DocManager.storagePath(wopi.id, userAddress);
const fileSize = fileSystem.statSync(storagePath).size;
if (!lockManager.hasLock(storagePath)) {
// ToDo: if body length is 0 bytes => handle document creation
if (!lockManager.hasLock(storagePath) && fileSize !== 0) {
// file isn't locked => mismatch
returnLockMismatch(res, '', 'File isn\'t locked');
} else if (lockManager.getLock(storagePath) === requestLock) {
} else if (lockManager.getLock(storagePath) === requestLock || fileSize === 0) {
// lock matches current lock => put file
saveFileFromBody(req, wopi.id, userAddress, true, (err, version) => {
if (!err) {

View File

@ -27,10 +27,15 @@ const siteUrl = configServer.get('siteUrl'); // the path to the editors installa
let cache = null;
const requestDiscovery = async function requestDiscovery() {
const requestDiscovery = async function requestDiscovery(DocManager) {
let absSiteUrl = siteUrl;
if (absSiteUrl.indexOf('/') === 0) {
absSiteUrl = DocManager.getServerHost() + siteUrl;
}
// eslint-disable-next-line no-unused-vars
return new Promise((resolve, reject) => {
const uri = siteUrl + configServer.get('wopi.discovery');
const uri = absSiteUrl + configServer.get('wopi.discovery');
const actions = [];
// parse url to allow request by relative url after
@ -81,13 +86,13 @@ const requestDiscovery = async function requestDiscovery() {
};
// get the wopi discovery information
const getDiscoveryInfo = async function getDiscoveryInfo() {
const getDiscoveryInfo = async function getDiscoveryInfo(DocManager) {
let actions = [];
if (cache) return cache;
try {
actions = await requestDiscovery();
actions = await requestDiscovery(DocManager);
} catch (e) {
return actions;
}
@ -102,8 +107,8 @@ const getDiscoveryInfo = async function getDiscoveryInfo() {
};
// get actions of the specified extension
const getActions = async function getActions(ext) {
const actions = await getDiscoveryInfo(); // get the wopi discovery information
const getActions = async function getActions(DocManager, ext) {
const actions = await getDiscoveryInfo(DocManager); // get the wopi discovery information
const filtered = [];
actions.forEach((action) => { // and filter it by the specified extention
@ -116,8 +121,8 @@ const getActions = async function getActions(ext) {
};
// get an action for the specified extension and name
const getAction = async function getAction(ext, name) {
const actions = await getDiscoveryInfo();
const getAction = async function getAction(DocManager, ext, name) {
const actions = await getDiscoveryInfo(DocManager);
let act = null;
actions.forEach((action) => {
@ -130,8 +135,8 @@ const getAction = async function getAction(ext, name) {
};
// get the default action for the specified extension
const getDefaultAction = async function getDefaultAction(ext) {
const actions = await getDiscoveryInfo();
const getDefaultAction = async function getDefaultAction(DocManager, ext) {
const actions = await getDiscoveryInfo(DocManager);
let act = null;
actions.forEach((action) => {

View File

@ -46,7 +46,7 @@ exports.registerRoutes = function registerRoutes(app) {
req.DocManager = new DocManager(req, res);
// get the wopi discovery information
const actions = await utils.getDiscoveryInfo();
const actions = await utils.getDiscoveryInfo(req.DocManager);
const wopiEnable = actions.length !== 0;
const docsExtEdit = []; // Supported extensions for WOPI
@ -65,11 +65,13 @@ exports.registerRoutes = function registerRoutes(app) {
// run through all the files and write the corresponding information to each file
// eslint-disable-next-line no-restricted-syntax
for (const file of files) {
const mobile = new RegExp(configServer.get('mobileRegEx'), 'i').test(req.get('User-Agent'));
const ext = fileUtility.getFileExtension(file.name, true); // get an extension of each file
// eslint-disable-next-line no-await-in-loop
file.actions = await utils.getActions(ext); // get actions of the specified extension
file.actions = await utils.getActions(req.DocManager, ext); // get actions of the specified extension
// eslint-disable-next-line no-await-in-loop
file.defaultAction = await utils.getDefaultAction(ext);// get the default action of the specified extension
file.defaultAction = await utils.getDefaultAction(req.DocManager, ext);// get the default action for extension
if (mobile) file.actions.forEach((act) => { if (act.name === 'mobileEdit') file.defaultAction = act; });
}
// render wopiIndex template with the parameters specified
@ -114,7 +116,7 @@ exports.registerRoutes = function registerRoutes(app) {
const user = users.getUser(req.query.userid); // get a user by the id
// get an action for the specified extension and name
const action = await utils.getAction(fileExt, req.query.action);
const action = await utils.getAction(req.DocManager, fileExt, req.query.action);
if (action && req.query.action === 'editnew') {
fileName = req.DocManager.requestEditnew(req, fileName, user);

File diff suppressed because it is too large Load Diff

View File

@ -17,17 +17,17 @@
"url": "https://github.com/ONLYOFFICE/document-server-integration/issues"
},
"dependencies": {
"body-parser": "^1.19.0",
"config": "^3.3.2",
"debug": "^4.2.0",
"ejs": "^3.1.5",
"express": "^4.17.1",
"fast-xml-parser": "^4.3.1",
"body-parser": "^1.20.2",
"config": "^3.3.11",
"debug": "^4.3.4",
"ejs": "^3.1.9",
"express": "^4.18.2",
"fast-xml-parser": "^4.3.4",
"formidable": "^1.2.2",
"he": "^1.2.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.2",
"jwa": "^2.0.0",
"log4js": "^6.3.0",
"log4js": "^6.9.1",
"mime": "^2.4.6",
"serve-favicon": "^2.5.0",
"urllib": "^2.36.1"
@ -44,8 +44,8 @@
]
},
"devDependencies": {
"eslint": "^8.28.0",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0"
"eslint-plugin-import": "^2.29.1"
}
}

View File

@ -1,8 +1,3 @@
<?xml version="1.0"?>
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path d="m9.05,22.33l0,-5.9l5.9,0l0,5.9l5.9,0l0,-10.33l2.95,0l-11.8,-10.33l-11.8,10.33l2.95,0l0,10.33l5.9,0z" fill="#FF6F3D" id="svg_1"/>
</g>
<svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 17V11H12V17H17V9H20L10 0L0 9H3V17H8Z" fill="#444444"/>
</svg>

Before

Width:  |  Height:  |  Size: 322 B

After

Width:  |  Height:  |  Size: 173 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="23" viewBox="0 0 24 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5332 22.2243L0.632544 17.5921C-0.210848 17.1877 -0.210848 16.5627 0.632544 16.1951L4.07945 14.5775L10.4966 17.5921C11.34 17.9965 12.6967 17.9965 13.5034 17.5921L19.9206 14.5775L23.3675 16.1951C24.2108 16.5995 24.2108 17.2245 23.3675 17.5921L13.4668 22.2243C12.6967 22.592 11.34 22.592 10.5332 22.2243Z" fill="#FF6F3D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5011 16.4922L0.630617 11.8546C-0.210206 11.4497 -0.210206 10.824 0.630617 10.456L3.99391 8.87329L10.5011 11.9282C11.342 12.3331 12.6946 12.3331 13.4989 11.9282L20.0061 8.87329L23.3694 10.456C24.2102 10.8608 24.2102 11.4865 23.3694 11.8546L13.4989 16.4922C12.658 16.897 11.3054 16.897 10.5011 16.4922Z" fill="#95C038"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5011 10.8195L0.630617 6.24863C-0.210206 5.84959 -0.210206 5.23289 0.630617 4.87013L10.5011 0.299281C11.342 -0.0997605 12.6946 -0.0997605 13.4989 0.299281L23.3694 4.87013C24.2102 5.26917 24.2102 5.88587 23.3694 6.24863L13.4989 10.8195C12.658 11.1822 11.3054 11.1822 10.5011 10.8195Z" fill="#5DC0E8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
<svg width="20" height="14" viewBox="0 0 20 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="20" height="2" rx="1" fill="white"/>
<rect y="6" width="20" height="2" rx="1" fill="white"/>
<rect y="12" width="20" height="2" rx="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 278 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 17C13 17.5523 12.5523 18 12 18C11.4477 18 11 17.5523 11 17V13H7C6.44772 13 6 12.5523 6 12C6 11.4477 6.44772 11 7 11H11V7C11 6.44772 11.4477 6 12 6C12.5523 6 13 6.44772 13 7V11H17C17.5523 11 18 11.4477 18 12C18 12.5523 17.5523 13 17 13H13V17Z" fill="#444444"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -19,13 +19,26 @@
var language;
var userid;
var directUrl;
var Formats;
var formatManager;
var leftPanelToggle = false;
window.onload = function () {
fetch('formats')
.then((response) => response.json())
.then((data) => {
Formats = data;
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
@ -109,7 +122,7 @@ if (typeof jQuery != "undefined") {
});
var timer = null;
var checkConvert = function (filePass, forceConvert) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer != null) {
clearTimeout(timer);
@ -125,22 +138,19 @@ if (typeof jQuery != "undefined") {
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (ConverExtList.indexOf(posExt) == -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
}
var convData = {filename: fileName, filePass: filePass, lang: language};
if (forceConvert) convData.forceConv = forceConvert;
timer = setTimeout(function () {
jq.ajaxSetup({ cache: false });
jq.ajax({
async: true,
type: "post",
dataType: "json",
data: convData,
data: {filename: fileName, filePass: filePass, lang: language, fileExt: fileType},
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -161,7 +171,7 @@ if (typeof jQuery != "undefined") {
return;
} else {
if (response.error.includes("-9")){
jq("#xmlError").removeClass("invisible");
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder",filePass);
return;
@ -177,7 +187,7 @@ if (typeof jQuery != "undefined") {
jq("#hiddenFileName").val(response.filename);
if (typeof response.step != "undefined" && response.step < 100) {
checkConvert(filePass, forceConvert);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -215,19 +225,16 @@ if (typeof jQuery != "undefined") {
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
var checkEdited = EditedExtList.split(",").filter(function(ext) { return ext == posExt;});
var checkFilled = FilledExtList.split(",").filter(function(ext) { return ext == posExt;});
if (checkEdited != "" || checkFilled != "") {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
jq(document).on("click", "#forceConvert:not(.disable)", function () {
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq("div[id='forceConvert']").addClass("disable, pale");
jq(".file-type").addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
@ -369,10 +376,14 @@ if (typeof jQuery != "undefined") {
jq("#convertFileName").removeClass("word slide cell");
jq("#convertFileName").addClass(type);
jq("#convTypes").empty();
let convExtensions = Formats.find(format => {return format.name == fileName.split('.').pop()}).convert;
convExtensions.forEach(ext => {
jq("#convTypes").append(jq(`<td name="convertingTypeButton" id="wordTo${ext}" class="button hoar" data="${ext}">${ext}</td>`));
});
let format = formatManager.findByExtension(fileName.split('.').pop());
if (format) {
format.convert.forEach(ext => {
jq("#convTypes").append(jq(`<td name="convertingTypeButton" id="wordTo${ext}" class="button hoar" data="${ext}">${ext}</td>`));
});
}
jq("#hiddenFileName").val(fileName);
jq("#convertStep1").addClass("done");
jq("#convertStep2").addClass("waiting");
@ -410,7 +421,7 @@ if (typeof jQuery != "undefined") {
async: true,
type: "post",
dataType: "json",
data: {filename: fileName, filePass: filePass, lang: language, fileExt: fileExt},
data: {filename: fileName, filePass: filePass, lang: language, fileExt: fileExt, keepOriginal: true},
url: UrlConverter,
complete: function (data) {
try {
@ -600,3 +611,26 @@ function collectParams(startParams) {
});
return startChar + params.join("&");
}
function toggleLeftPanel(event) {
leftPanelToggle = !leftPanelToggle;
event.preventDefault();
let leftPanel = document.querySelector(".left-panel");
if (leftPanelToggle) {
leftPanel.classList.remove("hide");
leftPanel.classList.add("active")
} else {
leftPanel.classList.remove("active");
leftPanel.classList.add("hide");
}
}
function toggleUserDescr(event) {
let list = event.currentTarget.querySelector("ul");
let cursor = window.getComputedStyle(event.currentTarget).getPropertyValue("cursor");
if (cursor === 'pointer') {
if (list.classList.contains("open")) list.classList.remove("open");
else list.classList.add("open");
}
}

View File

@ -141,9 +141,6 @@
.contentCells-wopi {
width: 100%;
}
.contentCells-icon {
width: 1%;
}
.contentCells-shift {
padding-right: 30px;
padding-left: 3px;
@ -154,3 +151,16 @@
width: 100%;
}
}
@media (max-width: 592px) and (min-width: 320px) {
.tableRow td:last-child:after{
display: none;
}
.contentCells-wopi {
width: auto;
}
.downloadContentCells {
margin-left: 24px;
}
}

View File

@ -74,6 +74,12 @@
}
}
@media (max-width: 1279px) and (min-width: 1024px) {
menu.main-nav {
padding-left: 64px;
}
}
@media (max-width: 1080px) {
.copy {
margin-right: 32px;
@ -84,7 +90,8 @@
}
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 90%;
}
@ -124,11 +131,18 @@
}
}
@media (max-width: 1023px) and (min-width: 593px) {
menu.main-nav {
padding-left: 40px;
}
}
@media (max-width: 769px) and (min-width: 593px) {
.contentCells-icon{
width: 5%;
}
.tableRow {
.tableRow,
menu.links {
width: 55%;
}
@ -144,7 +158,7 @@
}
.scroll-table-body {
top: 88px;
top: 33px;
}
footer {
@ -185,7 +199,8 @@
@media (max-width: 715px) {
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 45%;
}
}
@ -284,7 +299,8 @@
}
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 75%;
}
@ -324,7 +340,8 @@
}
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 95%;
}
@ -411,7 +428,7 @@
}
.scroll-table-body {
top: 88px;
top: 33px;
}
footer table tr {
@ -452,7 +469,8 @@
}
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 40%;
}
@ -551,8 +569,8 @@
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.forceConvert:hover,
.button.forceConvert {
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
@ -593,7 +611,8 @@
@media (max-width: 510px) and (min-width: 470px) {
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 35%;
}
@ -620,7 +639,8 @@
@media (max-width: 470px) and (min-width: 420px) {
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 30%;
}
.tableRow td:first-child{
@ -656,7 +676,8 @@
@media (max-width: 420px) and (min-width: 320px) {
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 25%;
}
@ -711,7 +732,8 @@
}
@media (max-width: 769px) and (min-width: 715px){
.tableRow,
.storedHeader {
.storedHeader,
menu.links {
width: 50%;
}
}
@ -760,3 +782,229 @@
max-width: none;
}
}
@media (max-width: 592px) and (min-width: 320px) {
header {
min-width: auto;
}
header, footer {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
z-index: 99;
}
.main-nav {
display: none;
}
.responsive-nav {
height: 24px;
display: flex;
flex-direction: row;
margin: 0;
align-items: center;
column-gap: 16px;
padding: 10px 16px;
width: 100%;
}
.left-panel {
background-color: rgba(186, 186, 186, 0.6);
display: none;
align-items: start;
width: 100%;
margin: 0;
position: fixed;
left: 0;
overflow-y: hidden;
height: calc(100% - 124px);
z-index:99;
}
.left-panel.hide {
display: none;
}
.left-panel.active {
display: flex;
}
.help-block {
height: calc(100% - 66px);
margin: 0;
background-color: #F5F5F5;
width: 192px;
padding: 33px 40px 33px 16px;
overflow-y: scroll;
}
.help-block::-webkit-scrollbar {
display: none;
}
.help-block {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.mobile-close-btn {
display: block;
width: 48px;
height: 48px;
background-color: #E2E2E2;
border-radius: 2px;
border-color: #E2E2E2;
color: #808080;
cursor: pointer;
outline: inherit;
border: none;
}
.center {
width: 100%;
margin: 0;
}
.table-main {
width: 100%;
}
.main {
height: calc(100% - 124px);
}
.main-panel {
width: 100%;
left: 0;
padding: 28px 16px;
row-gap: 12px;
}
menu.links {
width: 100%;
margin: 0;
padding: 0;
}
#portal-info {
width: 100%;
max-width: fit-content;
}
#portal-info .portal-name {
font-size: 16px;
}
#portal-info span:nth-child(2) {
font-size: 13px;
}
#portal-info .portal-descr {
font-size: 12px;
}
.users-list>li:first-child {
margin-top: 12px;
border-top: 1px solid #E5E5E5;
}
.user-descr {
width: 100%;
max-width: none;
min-width: auto;
border-bottom: 1px solid #E5E5E5;
padding: 12px 0;
margin: 0;
cursor: pointer;
}
.user-descr ul {
display: none;
}
.user-descr ul.open {
display: block;
}
.user-descr b {
font-size: 13px;
display: flex;
align-items: center;
column-gap: 8px;
margin: 0;
}
.user-descr b::before {
content: url("/images/plus.svg");
display: inline-block;
width: 24px;
height: 24px;
}
.storedHeader {
width: 100%;
padding: 12px 0;
}
.scroll-table-body tr:first-child {
padding-top: 0;
}
.tableRow {
width: 100%;
row-gap: 16px;
padding: 16px 0;
border-bottom: 1px solid #e5e5e5;
}
.tableRow td:first-child {
width: 100%;
}
.tableRow td {
padding: 0;
}
.firstContentCellViewers {
border-bottom: none !important;
}
.firstContentCellViewers ~ td {
border-bottom: none !important;
}
.downloadContentCellShift:after {
display: none;
}
.stored-edit {
padding: 0;
padding-left: 26px;
display: flex;
align-items: center;
}
.stored-edit span {
font-size: 14px;
line-height: 22px;
}
.clear-all {
font-size: 11px;
width: 112px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.scroll-table-body {
position: static;
}
.user-block-table {
height: auto;
}
}

View File

@ -53,14 +53,34 @@ a:visited {
header {
background: #333333;
height: 48px;
margin: 0 auto;
min-width: 1152px;
width: auto;
width: 100%;
}
header img {
margin: 10px 0 22px 32px;
.main-nav {
margin: 0;
display: flex;
align-items: center;
padding-left: 192px;
height: 48px;
}
.main-nav, .responsive-nav {
list-style: none;
column-gap: 24px;
}
.main-nav li, .mobile-nav li {
display: flex;
align-items: center;
height: 24px;
}
.responsive-nav {
display: none;
}
.mobile-close-btn {
display: none;
}
.center {
@ -274,30 +294,30 @@ label .checkbox {
opacity: 100%;
}
.button.forceConvert {
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.forceConvert.disable {
.button.file-type.disable {
cursor: default;
}
.button.forceConvert.pale {
.button.file-type.pale {
opacity: 30%;
}
.button.forceConvert.document {
.button.file-type.document {
background: #446995;
}
.button.forceConvert.spreadsheet {
.button.file-type.spreadsheet {
background: #40865C;
}
.button.forceConvert.presentation {
.button.file-type.presentation {
background: #AA5252;
}
@ -336,34 +356,51 @@ label .checkbox {
padding: 16px 0;
}
.links-panel {
.links {
display: flex;
padding: 0;
column-gap: 30px;
margin-bottom: 35px;
align-items: center;
list-style: none;
border-bottom: 1px solid #E2E2E2;
margin: 0;
margin-bottom: 24px;
}
.links-panel-current {
position: relative;
.links li {
padding: 4px;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.links-panel-current::after {
content: "";
background-color: #ff6f3d;
position: absolute;
left: -10%;
bottom: -8px;
width: 120%;
height: 2.5px;
.links li.active {
border-bottom: 2px solid #FF6F3D;
}
.links-panel a {
font-size: 14px;
.links li.active a {
color: #FF6F3D;
}
.links li.active a img {
filter: invert(55%) sepia(67%) saturate(2727%) hue-rotate(335deg) brightness(104%) contrast(101%);
}
.links a {
display: inline-block;
padding: 2px 0;
line-height: 20px;
font-size: 13px;
text-decoration: none;
}
.links-panel-home {
width: 22px;
.home-link {
height: 24px;
padding: 0 2px 8px !important;
}
.home-link a {
padding: 0;
padding-top: 7px;
}
.upload-panel,
@ -860,7 +897,7 @@ footer table tr td:first-child {
overflow-x: auto;
position: absolute;
right: 0;
top: 130px;
top: 75px;
scrollbar-color: #D0D5DA transparent;
scrollbar-width: thin;
}
@ -958,6 +995,12 @@ html {
padding-right: 28px;
}
ul.users-list {
list-style: none;
padding: 0;
margin: 0;
}
.user-descr {
display: inline-table;
width: 30vw;

View File

@ -412,7 +412,7 @@
if (config.type !== "mobile") {
return;
}
var wrapEl = document.getElementsByClassName("form");
var wrapEl = document.getElementsByTagName("iframe");
if (wrapEl.length) {
wrapEl[0].style.height = screen.availHeight + "px";
window.scrollTo(0, -1);

View File

@ -32,11 +32,27 @@
</head>
<body>
<header>
<div class="center">
<a href="./">
<img src ="images/logo.svg" alt="ONLYOFFICE" />
</a>
</div>
<nav>
<menu class="main-nav">
<li>
<a href="./">
<img src ="images/logo.svg" alt="ONLYOFFICE" />
</a>
</li>
</menu>
<menu class="responsive-nav">
<li>
<a href="#" onclick="toggleLeftPanel(event)">
<img src ="images/mobile-menu.svg" alt="ONLYOFFICE" />
</a>
</li>
<li>
<a href="./">
<img src ="images/mobile-logo.svg" alt="ONLYOFFICE" />
</a>
</li>
</menu>
</nav>
</header>
<div class="center main">
<table class="table-main">
@ -108,9 +124,22 @@
</table>
</div>
</div>
<button class="mobile-close-btn" onclick="toggleLeftPanel(event)">
<img src="images/close.svg" alt="">
</button>
</td>
<td class="section">
<div class="main-panel">
<menu class="links">
<li class="home-link active" >
<a href="./">
<img src="images/home.svg" alt="Home"/>
</a>
</li>
<li>
<a href="wopi">Wopi</a>
</li>
</menu>
<div id="portal-info" style="display: <%= storedFiles.length > 0 ? "none" : "table-cell" %>">
<span class="portal-name">ONLYOFFICE Document Editors Welcome!</span>
<span class="portal-descr">
@ -119,26 +148,22 @@
</span>
<span class="portal-descr">Please do NOT use this integration example on your own server without proper code modifications, it is intended for testing purposes only. In case you enabled this test example, disable it before going for production.</span>
<span class="portal-descr">You can open the same document using different users in different Web browser sessions, so you can check out multi-user editing functions.</span>
<% users.forEach(user => { %>
<div class="user-descr">
<b><%= user.name == null ? 'Anonymous' : user.name %></b>
<ul>
<% user.descriptions.forEach(description => { %>
<li><%= description %></li>
<% }) %>
</ul>
</div>
<% }) %>
<ul class="users-list">
<% users.forEach(user => { %>
<li class="user-descr" onclick="toggleUserDescr(event)">
<b><%= user.name == null ? 'Anonymous' : user.name %></b>
<ul>
<% user.descriptions.forEach(description => { %>
<li><%= description %></li>
<% }) %>
</ul>
</li>
<% }) %>
</ul>
</div>
<%if (storedFiles.length > 0)
{%>
<div class="stored-list">
<div class="links-panel">
<a href="./" class="links-panel-current">
<img src="images/home.svg" alt="Home" class="links-panel-home"/>
</a>
<a href="wopi">Wopi</a>
</div>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
@ -274,13 +299,13 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="xmlError" class="invisible">
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div id="forceConvert" class="button forceConvert document" data="docx">Document</div>
<div id="forceConvert" class="button forceConvert spreadsheet" data="xlsx">Spreadsheet</div>
<div id="forceConvert" class="button forceConvert presentation" data="pptx">Presentation</div>
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
@ -373,12 +398,10 @@
<script type="text/javascript" src="javascripts/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="javascripts/jquery.fileupload.js"></script>
<script type="text/javascript" src="javascripts/jquery.dropdownToggle.js"></script>
<script type="text/javascript" src="javascripts/formats.js"></script>
<script type="text/javascript" src="javascripts/jscript.js"></script>
<script type="text/javascript">
var ConverExtList = "<%= convertExts %>";
var EditedExtList = "<%= editedExts %>";
var FilledExtList = "<%= fillExts %>";
var UrlConverter = "convert";
var UrlEditor = "editor";
</script>

View File

@ -23,7 +23,7 @@
*
-->
<title>ONLYOFFICE Document Editors</title>
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="../images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<style type="text/css">
body {
@ -32,7 +32,7 @@
overflow: hidden;
-ms-content-zooming: none;
}
#office_frame {
width: 100%;
height: 100%;
@ -70,6 +70,29 @@
frameholder.appendChild(office_frame);
document.getElementById('office_form').submit();
var _onMessage = function(msg) {
var data = msg.data;
if (Object.prototype.toString.apply(data) !== '[object String]' || !window.JSON) {
return;
}
var cmd = JSON.parse(data);
if (cmd) {
if ( cmd.MessageId == 'App_LoadingStatus' ) {
var fixSize = function() {
document.getElementsByTagName("iframe")[0].style.height = window.innerHeight + "px";
}
fixSize();
window.addEventListener("orientationchange", fixSize);
}
}
};
window.addEventListener('message', function (e) {
_onMessage(e);
});
</script>
</body>

View File

@ -34,11 +34,27 @@
<body>
<header>
<div class="center">
<a href="wopi">
<img src="images/logo.svg" alt="ONLYOFFICE" />
</a>
</div>
<nav>
<menu class="main-nav">
<li>
<a href="./">
<img src ="images/logo.svg" alt="ONLYOFFICE" />
</a>
</li>
</menu>
<menu class="responsive-nav">
<li>
<a href="#" onclick="toggleLeftPanel(event)">
<img src ="images/mobile-menu.svg" alt="ONLYOFFICE" />
</a>
</li>
<li>
<a href="./">
<img src ="images/mobile-logo.svg" alt="ONLYOFFICE" />
</a>
</li>
</menu>
</nav>
</header>
<div class="center main">
<table class="table-main">
@ -99,18 +115,25 @@
</tbody>
</table>
</div>
<button class="mobile-close-btn" onclick="toggleLeftPanel(event)">
<img src="images/close.svg" alt="">
</button>
</td>
<td class="section">
<div class="main-panel">
<menu class="links">
<li class="home-link" >
<a href="./">
<img src="images/home.svg" alt="Home"/>
</a>
</li>
<li class="active">
<a href="wopi">Wopi</a>
</li>
</menu>
<div id="portal-info" style="display: <%= storedFiles.length > 0 ? "none" : "table-cell" %>">
<% if (!wopiEnable)
{ %>
<div class="links-panel">
<a href="./">
<img src="images/home.svg" alt="Home" class="links-panel-home"/>
</a>
<a href="wopi" class="links-panel-current">Wopi</a>
</div>
<span class="portal-name">ONLYOFFICE Document Editors Welcome!</span>
<span class="portal-descr">
Before you get started with a demo sample of ONLYOFFICE Docs, please enable the WOPI protocol.
@ -131,12 +154,6 @@
<% if (storedFiles.length > 0)
{ %>
<div class="stored-list">
<div class="links-panel">
<a href="./">
<img src="images/home.svg" alt="Home" class="links-panel-home"/>
</a>
<a href="wopi" class="links-panel-current">Wopi</a>
</div>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
@ -274,12 +291,10 @@
<script type="text/javascript" src="javascripts/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="javascripts/jquery.fileupload.js"></script>
<script type="text/javascript" src="javascripts/jquery.dropdownToggle.js"></script>
<script type="text/javascript" src="javascripts/formats.js"></script>
<script type="text/javascript" src="javascripts/jscript.js"></script>
<script type="text/javascript">
var ConverExtList = "<%= convertExts %>";
var EditedExtList = "<%= editedExts %>";
var FilledExtList = "<%= fillExts %>";
var UrlConverter = "convert";
var UrlEditor = "wopi-action";
</script>

View File

@ -621,6 +621,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch('formats')
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
JSON.parse(data.formats).forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery != "undefined") {
jq = jQuery.noConflict();
@ -111,7 +132,7 @@ if (typeof jQuery != "undefined") {
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (ConverExtList.indexOf(posExt) == -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -206,9 +227,9 @@ if (typeof jQuery != "undefined") {
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt + 1).trim().toLowerCase() : '';
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (EditedExtList.indexOf(posExt) != -1 || FillFormsExtList.indexOf(posExt) != -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -325,6 +346,22 @@ if (typeof jQuery != "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
jq.ajax({
async: true,
contentType: "text/xml",
type: "delete",
url: "delete",
complete: function (data) {
if (JSON.parse(data.responseText).status == 'success') {
window.location.reload(true);
}
}
});
}
});
jq(document).on("click", "#createSample", function () {
jq(".try-editor").each(function () {
var href = jq(this).attr("href");

View File

@ -143,6 +143,11 @@ function routers()
echo json_encode($response);
return;
}
if (str_starts_with($path, '/formats')) {
$response = formats();
echo json_encode($response);
return;
}
http_response_code(HTTPStatus::NotFound->value);
}

View File

@ -300,12 +300,15 @@ function convert()
function delete()
{
try {
$fileName = basename($_GET["fileName"]);
if (isset($_GET["fileName"]) && !empty($_GET["fileName"])) {
$fileName = basename($_GET["fileName"]);
$filePath = getStoragePath($fileName);
$filePath = getStoragePath($fileName);
unlink($filePath); // delete a file
delTree(getHistoryDir($filePath)); // delete all the elements from the history directory
unlink($filePath); // delete a file
delTree(getHistoryDir($filePath)); // delete all the elements from the history directory
} else {
delTree(getStoragePath('')); // delete the user's folder and all the containing files
}
} catch (Exception $e) {
sendlog("Deletion ".$e->getMessage(), "webedior-ajax.log");
$result["error"] = "error: " . $e->getMessage();
@ -656,3 +659,19 @@ function restore()
];
}
}
function formats()
{
try {
$formatManager = new FormatManager();
$formats = $formatManager->all();
return [
'formats' => json_encode($formats)
];
} catch (Exception $error) {
return [
'error' => 'Server error'
];
}
}

View File

@ -132,7 +132,7 @@ final class DocEditorView extends View
$editorsMode == "view" || $editorsMode == "filter" || $editorsMode == "blockcontent"),
"print" => !in_array("print", $user->deniedPermissions),
"fillForms" => $editorsMode != "view" && $editorsMode != "comment"
&& $editorsMode != "embedded" && $editorsMode != "blockcontent",
&& $editorsMode != "blockcontent",
"modifyFilter" => $editorsMode != "filter",
"modifyContentControl" => $editorsMode != "blockcontent",
"review" => $canEdit && ($editorsMode == "edit" || $editorsMode == "review"),

View File

@ -44,9 +44,6 @@ final class IndexView extends View
"editButton" => $this->getEditButton(),
"dataDocs" => $this->getPreloaderUrl(),
"date" => date("Y"),
"fillFormsExtList" => implode(",", $formatManager->fillableExtensions()),
"converExtList" => implode(",", $formatManager->convertibleExtensions()),
"editedExtList" => implode(",", $formatManager->editableExtensions()),
"serverVersion" => $configManager -> getVersion(),
];
}

View File

@ -220,11 +220,7 @@
<script type="text/javascript" src="assets/js/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="assets/js/jquery.fileupload.js"></script>
<script type="text/javascript" src="assets/js/jquery.dropdownToggle.js"></script>
<script type="text/javascript" src="assets/js/formats.js"></script>
<script type="text/javascript" src="assets/js/jscript.js"></script>
<script type="text/javascript">
var FillFormsExtList = '{fillFormsExtList}';
var ConverExtList = '{converExtList}';
var EditedExtList = '{editedExtList}';
</script>
</body>
</html>

View File

@ -1,5 +1,12 @@
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>

View File

@ -80,7 +80,8 @@ def routers():
path('restore', actions.restore),
path('saveas', actions.saveAs),
path('track', actions.track),
path('upload', actions.upload)
path('upload', actions.upload),
path('formats', actions.formats)
]
main += static(
settings.STATIC_URL,

View File

@ -286,6 +286,16 @@ def removeFile(filename, req):
shutil.rmtree(histDir)
# remove the user's directory and all the containing files
def removeUserFolder(req):
path = os.path.normpath(getRootFolder(req))
if not path.startswith(str(config_manager.storage_path())) or not os.path.exists(path):
raise FileNotFoundError
shutil.rmtree(path)
# generate file key
def generateFileKey(filename, req):
path = getStoragePath(filename, req)

View File

@ -78,6 +78,7 @@ def processError(error):
prefix = 'Error occurred in the ConvertService: '
mapping = {
'-9': f'{prefix}Error conversion output format',
'-8': f'{prefix}Error document VKey',
'-7': f'{prefix}Error document request',
'-6': f'{prefix}Error database',

View File

@ -30,6 +30,8 @@ from src.configuration import ConfigurationManager
from src.response import ErrorResponse
from src.utils import docManager, fileUtils, serviceConverter, users, jwtManager, historyManager, trackManager
from urllib.parse import urlparse, parse_qs
from src.format import FormatManager
import msgspec
config_manager = ConfigurationManager()
@ -75,13 +77,16 @@ def convert(request):
lang = request.COOKIES.get('ulang') if request.COOKIES.get('ulang') else 'en'
fileUri = docManager.getDownloadUrl(filename, request)
fileExt = fileUtils.getFileExt(filename)
newExt = 'ooxml' # convert to .ooxml
# get an auto-conversion extension from the request body or set it to the ooxml extension
conversionExtension = body.get('fileExt') or 'ooxml'
if docManager.isCanConvert(fileExt): # check if the file extension is available for converting
key = docManager.generateFileKey(filename, request) # generate the file key
# get the url of the converted file
convertedData = serviceConverter.getConvertedData(fileUri, fileExt, newExt, key, True, filePass, lang)
convertedData = serviceConverter.getConvertedData(
fileUri, fileExt, conversionExtension, key, True, filePass, lang
)
# if the converter url is not received, the original file name is passed to the response
if not convertedData:
@ -276,7 +281,7 @@ def edit(request):
'edit': canEdit & ((edMode == 'edit') | (edMode == 'view') | (edMode == 'filter') \
| (edMode == "blockcontent")),
'print': 'print' not in user.deniedPermissions,
'fillForms': (edMode != 'view') & (edMode != 'comment') & (edMode != 'embedded') \
'fillForms': (edMode != 'view') & (edMode != 'comment') \
& (edMode != "blockcontent"),
'modifyFilter': edMode != 'filter',
'modifyContentControl': edMode != "blockcontent",
@ -424,13 +429,19 @@ def track(request):
# remove a file
def remove(request):
filename = fileUtils.getFileName(request.GET['filename'])
response = {}
docManager.removeFile(filename, request)
try:
filename = request.GET.get('filename', '')
if filename:
filename = fileUtils.getFileName(filename)
docManager.removeFile(filename, request)
else:
docManager.removeUserFolder(request)
response.setdefault('success', True)
except Exception as e:
response.setdefault('error', str(e.args[0]))
response.setdefault('success', True)
return HttpResponse(json.dumps(response), content_type='application/json')
@ -658,3 +669,12 @@ def restore(request: HttpRequest) -> HttpResponse:
message=f'{type(error)}: {error}',
status=HTTPStatus.INTERNAL_SERVER_ERROR
)
@http.GET()
def formats(request: HttpRequest) -> HttpResponse:
data = {
'formats': [msgspec.to_builtins(format) for format in FormatManager().all()]
}
return HttpResponse(json.dumps(data), content_type='application/json')

View File

@ -16,17 +16,13 @@
"""
import json
from django.shortcuts import render
from src.configuration import ConfigurationManager
from src.format import FormatManager
from src.utils import users
from src.utils import docManager
config_manager = ConfigurationManager()
format_manager = FormatManager()
def getDirectUrlParam(request):
@ -41,10 +37,7 @@ def default(request): # default parameters that will be passed to the template
'users': users.USERS,
'languages': config_manager.languages(),
'preloadurl': config_manager.document_server_preloader_url().geturl(),
'editExt': json.dumps(format_manager.editable_extensions()), # file extensions that can be edited
'convExt': json.dumps(format_manager.convertible_extensions()), # file extensions that can be converted
'files': docManager.getStoredFiles(request), # information about stored files
'fillExt': json.dumps(format_manager.fillable_extensions()),
'directUrl': str(getDirectUrlParam(request)).lower,
'serverVersion': config_manager.getVersion()
}

View File

@ -503,6 +503,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -230,6 +230,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -593,6 +620,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -742,6 +792,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -17,6 +17,27 @@
*/
var directUrl;
var formatManager;
window.onload = function () {
fetch('formats')
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery !== "undefined") {
jq = jQuery.noConflict();
@ -87,7 +108,7 @@ if (typeof jQuery !== "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer !== null) {
clearTimeout(timer);
@ -103,7 +124,7 @@ if (typeof jQuery !== "undefined") {
var posExt = fileName.lastIndexOf(".");
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (ConverExtList.indexOf(posExt) === -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -115,7 +136,7 @@ if (typeof jQuery !== "undefined") {
contentType: "text/xml",
type: "post",
dataType: "json",
data: JSON.stringify({ filename: fileName, filePass: filePass }),
data: JSON.stringify({ filename: fileName, filePass: filePass, fileExt: fileType }),
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -131,6 +152,12 @@ if (typeof jQuery !== "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")){
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder",filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -142,7 +169,7 @@ if (typeof jQuery !== "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -177,10 +204,10 @@ if (typeof jQuery !== "undefined") {
jq("#beginView, #beginEmbedded").removeClass("disable");
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf(".");
var posExt = fileName.lastIndexOf(".") + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : "";
if (EditedExtList.indexOf(posExt) !== -1 || FillExtList.indexOf(posExt) !== -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -212,6 +239,15 @@ if (typeof jQuery !== "undefined") {
});
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var filePass = jq("#filePass").val();
if (filePass) {
@ -291,6 +327,22 @@ if (typeof jQuery !== "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
jq.ajax({
async: true,
contentType: "text/xml",
type: "delete",
url: "remove",
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -139,7 +139,14 @@
</div>
{% if files %}
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>
@ -274,6 +281,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -338,12 +354,10 @@
<script type="text/javascript" src="{% static "js/jquery.blockUI.js" %}"></script>
<script type="text/javascript" src="{% static "js/jquery.iframe-transport.js" %}"></script>
<script type="text/javascript" src="{% static "js/jquery.fileupload.js" %}"></script>
<script type="text/javascript" src="{% static "js/formats.js" %}"></script>
<script type="text/javascript" src="{% static "js/jscript.js" %}"></script>
<script type="text/javascript">
var FillExtList = {{ fillExt | safe }};
var ConverExtList = {{ convExt | safe }};
var EditedExtList = {{ editExt | safe }};
var UrlConverter = "convert";
var UrlEditor = "edit";
</script>

View File

@ -0,0 +1,66 @@
/**
*
* (c) Copyright Ascensio System SIA 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
class Format {
constructor(name, type, actions, convert, mime) {
this.name = name;
this.type = type;
this.actions = actions;
this.convert = convert;
this.mime = mime;
}
isAutoConvertible() {
return this.actions.includes('auto-convert');
}
isEditable() {
return this.actions.includes('edit') || this.actions.includes('lossy-edit');
}
isFillable() {
return this.actions.includes('fill');
}
}
class FormatManager {
formats = [];
constructor(formats) {
if(Array.isArray(formats)) this.formats = formats;
}
findByExtension(extension) {
return this.formats.find(format => format.name == extension);
}
isAutoConvertible(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isAutoConvertible();
}
isEditable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isEditable();
}
isFillable(extension) {
let format = this.findByExtension(extension);
return format !== undefined && format.isFillable();
}
}

View File

@ -18,6 +18,27 @@
var directUrl;
var userId;
var formatManager;
window.onload = function () {
fetch('formats')
.then((response) => response.json())
.then((data) => {
if (data.formats) {
let formats = [];
data.formats.forEach(format => {
formats.push(new Format(
format.name,
format.type,
format.actions,
format.convert,
format.mime
));
});
formatManager = new FormatManager(formats);
}
})
}
if (typeof jQuery != "undefined") {
jq = jQuery.noConflict();
@ -98,7 +119,7 @@ if (typeof jQuery != "undefined") {
});
var timer = null;
var checkConvert = function (filePass) {
var checkConvert = function (filePass, fileType) {
filePass = filePass ? filePass : null;
if (timer != null) {
clearTimeout(timer);
@ -111,10 +132,10 @@ if (typeof jQuery != "undefined") {
jq("#filePass").val("");
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf('.');
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (ConvertExtList.indexOf(posExt) == -1) {
if (!formatManager.isAutoConvertible(posExt)) {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
return;
@ -126,7 +147,7 @@ if (typeof jQuery != "undefined") {
contentType: "text/xml",
type: "post",
dataType: "json",
data: JSON.stringify({ filename: fileName, filePass: filePass }),
data: JSON.stringify({filename: fileName, filePass: filePass, fileExt: fileType}),
url: UrlConverter,
complete: function (data) {
var responseText = data.responseText;
@ -142,6 +163,12 @@ if (typeof jQuery != "undefined") {
}
return;
} else {
if (response.error.includes("Error conversion output format")){
jq("#select-file-type").removeClass("invisible");
jq("#step2").removeClass("current");
jq("#hiddenFileName").attr("placeholder",filePass);
return;
}
jq(".current").removeClass("current");
jq(".step:not(.done)").addClass("error");
jq("#mainProgress .error-message").show().find("span").text(response.error);
@ -153,7 +180,7 @@ if (typeof jQuery != "undefined") {
jq("#hiddenFileName").val(response.filename);
if (response.step && response.step < 100) {
checkConvert(filePass);
checkConvert(filePass, fileType);
} else {
jq("#step2").addClass("done").removeClass("current");
loadScripts();
@ -188,10 +215,10 @@ if (typeof jQuery != "undefined") {
jq("#beginView, #beginEmbedded").removeClass("disable");
var fileName = jq("#hiddenFileName").val();
var posExt = fileName.lastIndexOf('.');
var posExt = fileName.lastIndexOf('.') + 1;
posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : '';
if (EditedExtList.indexOf(posExt) != -1 || FillExtList.indexOf(posExt) != -1) {
if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) {
jq("#beginEdit").removeClass("disable");
}
};
@ -217,6 +244,15 @@ if (typeof jQuery != "undefined") {
});
};
jq(document).on("click", ".file-type:not(.disable)", function () {
const currentElement = jq(this);
var fileType = currentElement.attr("data");
var filePass = jq("#hiddenFileName").attr("placeholder");
jq('.file-type').addClass(["disable", "pale"]);
currentElement.removeClass("pale");
checkConvert(filePass, fileType);
});
jq(document).on("click", "#enterPass", function () {
var filePass = jq("#filePass").val();
if (filePass) {
@ -292,6 +328,7 @@ if (typeof jQuery != "undefined") {
jq.ajax({
async: true,
contentType: "text/xml",
type: "delete",
url: requestAddress,
complete: function (data) {
document.location.reload();
@ -299,6 +336,22 @@ if (typeof jQuery != "undefined") {
});
});
jq(document).on("click", ".clear-all", function () {
if (confirm("Delete all the files?")) {
jq.ajax({
async: true,
contentType: "text/xml",
type: "delete",
url: "remove",
complete: function (data) {
if (JSON.parse(data.responseText).success) {
window.location.reload(true);
}
}
});
}
});
function showUserTooltip (isMobile) {
if ( jq("div#portal-info").is(":hidden") ) {
jq("div#portal-info").show();

View File

@ -503,6 +503,17 @@
justify-content: space-between;
align-items: center;
}
.buttonsMobile.indent {
margin-bottom: 0;
flex-wrap: nowrap;
}
.button.file-type:hover,
.button.file-type {
height: 28px;
width: 100px;
margin-bottom: 10px !important;
font-size: 9px;
}
.button.gray{
margin: 0;
}

View File

@ -231,6 +231,33 @@ label .checkbox {
color: #FF6F3D;
}
.button.file-type {
font-size: 11px;
color: #FFFFFF;
padding: 8px 8px;
margin-right: 10px;
}
.button.file-type.disable {
cursor: default;
}
.button.file-type.pale {
opacity: 30%;
}
.button.file-type.document {
background: #446995;
}
.button.file-type.spreadsheet {
background: #40865C;
}
.button.file-type.presentation {
background: #AA5252;
}
.upload-panel {
float: left;
padding: 24px 0;
@ -594,6 +621,29 @@ footer table tr td:first-child {
width: 4%;
}
.storedHeader {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
.storedHeaderClearAll {
padding-right: 52px;
}
.clear-all {
display: inline-block;
width: 100px;
padding: 2px;
outline: 1px solid #E5E5E5;
text-align: center;
cursor:pointer;
text-transform: uppercase;
background-color: #F5F5F5;
color: #666666;
}
.select-user {
color: #444444;
font-family: Open Sans;
@ -743,6 +793,16 @@ html {
margin-left: 25px;
}
.buttonsMobile.indent{
padding-left: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
.invisible {
display: none;
}
.tooltip {
background: #FFFFFF;
border-radius: 5px;

View File

@ -102,14 +102,15 @@ class HomeController < ApplicationController
file_pass = body['filePass'] || nil
file_uri = DocumentHelper.get_download_url(file_name)
extension = File.extname(file_name).downcase
internal_extension = 'ooxml'
# get an auto-conversion extension from the request body or set it to the ooxml extension
conversion_extension = body['fileExt'] || 'ooxml'
if DocumentHelper.convert_exts.include?(extension) # check if the file with such an extension can be converted
key = ServiceConverter.generate_revision_id(file_uri) # generate document key
percent, new_file_uri, new_file_type = ServiceConverter.get_converted_data(
file_uri,
extension.delete('.'),
internal_extension.delete('.'),
conversion_extension.delete('.'),
key,
true,
file_pass,
@ -251,24 +252,31 @@ class HomeController < ApplicationController
# removing a file
def remove
file_name = File.basename(params[:filename]) # get the file name
unless file_name # if it doesn't exist
render(plain: '{"success":false}') # report that the operation is unsuccessful
return
end
DocumentHelper.init(request.remote_ip, request.base_url)
storage_path = DocumentHelper.storage_path(file_name, nil)
hist_dir = DocumentHelper.history_dir(storage_path)
# if the file exists
FileUtils.rm_f(storage_path) # delete it from the storage path
if params[:filename].present?
file_name = File.basename(params[:filename]) # get the file name
unless file_name # if it doesn't exist
render(plain: '{"success":false}') # report that the operation is unsuccessful
return
end
# if the history directory of this file exists
FileUtils.rm_rf(hist_dir) # delete it
storage_path = DocumentHelper.storage_path(file_name, nil)
hist_dir = DocumentHelper.history_dir(storage_path)
# if the file exists
FileUtils.rm_f(storage_path) # delete it from the storage path
# if the history directory of this file exists
FileUtils.rm_rf(hist_dir) # delete it
else
storage_path = DocumentHelper.storage_path('', nil)
FileUtils.rm_rf(storage_path) # remove the user's directory and all the containing files
end
render(plain: '{"success":true}') # report that the operation is successful
nil
rescue StandardError
render(plain: '{"error": "Server error"}')
end
# getting files information
@ -539,4 +547,15 @@ class HomeController < ApplicationController
}
)
end
# return all supported formats
def formats
render(
json: JSON.generate(
{
formats: FormatManager.new.all.map(&:serialize)
}
)
)
end
end

View File

@ -146,7 +146,7 @@ class FileModel
download: @user.denied_permissions.exclude?('download'),
edit: can_edit && ['edit', 'view', 'filter', 'blockcontent'].include?(editors_mode),
print: @user.denied_permissions.exclude?('print'),
fillForms: ['view', 'comment', 'embedded', 'blockcontent'].exclude?(editors_mode),
fillForms: ['view', 'comment', 'blockcontent'].exclude?(editors_mode),
modifyFilter: !editors_mode.eql?('filter'),
modifyContentControl: !editors_mode.eql?('blockcontent'),
review: can_edit && (editors_mode.eql?('edit') || editors_mode.eql?('review')),
@ -304,19 +304,19 @@ class FileModel
prev = hist_data[(i - 2).to_s] # get the history data from the previous file version
# write key and url information about previous file version with optional direct url
data_obj['previous'] = if enable_direct_url? == true
{ # write key and url information about previous file version with optional directUrl
fileType: prev['fileType'],
key: prev['key'],
url: prev['url'],
directUrl: prev['directUrl']
}
else
{
fileType: prev['fileType'],
key: prev['key'],
url: prev['url']
}
end
{ # write key and url information about previous file version with optional directUrl
fileType: prev['fileType'],
key: prev['key'],
url: prev['url'],
directUrl: prev['directUrl']
}
else
{
fileType: prev['fileType'],
key: prev['key'],
url: prev['url']
}
end
diff_path = [hist_dir, (i - 1).to_s, 'diff.zip'].join(File::SEPARATOR)
if File.exist?(diff_path)

View File

@ -108,6 +108,8 @@ class ServiceConverter
# add an error message to the error message template depending on the error code
case error_code
when -9
error_message = 'Error occurred in the ConvertService.ashx: Error conversion output format'
when -8
error_message = 'Error occurred in the ConvertService.ashx: Error document VKey'
when -7
@ -139,7 +141,7 @@ class ServiceConverter
error_element = file_result['error']
unless error_element.nil? # if an error occurs
process_convert_service_responce_error(Integer(error_element, 10)) # get an error message
process_convert_service_responce_error(Integer(error_element)) # get an error message
end
is_end_convert = file_result['endConvert'] # check if the conversion is completed

View File

@ -123,7 +123,14 @@
</div>
<% if docs.length > 0 %>
<div class="stored-list">
<span class="header-list">Your documents</span>
<div class="storedHeader">
<div class="storedHeaderText">
<span class="header-list">Your documents</span>
</div>
<div class="storedHeaderClearAll">
<div class="clear-all">Clear all</div>
</div>
</div>
<table class="tableHeader" cellspacing="0" cellpadding="0" width="100%">
<thead>
<tr>
@ -267,6 +274,15 @@
<div class="describeUpload">After these steps are completed, you can work with your document.</div>
<span id="step1" class="step">1. Loading the file.</span>
<span class="step-descr">The loading speed depends on file size and additional elements it contains.</span>
<div id="select-file-type" class="invisible">
<br />
<span class="step">Please select the current document type</span>
<div class="buttonsMobile indent">
<div class="button file-type document" data="docx">Document</div>
<div class="button file-type spreadsheet" data="xlsx">Spreadsheet</div>
<div class="button file-type presentation" data="pptx">Presentation</div>
</div>
</div>
<br />
<span id="step2" class="step">2. Conversion.</span>
<span class="step-descr">The file is converted to OOXML so that you can edit it.</span>
@ -329,9 +345,6 @@
<%= javascript_include_tag "application" %>
<script language="javascript" type="text/javascript">
var FillExtList = '<%= DocumentHelper.fill_forms_exts.join"," %>';
var ConvertExtList = '<%= DocumentHelper.convert_exts.join(",") %>';
var EditedExtList = '<%= DocumentHelper.edited_exts.join(",") %>';
var UrlConverter = '<%= convert_path %>';
var UrlEditor = '<%= editor_path %>';
</script>

View File

@ -39,12 +39,13 @@ class Application < Rails::Application
match '/files', to: 'home#files', via: 'get'
match '/index', to: 'home#index', via: 'get'
match '/reference', to: 'home#reference', via: 'post'
match '/remove', to: 'home#remove', via: 'get'
match '/remove', to: 'home#remove', via: 'delete'
match '/rename', to: 'home#rename', via: 'post'
match '/restore', to: 'home#restore', via: 'put'
match '/sample', to: 'home#sample', via: 'get'
match '/saveas', to: 'home#saveas', via: 'post'
match '/track', to: 'home#track', via: 'post'
match '/upload', to: 'home#upload', via: 'post'
match '/formats', to: 'home#formats', via: 'get'
end
end