Merge pull request #35245 from sokumon/form-sidebar
feat: init form sidebar redesign
This commit is contained in:
commit
45c0696c57
17 changed files with 416 additions and 270 deletions
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() }}
|
||||
{% } %}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue