Merge pull request 'feature/tab-inline-rename' (#563) from feature/tab-inline-rename into develop

This commit is contained in:
Julia Radzhabova
2025-07-03 15:21:05 +00:00
4 changed files with 208 additions and 45 deletions

View File

@ -156,7 +156,7 @@ define([
'</div>' +
'<div class="lr-separator" id="id-box-doc-name">' +
// '<label id="title-doc-name" /></label>' +
'<input id="title-doc-name" autofill="off" autocomplete="off"/></input>' +
'<input id="title-doc-name" autofill="off" autocomplete="off" spellcheck="false"/></input>' +
'</div>' +
'<div class="hedset">' +
'<div class="btn-slot" data-layout-name="header-user">' +

View File

@ -81,11 +81,21 @@ define([
},
onLaunch: function() {
this.statusbar = this.createView('Statusbar').render();
this.statusbar = this.createView('Statusbar', { controller: this }).render();
this.statusbar.$el.css('z-index', 10);
this.statusbar.labelZoom.css('min-width', 80);
this.statusbar.labelZoom.text(Common.Utils.String.format(this.zoomText, 100));
this.statusbar.zoomMenu.on('item:click', _.bind(this.menuZoomClick, this));
this.$measureSpan = $('<span>').css({
position: 'absolute',
visibility: 'hidden',
whiteSpace: 'pre',
top: 0,
left: 0,
margin: 0,
padding: 0,
border: 'none'
}).appendTo(document.body);
this.bindViewEvents(this.statusbar, this.events);
@ -194,7 +204,6 @@ define([
this.api.asc_registerCallback('asc_generateNewSheetNames', _.bind(function (arrNames, callback) {
callback(this.generateSheetNames(false, undefined, arrNames));
}, this));
this.statusbar.setApi(api);
},
@ -514,48 +523,182 @@ define([
}
},
renameWorksheet: function() {
isAllowedChar(char) {
return !/[:\\/*?\[\]]/.test(char);
},
isValidWorksheetName(name) {
return !/^'|'$/.test(name) && !/[:\\/*?\[\]]/.test(name);
},
updateInputWidth($input, $tabEl) {
this.$measureSpan.text($input.val() || ' ').css({
fontWeight: $input.css('font-weight'),
fontSize: $input.css('font-size'),
fontFamily: $input.css('font-family'),
letterSpacing: $input.css('letter-spacing'),
lineHeight: $input.css('line-height'),
fontStyle: $input.css('font-style'),
fontVariant: $input.css('font-variant')
});
const width = this.$measureSpan.width();
$input.width(width);
$tabEl.width(width);
},
showRenameError(message, $input) {
var me = this;
var wc = me.api.asc_getWorksheetsCount(), items = null;
if (wc > 0) {
var sindex = me.api.asc_getActiveWorksheetIndex();
if (me.api.asc_isWorksheetLockedOrDeleted(sindex)) {
return;
}
var value = Common.Utils.InternalSettings.get("sse-settings-coauthmode");
if (!value) {
items = [];
while (wc--) {
if (sindex !== wc) {
items.push(me.api.asc_getWorksheetName(wc).toLowerCase());
}
_.defer(function() {
Common.UI.error({
msg: message,
maxwidth: 600,
callback: function() {
_.delay(function() {
me.isRenameErrorShown = false;
$input.focus().select();
}, 50);
}
}
});
});
},
var tab = me.statusbar.tabbar.tabs[me.statusbar.tabbar.getActive()];
var top = Common.Utils.getPosition(me.statusbar.$el).top - 115,
left = Common.Utils.getOffset(tab.$el).left;
var current = me.api.asc_getWorksheetName(me.api.asc_getActiveWorksheetIndex());
var win = (new SSE.Views.Statusbar.RenameDialog({
current: current,
names: items,
api: me.api,
handler: function (btn, s) {
if (btn == 'ok' && s != current) {
me.api.asc_renameWorksheet(s);
}
me.api.asc_enableKeyEvents(true);
}
}));
if (typeof win.options.width == "number") {
var bodywidth = $('body').width();
if (left+win.options.width > bodywidth)
left = bodywidth - win.options.width - 5;
finishRename({ save, $input, $tabEl, tab, otherNames }) {
const me = this;
let newName = $input.val();
const currentName = tab.label;
if (save) {
if (newName === '' || !me.isValidWorksheetName(newName)) {
if (me.isRenameErrorShown) return false;
me.isRenameErrorShown = true;
me.showRenameError(this.errSheetNameRules, $input);
return false;
}
win.show(left, top);
if (otherNames.includes(newName.toLowerCase())) {
if (me.isRenameErrorShown) return false;
me.isRenameErrorShown = true;
me.showRenameError(this.errNameExists, $input);
return false;
}
if (newName !== currentName) {
me.api.asc_renameWorksheet(newName);
me.renameInputVal = null;
me.renamingWorksheet = null;
}
} else {
newName = currentName;
}
$input.remove();
$tabEl.append(document.createTextNode(newName));
$tabEl.attr('tabtitle', newName);
tab.$el.attr('data-label', newName);
me.renameInputCaret = null;
return true;
},
createRenameInput(currentName) {
return $('<input type="text" class="inline-rename" maxlength="31" spellcheck="false"/>').val(currentName).css({
color: 'inherit',
backgroundColor: 'transparent',
boxSizing: 'border-box',
padding: 0,
height: '80%',
border: 'none',
letterSpacing: '0.01em',
fontSize: 'inherit',
fontFamily: 'inherit',
outline: 'none',
margin: 0,
lineHeight: 'inherit',
cursor: 'text'
});
},
bindRenameEvents($input, $tabEl, tab, currentName, otherNames, originalWidth) {
const me = this;
$input.on('keypress', function(e) {
const char = String.fromCharCode(e.which || e.keyCode);
if (!me.isAllowedChar(char) && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
e.stopPropagation();
}
});
$input.on('input', function() {
me.renameInputVal = $input.val();
me.renameInputCaret = $input[0].selectionStart;
me.updateInputWidth($input, $tabEl);
me.onWindowResize();
});
$input.on('blur', function(e) {
if (!me.isRenameErrorShown) {
me.renamingWorksheet = null;
me.finishRename({ save: true, $input, $tabEl, tab, otherNames });
}
e.stopPropagation();
});
$input.on('keydown', function(e) {
if (e.key === 'Enter') {
me.renamingWorksheet = null;
me.finishRename({ save: true, $input, $tabEl, tab, otherNames });
} else if (e.key === 'Escape') {
me.renamingWorksheet = null;
me.finishRename({ save: false, $input, $tabEl, tab, otherNames });
$tabEl.width(originalWidth);
$input.remove();
me.onWindowResize();
}
e.stopPropagation();
});
$input.on('click', function(e) {
e.stopPropagation()
});
},
renameWorksheet(sheetFromUpdate, fromUpdate) {
const me = this;
me.isRenameErrorShown = false;
me.renamingWorksheet = this.api.asc_getActiveWorksheetId();
const sindex = me.api.asc_getActiveWorksheetIndex();
if (me.api.asc_isWorksheetLockedOrDeleted(sindex)) return;
const wc = me.api.asc_getWorksheetsCount();
var tab = sheetFromUpdate ? _.findWhere(me.statusbar.tabbar.tabs, { sheetid: sheetFromUpdate }) : me.statusbar.tabbar.tabs[sindex];
var currentName = sheetFromUpdate ? me.renameInputVal : me.api.asc_getWorksheetName(sindex);
if (!tab) return;
const $tabEl = tab.$el.find('span');
if ($tabEl.find('input.inline-rename').length > 0) return;
const otherNames = Array.from({ length: wc }, function(_, i) {
return i !== sindex ? me.api.asc_getWorksheetName(i).toLowerCase() : null;
}
).filter(Boolean);
$tabEl.contents().filter(function(_, node) {
return node.nodeType === 3;
}).remove();
setTimeout(function() {
const originalWidth = $tabEl.width();
const $input = me.createRenameInput(currentName);
$tabEl.append($input);
me.updateInputWidth($input, $tabEl);
if (fromUpdate) {
$input[0].focus();
$input[0].setSelectionRange(me.renameInputCaret, me.renameInputCaret);
} else {
$input.focus().select();
}
if (!tab.isActive()) {
me.api.asc_showWorksheet(tab.sheetindex);
}
me.bindRenameEvents($input, $tabEl, tab, currentName, otherNames, originalWidth);
}, 10);
},
moveWorksheet: function(selectArr, cut, silent, indTo) {
@ -980,6 +1123,8 @@ define([
strSheet : 'Sheet',
textSheetViewTip: 'You are in Sheet View mode. Filters and sorting are visible only to you and those who are still in this view.',
textSheetViewTipFilters: 'You are in Sheet View mode. Filters are visible only to you and those who are still in this view.',
textDisconnect: '<b>Connection is lost</b><br>Trying to connect. Please check connection settings.'
textDisconnect: '<b>Connection is lost</b><br>Trying to connect. Please check connection settings.',
errSheetNameRules : "<b>You typed an invalid sheet name:</b><br>- A sheet name cannot be empty.<br>- A sheet name cannot contain the following characters: \ / * ? [ ] : or the character ' as first or last character.",
errNameExists : 'Sheet with such a name already exists.'
}, SSE.Controllers.Statusbar || {}));
});

View File

@ -444,26 +444,28 @@ define([
update: function() {
var me = this;
var renamingWorksheet = this.controller && this.controller.renamingWorksheet;
this.tabbar.empty(true);
this.tabMenu.items[5].menu.removeAll();
this.tabMenu.items[5].hide();
this.btnAddWorksheet.setDisabled(true);
this.sheetListMenu.removeAll();
if (this.api) {
var wc = this.api.asc_getWorksheetsCount(), i = -1;
var hidentems = [], items = [], allItems = [], tab, locked, name;
var sindex = this.api.asc_getActiveWorksheetIndex();
var wbprotected = this.api.asc_isProtectedWorkbook();
var sid = me.api.asc_getActiveWorksheetId();
while (++i < wc) {
locked = me.api.asc_isWorksheetLockedOrDeleted(i);
name = me.api.asc_getActiveNamedSheetView ? me.api.asc_getActiveNamedSheetView(i) || '' : '';
var sheetid = me.api.asc_getWorksheetId(i)
tab = {
sheetindex : i,
sheetid : sheetid,
index : items.length,
active : sindex == i,
active : renamingWorksheet ? renamingWorksheet === sheetid : sid === sheetid,
label : me.api.asc_getWorksheetName(i),
// reorderable : !locked,
cls : locked ? 'coauth-locked':'',
@ -475,7 +477,6 @@ define([
this.api.asc_isWorksheetHidden(i)? hidentems.push(tab) : items.push(tab);
allItems.push(tab);
}
if (hidentems.length) {
hidentems.forEach(function(item){
me.tabMenu.items[5].menu.addItem(new Common.UI.MenuItem({
@ -489,12 +490,27 @@ define([
this.tabbar.add(items);
if (renamingWorksheet) {
const tab = _.findWhere(this.tabbar.tabs, { sheetid: renamingWorksheet });
if (tab) {
setTimeout(() => {
me.onSheetChanged(0, tab.sheetindex, tab);
this.controller.renameWorksheet(renamingWorksheet, true);
}, 50);
} else {
setTimeout(() => {
this.tabbar.setActive(sindex)
}, 50)
}
}
allItems.forEach(function(item){
var hidden = me.api.asc_isWorksheetHidden(item.sheetindex);
me.sheetListMenu.addItem(new Common.UI.MenuItem({
style: 'white-space: pre',
caption: Common.Utils.String.htmlEncode(item.label),
value: item.sheetindex,
sheetid: item.sheetid,
checkable: true,
checked: item.active,
hidden: hidden,

View File

@ -1613,6 +1613,8 @@
"SSE.Controllers.Statusbar.textSheetViewTipFilters": "You are in Sheet View mode. Filters are visible only to you and those who are still in this view.",
"SSE.Controllers.Statusbar.warnDeleteSheet": "The selected sheets might contain data. Are you sure you want to proceed?",
"SSE.Controllers.Statusbar.zoomText": "Zoom {0}%",
"SSE.Controllers.Statusbar.errNameExists": "Sheet with such a name already exists.",
"SSE.Controllers.Statusbar.errSheetNameRules": "<b>You typed an invalid sheet name:</b><br>- A sheet name cannot be empty.<br>- A sheet name cannot contain the following characters: \\ / * ? [ ] : or the character ' as first or last character.",
"SSE.Controllers.Toolbar.confirmAddFontName": "The font you are going to save is not available on the current device.<br>The text style will be displayed using one of the system fonts, the saved font will be used when it is available.<br>Do you want to continue?",
"SSE.Controllers.Toolbar.errorComboSeries": "To create a combination chart, select at least two series of data.",
"SSE.Controllers.Toolbar.errorMaxPoints": "The maximum number of points in series per chart is 4096.",