Merge pull request #35245 from sokumon/form-sidebar

feat: init form sidebar redesign
This commit is contained in:
Ejaaz Khan 2025-12-18 17:56:46 +05:30 committed by GitHub
commit 45c0696c57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 416 additions and 270 deletions

View file

@ -65,7 +65,7 @@ context("Data Control", () => {
.should("have.class", "reqd");
//Checking if the status is "Not Saved" initially
cy.get(".indicator-pill").should("have.text", "Not Saved");
cy.get(".page-head-content .indicator-pill").should("have.text", "Not Saved");
//Inputting data in the field
cy.fill_field("name1", "@@###", "Data");

View file

@ -76,6 +76,7 @@
"width",
"max_height",
"columns",
"icon",
"column_break_22",
"description",
"documentation_url",
@ -626,6 +627,12 @@
"fieldtype": "Select",
"label": "Button Color",
"options": "\nDefault\nPrimary\nInfo\nSuccess\nWarning\nDanger"
},
{
"depends_on": "eval: doc.type == \"Tab Break\"",
"fieldname": "icon",
"fieldtype": "Icon",
"label": "Icon"
}
],
"grid_page_length": 50,
@ -633,7 +640,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-12-01 06:56:29.335491",
"modified": "2025-12-15 11:28:13.492477",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",

View file

@ -182,7 +182,7 @@ def get_milestones(doctype, name):
def get_attachments(dt, dn):
return frappe.get_all(
"File",
fields=["name", "file_name", "file_url", "is_private"],
fields=["name", "file_name", "file_url", "is_private", "file_type", "file_size"],
filters={"attached_to_name": str(dn), "attached_to_doctype": dt},
)

View file

