Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
"build": "npm run buildMDViewer && npm run _create-src-node-pkg-lock && npm run _buildonly && npm run createJSDocs && npm run zipTestFiles && npm run lint && npm run _vulnerabilityCheck",
"build:debug": "npm run buildMDViewer && npm run _create-src-node-pkg-lock && npm run _buildonlyDebug && npm run createJSDocs && npm run zipTestFiles && npm run lint && npm run _vulnerabilityCheck",
"clean": "gulp clean && gulp reset",
"release:dev": "gulp releaseDev",
"release:staging": "gulp releaseStaging",
"release:prod": "gulp releaseProd",
"release:dev": "npm run buildMDViewer && gulp releaseDev",
"release:staging": "npm run buildMDViewer && gulp releaseStaging",
"release:prod": "npm run buildMDViewer && gulp releaseProd",
"validate:dist-size": "gulp validateDistSizeRestrictions",
"_releaseWebCache": "gulp releaseWebCache",
"_patchVersionBump": "gulp patchVersionBump",
Expand Down
77 changes: 77 additions & 0 deletions src-mdviewer/src/components/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,67 @@ function deleteTableColumn(table, colIdx) {
});
}

// Module-level clipboard for table row/column copy-paste.
// Stores the inner HTML of each cell so they can be re-inserted into other rows/cols.
let _tableRowClipboard = null; // { cells: [html, ...] }
let _tableColClipboard = null; // { cells: [html, ...] } one per row in source table

function copyTableRow(tr) {
_tableRowClipboard = {
cells: Array.from(tr.children).map(td => td.innerHTML)
};
}

function copyTableColumn(table, colIdx) {
const rows = table.querySelectorAll("tr");
_tableColClipboard = {
cells: Array.from(rows).map(row => {
const cell = row.children[colIdx];
return cell ? cell.innerHTML : "";
})
};
}

function pasteTableRow(table, afterRow) {
if (!_tableRowClipboard) return;
const tbody = table.querySelector("tbody") || table;
const refRow = afterRow || tbody.lastElementChild;
const colCount = refRow ? refRow.children.length : _tableRowClipboard.cells.length;
const newRow = document.createElement("tr");
for (let i = 0; i < colCount; i++) {
const td = document.createElement("td");
td.innerHTML = _tableRowClipboard.cells[i] != null ? _tableRowClipboard.cells[i] : "&nbsp;";
newRow.appendChild(td);
}
if (afterRow && afterRow.nextSibling) {
afterRow.parentNode.insertBefore(newRow, afterRow.nextSibling);
} else if (afterRow) {
afterRow.parentNode.appendChild(newRow);
} else {
tbody.appendChild(newRow);
}
focusCell(newRow.firstElementChild);
return newRow;
}

function pasteTableColumn(table, afterColIdx) {
if (!_tableColClipboard) return;
const rows = table.querySelectorAll("tr");
const insertIdx = afterColIdx != null ? afterColIdx + 1 : (rows[0]?.children.length || 0);
rows.forEach((row, rowIdx) => {
const isHeader = row.parentElement.tagName === "THEAD";
const cell = document.createElement(isHeader ? "th" : "td");
const html = _tableColClipboard.cells[rowIdx];
cell.innerHTML = (html != null && html !== "") ? html : (isHeader ? t("table.header") : "&nbsp;");
const refCell = row.children[insertIdx];
if (refCell) {
row.insertBefore(cell, refCell);
} else {
row.appendChild(cell);
}
});
}