@ -312,8 +312,8 @@ frappe.get_data_pill = (
style = "";
if (colored) {
color = frappe.get_palette(label);
style = `background-color: var(${color[0]}); color: var(${color[1]})`;
}
style = `background-color: var(${color[0]}); color: var(${color[1]})`;
let data_pill_wrapper = $(`
<button class="data-pill btn" style="${style}">
<div class="flex align-center ellipsis">

View file

@ -13,15 +13,6 @@ frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.Cont
<span class="comment-title">${__("Comments")}</span>
<span class="comment-count"></span>
</div>
<div class="form-stats-likes">
<span class="liked-by like-action d-flex align-items-center">
<svg class="icon icon-sm like-icon">
<use href="#icon-heart"></use>
</svg>
<span class="like-count ml-2"></span>
</span>
</div>
</div>
<div class="comment-input-container">
${frappe.avatar(frappe.session.user, "avatar-medium")}

View file

@ -7,7 +7,6 @@ frappe.ui.form.Footer = class FormFooter {
this.make();
this.make_comment_box();
this.make_timeline();
this.make_like();
// render-complete
$(this.frm.wrapper).on("render_complete", () => {
this.refresh();
@ -65,40 +64,10 @@ frappe.ui.form.Footer = class FormFooter {
this.frm.timeline.refresh();
}
this.refresh_comments_count();
this.refresh_like();
}
refresh_comments_count() {
let count = (this.frm.get_docinfo().comments || []).length;
this.wrapper.find(".comment-count")?.html(count ? `(${count})` : "");
}
make_like() {
this.like_wrapper = this.wrapper.find(".liked-by");
this.like_icon = this.wrapper.find(".liked-by .like-icon");
this.like_count = this.wrapper.find(".liked-by .like-count");
frappe.ui.setup_like_popover(this.wrapper.find(".form-stats-likes"), ".like-icon");
this.like_icon.on("click", () => {
frappe.ui.toggle_like(this.like_wrapper, this.frm.doctype, this.frm.doc.name, () => {
this.refresh_like();
});
});
}
refresh_like() {
if (!this.like_icon) {
return;
}
this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by);
const liked = frappe.ui.is_liked(this.frm.doc);
this.like_wrapper
.toggleClass("not-liked", !liked)
.toggleClass("liked", liked)
.attr("data-doctype", this.frm.doctype)
.attr("data-name", this.frm.doc.name);
this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length);
}
};

View file

@ -150,6 +150,7 @@ frappe.ui.form.Layout = class Layout {
if (this.is_tabbed_layout()) {
// add a tab without `fieldname` to avoid conflicts
this.add_default_tabs(fields);
let default_tab = {
label: __("Details"),
fieldtype: "Tab Break",
@ -201,7 +202,7 @@ frappe.ui.form.Layout = class Layout {
(this.fields[0] && this.fields[0].fieldtype != "Section Break") || !this.fields.length
);
}
add_default_tabs() {}
no_opening_tab() {
return (this.fields[1] && this.fields[1].fieldtype != "Tab Break") || !this.fields.length;
}

View file

@ -63,8 +63,8 @@ export default class Section {
make_head() {
this.head = $(`
<div class="section-head">
<span class="collapse-indicator mb-1"></span>
${__(this.df.label, null, this.df.parent)}
<span class="ml-2 collapse-indicator mb-1"></span>
</div>
`);
@ -135,7 +135,7 @@ export default class Section {
}
set_icon(hide) {
let indicator_icon = hide ? "es-line-down" : "es-line-up";
let indicator_icon = hide ? "chevron-right" : "es-line-down";
this.indicator && this.indicator.html(frappe.utils.icon(indicator_icon, "sm", "mb-1"));
}

View file

@ -30,14 +30,17 @@ frappe.ui.form.AssignTo = class AssignTo {
return;
}
let avatar_group = frappe.avatar_group(assigned_users, 5, {
align: "left",
overlap: true,
let assignment_group = new frappe.ui.form.AssignmentClass({
assignments: assigned_users,
assignment_details: assignments,
frm: this.frm,
});
assignment_group = assignment_group.make_assignment_section();
assignments_wrapper.show();
assignments_wrapper.append(avatar_group);
avatar_group.click(() => {
assignments_wrapper.append(assignment_group);
assignment_group.find(".view-all-assignment").click(() => {
new frappe.ui.form.AssignmentDialog({
assignments: assigned_users,
assignment_details: assignments,
@ -242,11 +245,115 @@ frappe.ui.form.AssignToDialog = class AssignToDialog {
}
};
frappe.ui.form.AssignmentDialog = class {
frappe.ui.form.AssignmentClass = class AssignmentClass {
constructor(opts) {
this.frm = opts.frm;
this.assignments = opts.assignments;
this.assignment_details = opts.assignment_details;
}
make_assignment_section() {
let $assignment_html = $("<div></div>");
let count = 0;
for (const assignment of this.assignments) {
if (count === 3) {
$assignment_html.append(
$(
'<a href="javascript:void(0)" class="text-muted view-all-assignment">View All</div>'
)
);
break;
}
$assignment_html.append(this.get_assignment_row(assignment));
count++;
}
return $assignment_html;
}
get_assignment_row(assignment, in_dialogue = false) {
const row = $(`
<div class="dialog-assignment-row" data-user="${assignment}">
<div class="assignee">
${frappe.avatar(assignment)}
${frappe.user.full_name(assignment)}
</div>
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
</div>
</div>
`);
const btn_group = row.find(".btn-group");
if (assignment === frappe.session.user) {
btn_group.append(`
<button type="button" class="btn complete-btn" title="${__("Done")}">
${frappe.utils.icon("tick", "xs")}
</button>
`);
btn_group.find(".complete-btn").click(() => {
this.close_assignment(assignment).then((assignments) => {
row.remove();
this.render(assignments);
});
});
if (in_dialogue) {
const html = this.assignment_details
.filter((x) => x.owner === assignment && strip_html(x.description))
.map((x) => x.description)
.join("<hr>");
if (html) {
let description = $(
"<div class='small text-muted' style='margin-left: 45px;max-height: 100px;'></div>"
).html(html);
row.find(".assignee").append(description);
}
}
}
if (assignment === frappe.session.user || this.frm.perm[0].write) {
btn_group.append(`
<button type="button" class="btn remove-btn" title="${__("Cancel")}">
${frappe.utils.icon("x")}
</button>
`);
btn_group.find(".remove-btn").click(() => {
this.remove_assignment(assignment).then((assignments) => {
row.remove();
this.render(assignments);
});
});
}
return row;
}
remove_assignment(assignment) {
return frappe.xcall("frappe.desk.form.assign_to.remove", {
doctype: this.frm.doctype,
name: this.frm.docname,
assign_to: assignment,
});
}
close_assignment(assignment) {
return frappe.xcall("frappe.desk.form.assign_to.close", {
doctype: this.frm.doctype,
name: this.frm.docname,
assign_to: assignment,
});
}
render(assignments) {
this.frm && this.frm.assign_to.render(assignments);
}
};
frappe.ui.form.AssignmentDialog = class extends frappe.ui.form.AssignmentClass {
constructor(opts) {
super(opts);
this.make();
}
@ -294,9 +401,6 @@ frappe.ui.form.AssignmentDialog = class {
});
this.dialog.show();
}
render(assignments) {
this.frm && this.frm.assign_to.render(assignments);
}
add_assignment(assignment) {
return frappe
.xcall("frappe.desk.form.assign_to.add", {
@ -309,79 +413,10 @@ frappe.ui.form.AssignmentDialog = class {
this.render(assignments);
});
}
remove_assignment(assignment) {
return frappe.xcall("frappe.desk.form.assign_to.remove", {
doctype: this.frm.doctype,
name: this.frm.docname,
assign_to: assignment,
});
}
close_assignment(assignment) {
return frappe.xcall("frappe.desk.form.assign_to.close", {
doctype: this.frm.doctype,
name: this.frm.docname,
assign_to: assignment,
});
}
update_assignment(assignment) {
const in_the_list = this.assignment_list.find(`[data-user="${assignment}"]`).length;
if (!in_the_list) {
this.assignment_list.append(this.get_assignment_row(assignment));
this.assignment_list.append(this.get_assignment_row(assignment, true));
}
}
get_assignment_row(assignment) {
const row = $(`
<div class="dialog-assignment-row" data-user="${assignment}">
<div class="assignee">
${frappe.avatar(assignment)}
${frappe.user.full_name(assignment)}
</div>
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
</div>
</div>
`);
const btn_group = row.find(".btn-group");
if (assignment === frappe.session.user) {
btn_group.append(`
<button type="button" class="btn btn-default complete-btn" title="${__("Done")}">
${frappe.utils.icon("tick", "xs")}
</button>
`);
btn_group.find(".complete-btn").click(() => {
this.close_assignment(assignment).then((assignments) => {
row.remove();
this.render(assignments);
});
});
const html = this.assignment_details
.filter((x) => x.owner === assignment && strip_html(x.description))
.map((x) => x.description)
.join("<hr>");
if (html) {
$(
"<div class='small overflow-auto m-1 p-1 flex-grow-1' style='max-height: 100px;'>"
)
.html(html)
.appendTo(row);
}
}
if (assignment === frappe.session.user || this.frm.perm[0].write) {
btn_group.append(`
<button type="button" class="btn btn-default remove-btn" title="${__("Cancel")}">
${frappe.utils.icon("close")}
</button>
`);
btn_group.find(".remove-btn").click(() => {
this.remove_assignment(assignment).then((assignments) => {
row.remove();
this.render(assignments);
});
});
}
return row;
}
};

View file

@ -113,47 +113,68 @@ frappe.ui.form.Attachments = class Attachments {
}
add_attachment(attachment) {
var file_name = attachment.file_name;
var file_url = this.get_file_url(attachment);
var fileid = attachment.name;
if (!file_name) {
file_name = file_url;
}
let file_name = attachment.file_name || this.get_file_url(attachment);
let file_url = this.get_file_url(attachment);
let fileid = attachment.name;
let me = this;
var me = this;
let $attachment_action = $(`<div></div>`);
let file_label = `
<a href="${file_url}" target="_blank" title="${frappe.utils.escape_html(file_name)}"
class="ellipsis attachment-file-label"
>
let $file_label = $(`
<a href="${file_url}" target="_blank" title="${frappe.utils.escape_html(
file_name
)}" class="ellipsis attachment-file-label">
<span>${frappe.utils.xss_sanitise(file_name)}</span>
</a>`;
</a>
`);
$attachment_action.append($file_label);
let remove_action = null;
if (this.can_delete_attachment()) {
remove_action = function (target_id) {
frappe.confirm(__("Are you sure you want to delete the attachment?"), function () {
let target_attachment = me
.get_attachments()
.find((attachment) => attachment.name === target_id);
let to_be_removed = me
.get_attachments()
.filter(
(attachment) => attachment.file_name === target_attachment.file_name
);
to_be_removed.forEach((attachment) => me.remove_attachment(attachment.name));
});
return false;
};
let $delete_attachment = $(`
<button class="btn btn-link attachment-remove-btn float-right">
${frappe.utils.icon("x")}
</button>
`);
$delete_attachment.on("click", () => {
me.delete_attachment(fileid);
});
$attachment_action.append($delete_attachment);
}
const icon = `<a href="/app/file/${fileid}" class="attachment-icon">
${frappe.utils.icon(attachment.is_private ? "es-line-lock" : "es-line-unlock", "sm ml-0")}
</a>`;
const $attachment_meta = $(`
<div class="mt-1">
<a href="/desk/file/${fileid}" class="attachment-icon">
${frappe.utils.icon(attachment.is_private ? "es-line-lock" : "es-line-unlock", "sm ml-0")}
</a>
<span class="file-type">${attachment.file_type}</span>
<span class="file-size">
${frappe.form.formatters.FileSize(attachment.file_size)}
</span>
</div>
`);
$(`<div class="attachment-row"></div>`)
.append(frappe.get_data_pill(file_label, fileid, remove_action, icon))
.insertAfter(this.add_attachment_wrapper);
// Final row
let $row = $('<div class="attachment-row"></div>');
$row.append($attachment_action);
$row.append($attachment_meta);
$row.insertAfter(this.add_attachment_wrapper);
}
delete_attachment(target_id) {
let me = this;
frappe.confirm(__("Are you sure you want to delete the attachment?"), function () {
let target_attachment = me
.get_attachments()
.find((attachment) => attachment.name === target_id);
let to_be_removed = me
.get_attachments()
.filter((attachment) => attachment.file_name === target_attachment.file_name);
to_be_removed.forEach((attachment) => me.remove_attachment(attachment.name));
});
}
can_delete_attachment() {

View file

@ -16,6 +16,7 @@ frappe.ui.form.Sidebar = class {
doctype: this.frm.doctype,
frm: this.frm,
can_write: frappe.model.can_write(this.frm.doctype, this.frm.docname),
image_field: this.frm.meta.image_field ?? false,
});
this.sidebar = $('<div class="form-sidebar overlay-sidebar hidden-xs hidden-sm"></div>')
@ -34,10 +35,27 @@ frappe.ui.form.Sidebar = class {
this.setup_keyboard_shortcuts();
this.show_auto_repeat_status();
frappe.ui.form.setup_user_image_event(this.frm);
this.indicator = $(this.sidebar).find(".sidebar-meta-details .indicator-pill");
this.set_form_indicator();
this.setup_copy_event();
this.make_like();
this.refresh();
}
set_form_indicator() {
let indicator = frappe.get_indicator(this.frm.doc);
if (indicator) {
this.set_indicator(indicator[0], indicator[1]);
}
}
set_indicator(label, color) {
this.clear_indicator().removeClass("hide").html(`<span>${label}</span>`).addClass(color);
}
clear_indicator() {
return this.indicator.addClass("indicator-pill no-indicator-dot whitespace-nowrap hide");
}
setup_keyboard_shortcuts() {
// add assignment shortcut
let assignment_link = this.sidebar.find(".add-assignment");
@ -61,6 +79,45 @@ frappe.ui.form.Sidebar = class {
this.refresh_creation_modified();
frappe.ui.form.set_user_image(this.frm);
}
this.refresh_like();
}
setup_copy_event() {
$(this.sidebar)
.find(".sidebar-meta-details .form-name-copy")
.on("click", (e) => {
frappe.utils.copy_to_clipboard($(e.currentTarget).attr("data-copy"));
});
}
make_like() {
this.like_wrapper = this.sidebar.find(".liked-by");
this.like_icon = this.sidebar.find(".liked-by .like-icon");
this.like_count = this.sidebar.find(".liked-by .like-count");
frappe.ui.setup_like_popover(this.sidebar.find(".form-stats-likes"), ".like-icon");
this.like_icon.on("click", () => {
frappe.ui.toggle_like(this.like_wrapper, this.frm.doctype, this.frm.doc.name, () => {
this.refresh_like();
});
});
}
refresh_like() {
if (!this.like_icon) {
return;
}
this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by);
const liked = frappe.ui.is_liked(this.frm.doc);
this.like_wrapper
.toggleClass("not-liked", !liked)
.toggleClass("liked", liked)
.attr("data-doctype", this.frm.doctype)
.attr("data-name", this.frm.doc.name);
this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length);
}
refresh_web_view_count() {
@ -76,62 +133,28 @@ frappe.ui.form.Sidebar = class {
}
refresh_creation_modified() {
let user_list = [this.frm.doc.owner, this.frm.doc.modified_by];
if (this.frm.doc.owner === this.frm.doc.modified_by) {
user_list = [this.frm.doc.owner];
}
let avatar_group = frappe.avatar_group(user_list, 5, {
align: "left",
overlap: true,
});
this.sidebar.find(".created-modified-section").append(avatar_group);
let creation_message =
get_user_message(
this.frm.doc.owner,
__("You created this", null),
__("{0} created this", [get_user_link(this.frm.doc.owner)])
) +
" · " +
cint(frappe.boot.user.show_absolute_datetime_in_timeline) ||
cint(frappe.boot.sysdefaults.show_absolute_datetime_in_timeline)
? frappe.datetime.str_to_user(this.frm.doc.creation)
: comment_when(this.frm.doc.creation);
let modified_message =
get_user_message(
this.frm.doc.modified_by,
__("You last edited this", null),
__("{0} last edited this", [get_user_link(this.frm.doc.modified_by)])
) +
" · " +
cint(frappe.boot.user.show_absolute_datetime_in_timeline) ||
cint(frappe.boot.sysdefaults.show_absolute_datetime_in_timeline)
? frappe.datetime.str_to_user(this.frm.doc.modified)
: comment_when(this.frm.doc.modified);
if (user_list.length === 1) {
// same user created and edited
avatar_group.find(".avatar").popover({
trigger: "hover",
html: true,
content: creation_message + "<br>" + modified_message,
});
} else {
avatar_group.find(".avatar:first-child").popover({
trigger: "hover",
html: true,
content: creation_message,
});
avatar_group.find(".avatar:last-child").popover({
trigger: "hover",
html: true,
content: modified_message,
});
}
this.sidebar
.find(".modified-by")
.html(
get_user_message(
this.frm.doc.modified_by,
__("You last edited this", null),
__("{0} last edited this", [get_user_link(this.frm.doc.modified_by)])
) +
" · " +
comment_when(this.frm.doc.modified)
);
this.sidebar
.find(".created-by")
.html(
get_user_message(
this.frm.doc.owner,
__("You created this", null),
__("{0} created this", [get_user_link(this.frm.doc.owner)])
) +
" · " +
comment_when(this.frm.doc.creation)
);
}
show_auto_repeat_status() {

View file

@ -1,3 +1,9 @@
const ICON_MAP = {
"More Info": "info",
Dashboard: "layout-dashboard",
Details: "notepad-text",
Connections: "waypoints",
};
export default class Tab {
constructor(layout, df, frm, tab_link_container, tabs_content) {
this.layout = layout;
@ -29,6 +35,7 @@ export default class Tab {
type="button"
role="tab"
aria-controls="${id}">
${frappe.utils.icon(this.df.icon || ICON_MAP[this.label] || "list")}
${__(this.label, null, this.doctype)}
</button>
</li>

View file

@ -1,5 +1,5 @@
<div class="sidebar-section user-actions hidden"></div>
<div class="sidebar-section sidebar-image-section hide">
<div class="flex justify-between sidebar-image-section sidebar-section hide">
<div class="sidebar-image-wrapper">
<img class="sidebar-image">
<div class="sidebar-standard-image">
@ -17,9 +17,27 @@
</div>
{% endif %}
</div>
<div class="form-stats-likes">
<span class="liked-by like-action d-flex align-items-center">
<svg class="icon icon-sm like-icon pointer">
<use href="#icon-heart"></use>
</svg>
<span class="like-count ml-2"></span>
</span>
</div>
</div>
<div class="sidebar-section sidebar-meta-details border-bottom">
<div class="form-details flex {{image_field ? "justify-center" : "justify-between"}}">
<span class="ellipsis mr-3 bold">{%= frm.get_title() %}</span>
<span class="form-name-copy" data-copy="{{frm.get_title()}}">{%= frappe.utils.icon("copy")%}</span>
</div>
<div class="form-name-container mt-2 flex {{image_field ? "justify-center" : "justify-between"}}">
<span class="ellipsis mr-3">{%= frm.doc.name %}</span>
<span class="indicator-pill whitespace-nowrap"></span>
</div>
</div>
{% if frm.meta.beta %}
<div class="sidebar-section">
<div class="sidebar-section border-bottom">
<p><label class="indicator-pill yellow" title="{{ __("This feature is brand new and still experimental") }}">{{ __("Experimental") }}</label></p>
<div><a class="small text-muted" href="https://github.com/frappe/{{ frappe.boot.module_app[frappe.scrub(frm.meta.module)] }}/issues/new"
target="_blank">
@ -27,7 +45,7 @@
</div>
{% endif %}
<div class="sidebar-section sidebar-rating hide">
<div class="sidebar-section sidebar-rating hide border-bottom">
<div style="position: relative;">
<a class="strong badge-hover">
<span>{%= __("Feedback") %}</span>
@ -35,18 +53,19 @@
</div>
<div class="rating-icons"></div>
</div>
<div class="sidebar-section hidden">
<div class="sidebar-section hidden border-bottom">
{% if (frappe.help.has_help(doctype)) { %}
<div>
<a class="help-link list-link" data-doctype="{{ doctype }}">{{ __("Help") }}</a>
</div>
{% } %}
</div>
<div class="sidebar-section form-assignments">
<div class="sidebar-section form-assignments border-bottom">
<div>
<span class="form-sidebar-items">
<span class="add-assignment-label">
<span class="ellipsis">{%= __("Assigned To") %}</span>
<span class="add-assignment-label form-sidebar-label">
{%= frappe.utils.icon("users") %}
<span class="ellipsis">{%= __("Assign") %}</span>
</span>
<button class="add-assignment-btn btn btn-link icon-btn">
<svg class="es-icon icon-sm"><use href="#es-line-add"></use></svg>
@ -55,11 +74,12 @@
<div class="assignments"></div>
</div>
</div>
<div class="sidebar-section form-attachments">
<div class="sidebar-section form-attachments border-bottom">
<div class="attachments-actions">
<span class="form-sidebar-items">
<span>
<a class="pill-label ellipsis explore-link">
<a class="pill-label ellipsis form-sidebar-label explore-link">
{%= frappe.utils.icon("paperclip") %}
{%= __("Attachments") %}
</a>
</span>
@ -74,10 +94,11 @@
</span>
</a>
</div>
<div class="sidebar-section form-tags">
<div class="sidebar-section form-tags border-bottom">
<div>
<span class="form-sidebar-items">
<div>
<div class="form-sidebar-label">
{%= frappe.utils.icon("tag") %}
<span class="tags-label ellipsis">{%= __("Tags") %}</span>
</div>
</span>
@ -86,7 +107,8 @@
<div class="sidebar-section form-shared">
<div>
<span class="form-sidebar-items">
<span class="share-label">
<span class="share-label form-sidebar-label">
{%= frappe.utils.icon("share-2") %}
<span class="ellipsis">{%= __("Share") %}</span>
</span>
<button class="share-doc-btn btn btn-link icon-btn">
@ -113,11 +135,14 @@
<div class="sidebar-section hidden">
<a><li class="indicator blue auto-repeat-status" style="display: none;"></li></a>
</div>
<div class="sidebar-section text-muted border-top mt-3 pt-3">
<div class="pageview-count hidden"></div>
<div class="created-modified-section">
</div>
<div class="sidebar-section text-muted border-top pt-3">
<ul class="list-unstyled sidebar-menu text-muted">
<li class="pageview-count"></li>
<li class="modified-by"></li>
<li class="created-by"></li>
</ul>
</div>
{% if(frappe.get_form_sidebar_extension) { %}
{{ frappe.get_form_sidebar_extension() }}
{% } %}

View file

@ -87,7 +87,7 @@ frappe.ui.setup_like_popover = ($parent, selector) => {
let liked_by_list = $(`<ul class="list-unstyled"></ul>`);
// to show social profile of the user
let link_base = "/app/user-profile/";
let link_base = "/desk/user/";
liked_by.forEach((user) => {
// append user list item

View file

@ -29,6 +29,7 @@
.form-section,
.form-dashboard-section {
padding: var(--padding-md);
.form-section-description {
max-width: var(--page-max-width);
margin: auto;
@ -39,15 +40,14 @@
.section-head {
@extend .head-title;
@include get_textstyle("lg", "semibold");
@include get_textstyle("md", "medium");
color: var(--text-color);
width: 100%;
padding: var(--padding-sm) var(--padding-md);
padding: var(--padding-md);
cursor: default;
.collapse-indicator {
color: var(--text-muted);
margin-left: 10px;
position: relative;
padding: 0px;
&:focus-visible {
@ -78,7 +78,17 @@
}
}
}
.section-head {
display: flex;
align-items: center;
gap: 8px;
}
.section-head::after {
content: "";
flex: 1;
border-bottom: 1px solid var(--border-color);
}
.section-head,
.section-body {
margin: auto !important;
@ -116,7 +126,6 @@
border: 0 !important;
}
.form-section.card-section,
.form-dashboard-section {
border-bottom: 1px solid var(--border-color);
padding: var(--padding-md) 0;
@ -485,15 +494,18 @@
white-space: nowrap;
@include get_textstyle("base", "regular");
.nav-link {
color: var(--text-muted);
color: var(--text-light);
padding: 10px 0;
margin: 0 var(--margin-md);
background-color: var(--card-bg);
border: 0;
display: flex;
align-items: center;
gap: 6px;
&.active {
font-weight: 600;
font-weight: 400;
border-bottom: 1px solid var(--text-color);
color: var(--text-color);
color: var(--text-neutral);
padding-bottom: 9px;
}
&:focus-visible {

View file

@ -1,11 +1,14 @@
:root {
--form-sidebar-width: 277px;
--form-sidebar-image-width: 80px;
}
.underline-hover {
display: initial;
}
// form sidebar
.form-sidebar {
padding-top: var(--padding-sm);
.sidebar-section {
padding: var(--padding-md);
.form-sidebar-items {
display: flex;
width: 100%;
@ -62,30 +65,31 @@
}
.sidebar-image-section {
width: min(100%, var(--sidebar-width));
cursor: pointer;
padding-bottom: 0px;
.sidebar-image {
height: auto;
max-height: var(--sidebar-width);
object-fit: cover;
cursor: pointer;
}
.standard-image {
font-size: 52px;
font-size: 40px;
border-radius: var(--border-radius-lg);
}
.sidebar-image-wrapper {
position: relative;
width: fit-content;
height: fit-content;
margin: 0 auto;
}
.sidebar-image,
.sidebar-standard-image {
transition: opacity 0.3s;
width: var(--form-sidebar-image-width);
height: var(--form-sidebar-image-width);
border-radius: var(--border-radius-lg);
border: 1px solid var(--border-color);
width: 100%;
}
.sidebar-image-wrapper:hover {
@ -114,22 +118,42 @@
padding: 50% 0;
}
}
.form-details {
display: flex;
align-items: center;
}
}
.sidebar-meta-details {
.form-name-copy {
cursor: pointer;
}
}
}
.form-sidebar-label {
display: flex;
align-items: center;
gap: 8px;
}
body[data-route^="Form"] {
.layout-side-section .form-sidebar {
width: var(--form-sidebar-width);
}
}
.layout-side-section {
@include get_textstyle("sm", "regular");
// padding-right: 30px;
min-width: var(--sidebar-width);
min-width: var(--form-sidebar-width);
border-left: 1px solid var(--border-color);
@media (max-width: map-get($grid-breakpoints, "xl")) {
min-width: calc(38vw - var(--sidebar-width));
}
.sidebar-section {
padding: var(--padding-sm) var(--padding-md);
}
// .sidebar-section {
// border-bottom: 1px solid var(--border-color);
// }
.sidebar-section:last-child {
border-bottom: none;
@ -270,19 +294,42 @@
}
}
.attachment-row {
.data-pill {
background-color: unset;
box-shadow: none;
padding: 0 var(--padding-xs) 0 var(--padding-md) !important;
.attachment-file-label {
width: 94px;
display: block;
margin-left: var(--margin-xs);
text-align: left;
margin: var(--margin-sm) 0;
padding: var(--padding-sm);
.attachment-file-label {
max-width: 150px;
margin-left: var(--margin-xs);
text-align: left;
}
.attachment-icon {
line-height: 0;
}
.file-type,
.file-size {
color: var(--text-light);
}
.attachment-remove-btn {
display: none;
}
.file-type {
position: relative;
margin-right: 6px;
&::after {
content: ".";
width: 5px;
height: 2px;
display: inline-block;
position: absolute;
bottom: 32px;
font-size: 25px;
right: -7px;
}
.attachment-icon {
line-height: 0;
}
&:hover {
background-color: var(--subtle-fg);
border-radius: var(--border-radius-sm);
.attachment-remove-btn {
display: block;
}
}
}
@ -295,10 +342,6 @@
display: inline-flex;
}
.explore-link {
margin-top: var(--margin-sm);
}
.attachments-actions {
gap: 6px;
}
@ -314,6 +357,12 @@
color: var(--text-light);
}
.form-assignments,
.form-attachments,
.form-tags,
.form-shared {
padding: 8px;
}
.form-assignments,
.form-shared {
.assignments,
@ -324,7 +373,6 @@
.add-assignment-btn,
.add-attachment-btn,
.add-review-btn,
.shares,
.add-tags-btn,
.share-doc-btn,
.followed-by {

View file

@ -91,6 +91,13 @@
width: 80%;
}
body[data-route^="Form"] {
.layout-main-section-wrapper {
width: calc(100% - var(--form-sidebar-width));
flex: calc(100% - var(--form-sidebar-width));
}
}
.layout-main-section.frappe-card {
overflow: hidden;
@include card($padding: 0px);