function deleteTable(table) {
const wrapper = table.closest(".table-wrapper");
const target = wrapper || table;
Expand Down Expand Up @@ -858,6 +919,10 @@ function showHandleMenu(anchor, type, ctx, contentEl, wrapper, clickX) {
{ label: t("table.insert_row_above"), action: () => { flushSnapshot(contentEl); addTableRow(ctx.table, null, ctx.tr); dispatchInputEvent(contentEl); } },
{ label: t("table.insert_row_below"), action: () => { flushSnapshot(contentEl); addTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } },
{ divider: true },
{ label: t("table.copy_row"), action: () => { copyTableRow(ctx.tr); } },
{ label: t("table.cut_row"), disabled: ctx.isHeader, action: () => { flushSnapshot(contentEl); copyTableRow(ctx.tr); deleteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } },
...(_tableRowClipboard ? [{ label: t("table.paste_row"), action: () => { flushSnapshot(contentEl); pasteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } }] : []),
{ divider: true },
{ label: t("table.delete_row"), destructive: true, disabled: ctx.isHeader, action: () => { flushSnapshot(contentEl); deleteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } },
{ divider: true },
{ label: t("table.delete_table"), destructive: true, action: () => { flushSnapshot(contentEl); deleteTable(ctx.table); dispatchInputEvent(contentEl); } }
Expand All @@ -867,6 +932,10 @@ function showHandleMenu(anchor, type, ctx, contentEl, wrapper, clickX) {
{ label: t("table.insert_col_left"), action: () => { flushSnapshot(contentEl); addTableColumn(ctx.table, ctx.colIdx - 1); dispatchInputEvent(contentEl); } },
{ label: t("table.insert_col_right"), action: () => { flushSnapshot(contentEl); addTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
{ divider: true },
{ label: t("table.copy_col"), action: () => { copyTableColumn(ctx.table, ctx.colIdx); } },
{ label: t("table.cut_col"), action: () => { flushSnapshot(contentEl); copyTableColumn(ctx.table, ctx.colIdx); deleteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
...(_tableColClipboard ? [{ label: t("table.paste_col"), action: () => { flushSnapshot(contentEl); pasteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } }] : []),
{ divider: true },
{ label: t("table.delete_col"), destructive: true, action: () => { flushSnapshot(contentEl); deleteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
{ divider: true },
{ label: t("table.delete_table"), destructive: true, action: () => { flushSnapshot(contentEl); deleteTable(ctx.table); dispatchInputEvent(contentEl); } }
Expand Down Expand Up @@ -1091,6 +1160,14 @@ function showTableContextMenu(x, y, ctx, contentEl) {
{ label: t("table.add_col_left"), action: () => { flushSnapshot(contentEl); addTableColumn(ctx.table, ctx.colIdx - 1); dispatchInputEvent(contentEl); } },
{ label: t("table.add_col_right"), action: () => { flushSnapshot(contentEl); addTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
{ divider: true },
{ label: t("table.copy_row"), action: () => { copyTableRow(ctx.tr); } },
{ label: t("table.cut_row"), action: () => { flushSnapshot(contentEl); copyTableRow(ctx.tr); deleteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } },
...(_tableRowClipboard ? [{ label: t("table.paste_row"), action: () => { flushSnapshot(contentEl); pasteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } }] : []),
{ divider: true },
{ label: t("table.copy_col"), action: () => { copyTableColumn(ctx.table, ctx.colIdx); } },
{ label: t("table.cut_col"), action: () => { flushSnapshot(contentEl); copyTableColumn(ctx.table, ctx.colIdx); deleteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
...(_tableColClipboard ? [{ label: t("table.paste_col"), action: () => { flushSnapshot(contentEl); pasteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } }] : []),
{ divider: true },
{ label: t("table.delete_row"), destructive: true, action: () => { flushSnapshot(contentEl); deleteTableRow(ctx.table, ctx.tr); dispatchInputEvent(contentEl); } },
{ label: t("table.delete_col"), destructive: true, action: () => { flushSnapshot(contentEl); deleteTableColumn(ctx.table, ctx.colIdx); dispatchInputEvent(contentEl); } },
{ divider: true },
Expand Down
6 changes: 6 additions & 0 deletions src-mdviewer/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@
"add_row_below": "Add row below",
"add_col_left": "Add column left",
"add_col_right": "Add column right",
"copy_row": "Copy row",
"cut_row": "Cut row",
"paste_row": "Paste row below",
"copy_col": "Copy column",
"cut_col": "Cut column",
"paste_col": "Paste column right",
"delete_table": "Delete table"
},
"dialog": {
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/default/Git/src/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ define(function (require, exports) {
var $panelHtml = $(panelHtml);
$panelHtml.find(".git-available, .git-not-available").hide();

gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE, {iconClass: "fa-brands fa-git-alt"});
gitPanel = WorkspaceManager.createBottomPanel("main-git.panel", $panelHtml, 100, Strings.GIT_PANEL_TITLE, {iconSvg: "styles/images/panel-icon-git.svg"});
$gitPanel = gitPanel.$panel;
const resizeObserver = new ResizeObserver(_panelResized);
resizeObserver.observe($gitPanel[0]);
Expand Down
2 changes: 1 addition & 1 deletion src/extensionsIntegrated/CustomSnippets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ define(function (require, exports, module) {
*/
function _createPanel() {
customSnippetsPanel = WorkspaceManager.createBottomPanel(PANEL_ID, $snippetsPanel, PANEL_MIN_SIZE,
Strings.CUSTOM_SNIPPETS_PANEL_TITLE, {iconClass: "fa-solid fa-code"});
Strings.CUSTOM_SNIPPETS_PANEL_TITLE, {iconSvg: "styles/images/panel-icon-snippets.svg"});
UIHelper.init(customSnippetsPanel);
customSnippetsPanel.show();

Expand Down
2 changes: 1 addition & 1 deletion src/extensionsIntegrated/DisplayShortcuts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ define(function (require, exports, module) {
// AppInit.htmlReady() has already executed before extensions are loaded
// so, for now, we need to call this ourself
panel = WorkspaceManager.createBottomPanel(TOGGLE_SHORTCUTS_ID, $(s), 300,
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE, {iconClass: "fa-solid fa-keyboard"});
Strings.KEYBOARD_SHORTCUT_PANEL_TITLE, {iconSvg: "styles/images/panel-icon-shortcuts.svg"});
panel.hide();

$shortcutsPanel = $("#shortcuts-panel");
Expand Down
2 changes: 1 addition & 1 deletion src/extensionsIntegrated/Terminal/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ define(function (require, exports, module) {
};

$panel = $(Mustache.render(panelHTML, templateVars));
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE, undefined, {iconClass: "fa-solid fa-terminal"});
panel = WorkspaceManager.createBottomPanel(PANEL_ID, $panel, PANEL_MIN_SIZE, undefined, {iconSvg: "styles/images/panel-icon-terminal.svg"});

// Override focus() so Shift+Escape can transfer focus to the terminal
panel.focus = function () {
Expand Down
2 changes: 1 addition & 1 deletion src/language/CodeInspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ define(function (require, exports, module) {
Editor.registerGutter(CODE_INSPECTION_GUTTER, CODE_INSPECTION_GUTTER_PRIORITY);
// Create bottom panel to list error details
var panelHtml = Mustache.render(PanelTemplate, Strings);
problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS, {iconClass: "fa-solid fa-triangle-exclamation"});
problemsPanel = WorkspaceManager.createBottomPanel("errors", $(panelHtml), 100, Strings.CMD_VIEW_TOGGLE_PROBLEMS, {iconSvg: "styles/images/panel-icon-problems.svg"});
$problemsPanel = $("#problems-panel");
$fixAllBtn = $problemsPanel.find(".problems-fix-all-btn");
$fixAllBtn.click(()=>{
Expand Down
2 changes: 1 addition & 1 deletion src/search/SearchResultsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ define(function (require, exports, module) {
const self = this;
let panelHtml = Mustache.render(searchPanelTemplate, {panelID: panelID});

this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title, {iconClass: "fa-solid fa-magnifying-glass"});
this._panel = WorkspaceManager.createBottomPanel(panelName, $(panelHtml), 100, title, {iconSvg: "styles/images/panel-icon-search.svg"});
this._$summary = this._panel.$panel.find(".title");
this._$table = this._panel.$panel.find(".table-container");
this._$previewEditor = this._panel.$panel.find(".search-editor-preview");
Expand Down
72 changes: 51 additions & 21 deletions src/styles/Extn-BottomPanelTabs.less
Original file line number Diff line number Diff line change
Expand Up @@ -144,31 +144,36 @@
}
}

/* Tab icon: hidden by default, shown when tabs are collapsed */
.bottom-panel-tab-icon {
display: none;
font-size: 1rem;
/* SVG icons rendered as masks so they inherit currentColor (works with light/dark themes).
The mask-image URL is set inline via JS (PanelView.js, DefaultPanelView.js). */
.panel-titlebar-icon {
display: inline-block;
width: 1rem;
text-align: center;
height: 1rem;
vertical-align: middle;
background-color: currentColor;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: contain;
mask-size: contain;
pointer-events: none;
}

/* Override any FA class specificity (e.g. fa-brands sets font-size: 1.2em) */
i.panel-titlebar-icon.panel-titlebar-icon {
font-size: 1rem;
}

img.panel-titlebar-icon {
width: 1rem;
height: 1rem;
vertical-align: middle;
/* Tab icon: hidden by default, shown when tabs are collapsed */
.bottom-panel-tab-icon {
display: none;
}

.default-panel-btn i.panel-titlebar-icon {
font-size: 20px;
/* Default panel (Tools) tab: always show the icon alongside the title,
in both expanded and collapsed modes, active or inactive. */
.bottom-panel-tab.bottom-panel-tab-default .bottom-panel-tab-icon {
display: inline-flex;
margin-right: 0.4rem;
}

.default-panel-btn img.panel-titlebar-icon {
.default-panel-btn .panel-titlebar-icon {
width: 20px;
height: 20px;
}
Expand Down Expand Up @@ -229,9 +234,27 @@
.bottom-panel-tab-close-btn {
margin-left: 0.8rem;
}
/* Only show close button on the active tab to prevent accidental clicks */
/* Fix all collapsed tabs to the same width so the UI doesn't shake
when switching active tab. The width matches the active tab's
natural content (icon + close button) using fixed px since the
icon has a fixed size. The Tools button (.bottom-panel-add-btn)
is a separate element and keeps its natural "Tools" text width. */
.bottom-panel-tab {

Check warning on line 242 in src/styles/Extn-BottomPanelTabs.less

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected duplicate selector ".bottom-panel-tab", first used at line 231

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ14uDW_UM17xPqbM19F&open=AZ14uDW_UM17xPqbM19F&pullRequest=2808
min-width: 63px;
box-sizing: border-box;
justify-content: center;
}
.bottom-panel-tab:not(.active) .bottom-panel-tab-close-btn {
visibility: hidden;
display: none;
}
/* Default panel (Tools) tab: keep natural width and show its title
even in collapsed mode (other tabs collapse to icon-only). */
.bottom-panel-tab.bottom-panel-tab-default {
min-width: auto;
justify-content: flex-start;
}
.bottom-panel-tab.bottom-panel-tab-default .bottom-panel-tab-title {
display: inline;
}
}

Expand Down Expand Up @@ -285,17 +308,24 @@
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
padding: 0 10px;
height: 2rem;
min-width: 70px;
line-height: 2rem;
overflow: hidden;
cursor: pointer;
color: #888;
font-size: 0.82rem;
flex: 0 0 auto;
white-space: nowrap;
user-select: none;
-webkit-user-drag: none;
transition: color 0.12s ease, background-color 0.12s ease;

img {
-webkit-user-drag: none;
pointer-events: none;
}

.dark & {
color: #777;
}
Expand Down
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-default.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-git.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-problems.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-shortcuts.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/styles/images/panel-icon-snippets.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading