* [feature] help search modal * add symlinks for docs assets * update help links * [ui] fix help dropdown in mobile view * replaced {index} tags in help content, api for fetching help content * help links open in help modal * moved help content logic to pyversion-14
@@ -57,14 +57,19 @@ def make_asset_dirs(make_copy=False): | |||||
# symlink app/public > assets/app | # symlink app/public > assets/app | ||||
for app_name in frappe.get_all_apps(True): | for app_name in frappe.get_all_apps(True): | ||||
pymodule = frappe.get_module(app_name) | pymodule = frappe.get_module(app_name) | ||||
source = os.path.join(os.path.abspath(os.path.dirname(pymodule.__file__)), 'public') | |||||
target = os.path.join(assets_path, app_name) | |||||
if not os.path.exists(target) and os.path.exists(source): | |||||
if make_copy: | |||||
shutil.copytree(os.path.abspath(source), target) | |||||
else: | |||||
os.symlink(os.path.abspath(source), target) | |||||
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__)) | |||||
symlinks = [] | |||||
symlinks.append([os.path.join(app_base_path, 'public'), os.path.join(assets_path, app_name)]) | |||||
symlinks.append([os.path.join(app_base_path, 'docs'), os.path.join(assets_path, app_name + '_docs')]) | |||||
for source, target in symlinks: | |||||
source = os.path.abspath(source) | |||||
if not os.path.exists(target) and os.path.exists(source): | |||||
if make_copy: | |||||
shutil.copytree(source, target) | |||||
else: | |||||
os.symlink(source, target) | |||||
def build(no_compress=False, verbose=False): | def build(no_compress=False, verbose=False): | ||||
assets_path = os.path.join(frappe.local.sites_path, "assets") | assets_path = os.path.join(frappe.local.sites_path, "assets") | ||||
@@ -1,6 +1,6 @@ | |||||
Often while building javascript interfaces, there is a need to render DOM as an HTML template. Frappe Framework uses John Resig's Microtemplate script to render HTML templates in the Desk application. | Often while building javascript interfaces, there is a need to render DOM as an HTML template. Frappe Framework uses John Resig's Microtemplate script to render HTML templates in the Desk application. | ||||
> Note 1: In Frappe we use the Jinja-like `{%` tags to embed code rather than the standard `<%` | |||||
> Note 1: In Frappe we use the Jinja-like `{% raw %}{%{% endraw %}` tags to embed code rather than the standard `<%` | |||||
> Note 2: Never use single quotes `'` inside the HTML template. | > Note 2: Never use single quotes `'` inside the HTML template. | ||||
@@ -65,6 +65,6 @@ Here is what the report looks like: | |||||
1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded. | 1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded. | ||||
1. You can use all global functions like `fmt_money` and dateutil. | 1. You can use all global functions like `fmt_money` and dateutil. | ||||
1. Translatable strings should be written as `__("text")` | 1. Translatable strings should be written as `__("text")` | ||||
1. You can create modules and import using `{% include "templates/includes/formats/common_format" %}` | |||||
1. You can create modules and import using `{% raw %}{% include "templates/includes/formats/common_format" %}{% endraw %}` | |||||
<!-- markdown --> | <!-- markdown --> |
@@ -1,6 +1,6 @@ | |||||
Often while building javascript interfaces, there is a need to render DOM as an HTML template. Frappe Framework uses John Resig's Microtemplate script to render HTML templates in the Desk application. | Often while building javascript interfaces, there is a need to render DOM as an HTML template. Frappe Framework uses John Resig's Microtemplate script to render HTML templates in the Desk application. | ||||
> Note 1: In Frappe we use the Jinja-like `{%` tags to embed code rather than the standard `<%` | |||||
> Note 1: In Frappe we use the Jinja-like `{% raw %}{%{% endraw %}` tags to embed code rather than the standard `<%` | |||||
> Note 2: Never use single quotes `'` inside the HTML template. | > Note 2: Never use single quotes `'` inside the HTML template. | ||||
@@ -65,6 +65,6 @@ Here is what the report looks like: | |||||
1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded. | 1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded. | ||||
1. You can use all global functions like `fmt_money` and dateutil. | 1. You can use all global functions like `fmt_money` and dateutil. | ||||
1. Translatable strings should be written as `__("text")` | 1. Translatable strings should be written as `__("text")` | ||||
1. You can create modules and import using `{% include "templates/includes/formats/common_format" %}` | |||||
1. You can create modules and import using `{% raw %}{% include "templates/includes/formats/common_format" %}{% endraw %}` | |||||
<!-- markdown --> | <!-- markdown --> |
@@ -2,5 +2,5 @@ | |||||
Select your language | Select your language | ||||
1. [English](en) | |||||
1. [Français](fr) | |||||
1. [English]({{docs_base_url}}/user/en) | |||||
1. [Français]({{docs_base_url}}/user/fr) |
@@ -115,6 +115,7 @@ | |||||
"public/js/frappe/misc/datetime.js", | "public/js/frappe/misc/datetime.js", | ||||
"public/js/frappe/misc/number_format.js", | "public/js/frappe/misc/number_format.js", | ||||
"public/js/frappe/misc/help.js", | "public/js/frappe/misc/help.js", | ||||
"public/js/frappe/misc/help_links.js", | |||||
"public/js/frappe/ui/upload.html", | "public/js/frappe/ui/upload.html", | ||||
"public/js/frappe/upload.js", | "public/js/frappe/upload.js", | ||||
@@ -102,6 +102,12 @@ kbd { | |||||
font-size: 12px; | font-size: 12px; | ||||
border-radius: 0px 0px 4px 4px; | border-radius: 0px 0px 4px 4px; | ||||
} | } | ||||
.dropdown-menu .dropdown-header { | |||||
padding: 3px 14px; | |||||
font-size: 11px; | |||||
font-weight: 200; | |||||
padding-top: 12px; | |||||
} | |||||
.dropdown-menu .divider { | .dropdown-menu .divider { | ||||
margin: 0px; | margin: 0px; | ||||
} | } | ||||
@@ -102,6 +102,12 @@ kbd { | |||||
font-size: 12px; | font-size: 12px; | ||||
border-radius: 0px 0px 4px 4px; | border-radius: 0px 0px 4px 4px; | ||||
} | } | ||||
.dropdown-menu .dropdown-header { | |||||
padding: 3px 14px; | |||||
font-size: 11px; | |||||
font-weight: 200; | |||||
padding-top: 12px; | |||||
} | |||||
.dropdown-menu .divider { | .dropdown-menu .divider { | ||||
margin: 0px; | margin: 0px; | ||||
} | } | ||||
@@ -582,3 +588,43 @@ fieldset[disabled] .form-control { | |||||
.liked-by-popover li { | .liked-by-popover li { | ||||
margin: 15px 0px; | margin: 15px 0px; | ||||
} | } | ||||
.screenshot { | |||||
border: 1px solid #d1d8dd; | |||||
box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.15); | |||||
margin: 8px 0px; | |||||
max-width: 100%; | |||||
} | |||||
.help-modal a { | |||||
color: #5E64FF; | |||||
} | |||||
.help-modal .modal-dialog { | |||||
width: 768px; | |||||
} | |||||
.help-modal .modal-body { | |||||
padding: 15px 27px; | |||||
} | |||||
.help-modal .parent-link { | |||||
line-height: 4em; | |||||
} | |||||
.help-modal .parent-link:before { | |||||
font-family: 'Octicons'; | |||||
content: '\f0a4'; | |||||
} | |||||
.help-modal .edit-container { | |||||
padding-bottom: 12px; | |||||
} | |||||
@media (max-width: 767px) { | |||||
.help-modal .modal-dialog { | |||||
width: auto; | |||||
} | |||||
.help-modal .modal-content { | |||||
height: auto !important; | |||||
} | |||||
.help-modal iframe { | |||||
height: auto; | |||||
width: 100%; | |||||
} | |||||
} | |||||
.search-result { | |||||
margin-bottom: 24px; | |||||
} |
@@ -29,7 +29,7 @@ | |||||
} | } | ||||
@media (max-width: 991px) { | @media (max-width: 991px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 40% !important; | |||||
width: 35% !important; | |||||
} | } | ||||
.navbar-desk ~ ul > li { | .navbar-desk ~ ul > li { | ||||
float: left; | float: left; | ||||
@@ -48,7 +48,7 @@ | |||||
} | } | ||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 60% !important; | |||||
width: 50% !important; | |||||
} | } | ||||
} | } | ||||
#search-modal .modal-dialog, | #search-modal .modal-dialog, | ||||
@@ -76,9 +76,37 @@ | |||||
.dropdown-navbar-new-comments .dropdown-menu { | .dropdown-navbar-new-comments .dropdown-menu { | ||||
margin-top: 0; | margin-top: 0; | ||||
} | } | ||||
.dropdown-help .dropdown-menu { | |||||
width: 350px !important; | |||||
max-height: 440px; | |||||
overflow: scroll; | |||||
} | |||||
.dropdown-help .dropdown-menu .input-group { | |||||
width: 100%; | |||||
background-color: #f5f7fa; | |||||
padding: 8px 12px; | |||||
} | |||||
.dropdown-help .dropdown-menu input { | |||||
width: 100%; | |||||
padding: 5px 10px; | |||||
outline: none; | |||||
border-radius: 3px 0 0 3px; | |||||
border: 1px solid #d1d8dd; | |||||
opacity: 0.9; | |||||
line-height: 1.5; | |||||
} | |||||
.dropdown-help .dropdown-menu button { | |||||
border: 1px solid #d1d8dd; | |||||
} | |||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.dropdown-navbar-new-comments.open .dropdown-menu, | |||||
.dropdown-navbar-user.open .dropdown-menu { | |||||
.dropdown-help .dropdown-menu { | |||||
position: fixed !important; | |||||
top: 40px; | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
@media (max-width: 767px) { | |||||
.dropdown-mobile.open .dropdown-menu { | |||||
position: absolute; | position: absolute; | ||||
border-top: 1px solid rgba(0, 0, 0, 0.14902); | border-top: 1px solid rgba(0, 0, 0, 0.14902); | ||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); | ||||
@@ -86,8 +114,7 @@ | |||||
right: 0; | right: 0; | ||||
left: auto; | left: auto; | ||||
} | } | ||||
.dropdown-navbar-new-comments.open .dropdown-menu > li > a, | |||||
.dropdown-navbar-user.open .dropdown-menu > li > a { | |||||
.dropdown-mobile.open .dropdown-menu > li > a { | |||||
padding: 12px; | padding: 12px; | ||||
} | } | ||||
} | } | ||||
@@ -107,6 +134,11 @@ | |||||
width: 300px; | width: 300px; | ||||
background-color: rgba(255, 255, 255, 0.9); | background-color: rgba(255, 255, 255, 0.9); | ||||
} | } | ||||
@media (max-width: 991px) { | |||||
#navbar-search { | |||||
width: 250px; | |||||
} | |||||
} | |||||
.navbar .navbar-search-icon { | .navbar .navbar-search-icon { | ||||
color: #6C7680; | color: #6C7680; | ||||
font-size: inherit; | font-size: inherit; | ||||
@@ -502,12 +534,6 @@ p { | |||||
border: 1px solid #d1d8dd; | border: 1px solid #d1d8dd; | ||||
border-radius: 15px; | border-radius: 15px; | ||||
} | } | ||||
.screenshot { | |||||
border: 1px solid #d1d8dd; | |||||
box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.15); | |||||
margin: 8px 0px; | |||||
max-width: 100%; | |||||
} | |||||
hr { | hr { | ||||
margin: 48px 0px 30px; | margin: 48px 0px 30px; | ||||
} | } | ||||
@@ -29,7 +29,7 @@ | |||||
} | } | ||||
@media (max-width: 991px) { | @media (max-width: 991px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 40% !important; | |||||
width: 35% !important; | |||||
} | } | ||||
.navbar-desk ~ ul > li { | .navbar-desk ~ ul > li { | ||||
float: left; | float: left; | ||||
@@ -48,7 +48,7 @@ | |||||
} | } | ||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 60% !important; | |||||
width: 50% !important; | |||||
} | } | ||||
} | } | ||||
#search-modal .modal-dialog, | #search-modal .modal-dialog, | ||||
@@ -76,9 +76,37 @@ | |||||
.dropdown-navbar-new-comments .dropdown-menu { | .dropdown-navbar-new-comments .dropdown-menu { | ||||
margin-top: 0; | margin-top: 0; | ||||
} | } | ||||
.dropdown-help .dropdown-menu { | |||||
width: 350px !important; | |||||
max-height: 440px; | |||||
overflow: scroll; | |||||
} | |||||
.dropdown-help .dropdown-menu .input-group { | |||||
width: 100%; | |||||
background-color: #f5f7fa; | |||||
padding: 8px 12px; | |||||
} | |||||
.dropdown-help .dropdown-menu input { | |||||
width: 100%; | |||||
padding: 5px 10px; | |||||
outline: none; | |||||
border-radius: 3px 0 0 3px; | |||||
border: 1px solid #d1d8dd; | |||||
opacity: 0.9; | |||||
line-height: 1.5; | |||||
} | |||||
.dropdown-help .dropdown-menu button { | |||||
border: 1px solid #d1d8dd; | |||||
} | |||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.dropdown-navbar-new-comments.open .dropdown-menu, | |||||
.dropdown-navbar-user.open .dropdown-menu { | |||||
.dropdown-help .dropdown-menu { | |||||
position: fixed !important; | |||||
top: 40px; | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
@media (max-width: 767px) { | |||||
.dropdown-mobile.open .dropdown-menu { | |||||
position: absolute; | position: absolute; | ||||
border-top: 1px solid rgba(0, 0, 0, 0.14902); | border-top: 1px solid rgba(0, 0, 0, 0.14902); | ||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); | ||||
@@ -86,8 +114,7 @@ | |||||
right: 0; | right: 0; | ||||
left: auto; | left: auto; | ||||
} | } | ||||
.dropdown-navbar-new-comments.open .dropdown-menu > li > a, | |||||
.dropdown-navbar-user.open .dropdown-menu > li > a { | |||||
.dropdown-mobile.open .dropdown-menu > li > a { | |||||
padding: 12px; | padding: 12px; | ||||
} | } | ||||
} | } | ||||
@@ -107,6 +134,11 @@ | |||||
width: 300px; | width: 300px; | ||||
background-color: rgba(255, 255, 255, 0.9); | background-color: rgba(255, 255, 255, 0.9); | ||||
} | } | ||||
@media (max-width: 991px) { | |||||
#navbar-search { | |||||
width: 250px; | |||||
} | |||||
} | |||||
.navbar .navbar-search-icon { | .navbar .navbar-search-icon { | ||||
color: #6C7680; | color: #6C7680; | ||||
font-size: inherit; | font-size: inherit; | ||||
@@ -102,6 +102,12 @@ kbd { | |||||
font-size: 12px; | font-size: 12px; | ||||
border-radius: 0px 0px 4px 4px; | border-radius: 0px 0px 4px 4px; | ||||
} | } | ||||
.dropdown-menu .dropdown-header { | |||||
padding: 3px 14px; | |||||
font-size: 11px; | |||||
font-weight: 200; | |||||
padding-top: 12px; | |||||
} | |||||
.dropdown-menu .divider { | .dropdown-menu .divider { | ||||
margin: 0px; | margin: 0px; | ||||
} | } | ||||
@@ -0,0 +1,51 @@ | |||||
frappe.provide('frappe.help.help_links'); | |||||
frappe.help.help_links['data-import-tool'] = [ | |||||
{ label: 'Importing and Exporting Data', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/data/data-import-tool.html' }, | |||||
] | |||||
frappe.help.help_links['modules/Setup'] = [ | |||||
{ label: 'Users and Permissions', url: 'http://frappe.github.io/erpnext/user/manual/en/setting-up/users-and-permissions/' }, | |||||
{ label: 'Settings', url: 'http://frappe.github.io/erpnext/user/manual/en/setting-up/settings/' }, | |||||
{ label: 'Data Management', url: 'http://frappe.github.io/erpnext/user/manual/en/setting-up/data/' }, | |||||
{ label: 'Email', url: 'http://frappe.github.io/erpnext/user/manual/en/setting-up/email/' }, | |||||
{ label: 'Printing', url: 'http://frappe.github.io/erpnext/user/manual/en/setting-up/print/' }, | |||||
] | |||||
frappe.help.help_links['List/User'] = [ | |||||
{ label: 'Adding Users', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/users-and-permissions/adding-users' }, | |||||
{ label: 'Rename User', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/rename-user' }, | |||||
] | |||||
frappe.help.help_links['permission-manager'] = [ | |||||
{ label: 'Role Permissions Manager', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/users-and-permissions/role-based-permissions' }, | |||||
] | |||||
frappe.help.help_links['user-permissions'] = [ | |||||
{ label: 'User Permissions', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/users-and-permissions/user-permissions' }, | |||||
] | |||||
frappe.help.help_links['Form/System Settings'] = [ | |||||
{ label: 'System Settings', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/settings/system-settings' }, | |||||
] | |||||
frappe.help.help_links['modules_setup'] = [ | |||||
{ label: 'Show or Hide Desktop Icons', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/settings/module-settings' }, | |||||
] | |||||
frappe.help.help_links['List/Email Account'] = [ | |||||
{ label: 'Email Account', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/email/email-account' }, | |||||
] | |||||
frappe.help.help_links['List/Email Alert'] = [ | |||||
{ label: 'Email Alert', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/email/email-alerts' }, | |||||
] | |||||
frappe.help.help_links['Form/Print Settings'] = [ | |||||
{ label: 'Print Settings', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/print-settings' }, | |||||
] | |||||
frappe.help.help_links['print-format-builder'] = [ | |||||
{ label: 'Print Format Builder', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/print-format-builder' }, | |||||
] | |||||
@@ -14,7 +14,7 @@ | |||||
<li class="visible-xs"> | <li class="visible-xs"> | ||||
<a class="navbar-search-button" href="#" data-toggle="modal" data-target="#search-modal"><i class="octicon octicon-search"></i></a> | <a class="navbar-search-button" href="#" data-toggle="modal" data-target="#search-modal"><i class="octicon octicon-search"></i></a> | ||||
</li> | </li> | ||||
<li class="dropdown dropdown-navbar-user"> | |||||
<li class="dropdown dropdown-navbar-user dropdown-mobile"> | |||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" | <a class="dropdown-toggle" data-toggle="dropdown" href="#" | ||||
onclick="return false;"> | onclick="return false;"> | ||||
{{ avatar }} | {{ avatar }} | ||||
@@ -44,7 +44,31 @@ | |||||
{%= __("Logout") %}</a></li> | {%= __("Logout") %}</a></li> | ||||
</ul> | </ul> | ||||
</li> | </li> | ||||
<li class="dropdown dropdown-navbar-new-comments"> | |||||
<li class="dropdown dropdown-help dropdown-mobile"> | |||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" | |||||
onclick="return false;" style="height: 40px;"> | |||||
<span class="hidden-xs hidden-sm" style="vertical-align: middle;">Help <b class="caret"></b></span> | |||||
<span class="visible-xs visible-sm standard-image" | |||||
style="padding: 50% 7px; font-size: 17px; background-color: #fafbfc; font-weight: 100;">?</span> | |||||
</a> | |||||
<ul class="dropdown-menu" role="menu"> | |||||
<div class="input-group" style="border-bottom: 1px solid #d1d8dd;"> | |||||
<input id="input-help" type="text" placeholder="What do you need help with?" autofocus> | |||||
<span class="input-group-btn"><button class="btn btn-default">Go</button></span> | |||||
</div> | |||||
<li id="help-links"></li> | |||||
<li class="divider"></li> | |||||
<li> | |||||
<a data-path="/user/manual/index" target="_blank">Browse the manual</a> | |||||
<a href="https://discuss.erpnext.com" target="_blank">Explore the forums</a> | |||||
<a href="https://gitter.im/frappe/erpnext" target="_blank">Chat</a> | |||||
<a href="mailto:hello@erpnext.com" target="_blank">Email us</a> | |||||
</li> | |||||
</ul> | |||||
</li> | |||||
<li class="dropdown dropdown-navbar-new-comments dropdown-mobile"> | |||||
<a class="btn dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> | <a class="btn dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> | ||||
<span class="navbar-new-comments">0</span> | <span class="navbar-new-comments">0</span> | ||||
</a> | </a> | ||||
@@ -9,12 +9,40 @@ frappe.ui.toolbar.Toolbar = Class.extend({ | |||||
avatar: frappe.avatar(frappe.session.user) | avatar: frappe.avatar(frappe.session.user) | ||||
})); | })); | ||||
this.setup_sidebar(); | |||||
$(document).on("notification-update", function() { | |||||
frappe.ui.notifications.update_notifications(); | |||||
}); | |||||
$('.dropdown-toggle').dropdown(); | |||||
this.setup_help(); | |||||
$(document).trigger('toolbar_setup'); | |||||
// clear all custom menus on page change | |||||
$(document).on("page-change", function() { | |||||
$("header .navbar .custom-menu").remove(); | |||||
}); | |||||
frappe.search.setup(); | |||||
}, | |||||
setup_sidebar: function () { | |||||
var header = $('header'); | |||||
header.find(".toggle-sidebar").on("click", function () { | header.find(".toggle-sidebar").on("click", function () { | ||||
var layout_side_section = $('.layout-side-section'); | var layout_side_section = $('.layout-side-section'); | ||||
var overlay_sidebar = layout_side_section.find('.overlay-sidebar'); | var overlay_sidebar = layout_side_section.find('.overlay-sidebar'); | ||||
overlay_sidebar.addClass('opened'); | overlay_sidebar.addClass('opened'); | ||||
overlay_sidebar.find('.reports-dropdown').removeClass('dropdown-menu').addClass('list-unstyled'); | |||||
overlay_sidebar.find('.dropdown-toggle').addClass('text-muted').find('.caret').addClass('hidden-xs hidden-sm'); | |||||
overlay_sidebar.find('.reports-dropdown') | |||||
.removeClass('dropdown-menu') | |||||
.addClass('list-unstyled'); | |||||
overlay_sidebar.find('.dropdown-toggle') | |||||
.addClass('text-muted').find('.caret') | |||||
.addClass('hidden-xs hidden-sm'); | |||||
$('<div class="close-sidebar">').hide().appendTo(layout_side_section).fadeIn(); | $('<div class="close-sidebar">').hide().appendTo(layout_side_section).fadeIn(); | ||||
@@ -28,28 +56,149 @@ frappe.ui.toolbar.Toolbar = Class.extend({ | |||||
scroll_container.css("overflow-y", ""); | scroll_container.css("overflow-y", ""); | ||||
layout_side_section.find(".close-sidebar").fadeOut(function() { | layout_side_section.find(".close-sidebar").fadeOut(function() { | ||||
overlay_sidebar.removeClass('opened').find('.dropdown-toggle').removeClass('text-muted'); | |||||
overlay_sidebar.find('.reports-dropdown').addClass('dropdown-menu'); | |||||
overlay_sidebar.removeClass('opened') | |||||
.find('.dropdown-toggle') | |||||
.removeClass('text-muted'); | |||||
overlay_sidebar.find('.reports-dropdown') | |||||
.addClass('dropdown-menu'); | |||||
}); | }); | ||||
} | } | ||||
}); | }); | ||||
}, | |||||
$(document).on("notification-update", function() { | |||||
frappe.ui.notifications.update_notifications(); | |||||
setup_help: function () { | |||||
$(".dropdown-help .dropdown-toggle").on("click", function () { | |||||
$(".dropdown-help input").focus(); | |||||
}); | }); | ||||
$('.dropdown-toggle').dropdown(); | |||||
$(".dropdown-help .dropdown-menu").on("click", "input, button", function (e) { | |||||
e.stopPropagation(); | |||||
}); | |||||
$(document).trigger('toolbar_setup'); | |||||
$("#input-help").on("keydown", function (e) { | |||||
if(e.which == 13) { | |||||
var keywords = $(this).val(); | |||||
show_help_results(keywords); | |||||
$(this).val(""); | |||||
} | |||||
}); | |||||
// clear all custom menus on page change | |||||
$(document).on("page-change", function() { | |||||
$("header .navbar .custom-menu").remove(); | |||||
$("#input-help + span").on("click", function () { | |||||
var keywords = $(this).val(); | |||||
show_help_results(keywords); | |||||
$(this).val(""); | |||||
}); | }); | ||||
frappe.search.setup(); | |||||
}, | |||||
$(document).on("page-change", function () { | |||||
var $help_links = $(".dropdown-help #help-links"); | |||||
$help_links.html(""); | |||||
var route = frappe.get_route_str(); | |||||
var breadcrumbs = route.split("/"); | |||||
var links = []; | |||||
for (var i = 0; i < breadcrumbs.length; i++) { | |||||
var r = route.split("/", i + 1); | |||||
var key = r.join("/"); | |||||
var help_links = frappe.help.help_links[key] || []; | |||||
links = $.merge(links, help_links); | |||||
} | |||||
if(links.length === 0) { | |||||
$help_links.next().hide(); | |||||
} | |||||
else { | |||||
$help_links.next().show(); | |||||
} | |||||
for (var i = 0; i < links.length; i++) { | |||||
var link = links[i]; | |||||
var url = link.url; | |||||
var data_path = url.slice(url.indexOf('/user')); | |||||
if(data_path.lastIndexOf('.')){ | |||||
data_path = data_path.slice(0, data_path.lastIndexOf('.')); | |||||
} | |||||
$("<a>", { | |||||
href: link.url, | |||||
text: link.label, | |||||
target: "_blank", | |||||
"data-path": data_path | |||||
}).appendTo($help_links); | |||||
} | |||||
$('.dropdown-help .dropdown-menu').on('click', 'a', show_results); | |||||
}); | |||||
var $help_modal = frappe.get_modal("Help", ""); | |||||
$help_modal.addClass('help-modal'); | |||||
var $result_modal = frappe.get_modal("", ""); | |||||
$result_modal.addClass("help-modal"); | |||||
$(document).on("click", ".help-modal a", show_results); | |||||
function show_help_results(keywords) { | |||||
frappe.call({ | |||||
method: "frappe.utils.help.get_help", | |||||
args: { | |||||
text: keywords | |||||
}, | |||||
callback: function (r) { | |||||
var results = r.message || []; | |||||
var result_html = "<h4 style='margin-bottom: 25px'>Showing results for '" + keywords + "' </h4>"; | |||||
for (var i = 0, l = results.length; i < l; i++) { | |||||
var title = results[i][0]; | |||||
var intro = results[i][1]; | |||||
var fpath = results[i][2]; | |||||
result_html += "<div class='search-result'>" + | |||||
"<a href='#' class='h4' data-path='"+fpath+"'>" + title + "</a>" + | |||||
"<p>" + intro + "</p>" + | |||||
"</div>"; | |||||
} | |||||
if(results.length === 0) { | |||||
result_html += "<p class='padding'>No results found</p>"; | |||||
} | |||||
$help_modal.find('.modal-body').html(result_html); | |||||
$help_modal.modal('show'); | |||||
} | |||||
}); | |||||
} | |||||
function show_results(e) { | |||||
//edit links | |||||
href = e.target.href; | |||||
if(href.indexOf('blob') > 0) { | |||||
window.open(href, '_blank'); | |||||
} | |||||
var converter = new Showdown.converter(); | |||||
var path = $(this).attr("data-path"); | |||||
if(path) { | |||||
e.preventDefault(); | |||||
frappe.call({ | |||||
method: "frappe.utils.help.get_help_content", | |||||
args: { | |||||
path: path | |||||
}, | |||||
callback: function (r) { | |||||
if(r.message){ | |||||
var title = r.message[0][0]; | |||||
var content = r.message[0][1]; | |||||
$result_modal.find('.modal-title').html("<span>" + title + "</span>"); | |||||
$result_modal.find('.modal-body').html(content); | |||||
$result_modal.modal('show'); | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
}); | }); | ||||
$.extend(frappe.ui.toolbar, { | $.extend(frappe.ui.toolbar, { | ||||
@@ -117,6 +117,14 @@ kbd { | |||||
// only rounded bottoms | // only rounded bottoms | ||||
border-radius: 0px 0px 4px 4px; | border-radius: 0px 0px 4px 4px; | ||||
} | |||||
.dropdown-menu .dropdown-header { | |||||
padding: 3px 14px; | |||||
font-size: 11px; | |||||
font-weight: 200; | |||||
padding-top: 12px; | |||||
} | } | ||||
.dropdown-menu .divider { | .dropdown-menu .divider { | ||||
@@ -449,3 +449,56 @@ textarea.form-control { | |||||
margin: 15px 0px; | margin: 15px 0px; | ||||
} | } | ||||
} | } | ||||
.screenshot { | |||||
border: 1px solid @border-color; | |||||
box-shadow: 1px 1px 7px rgba(0,0,0,0.15); | |||||
margin: 8px 0px; | |||||
max-width: 100%; | |||||
} | |||||
.help-modal { | |||||
a { | |||||
color: @brand-primary; | |||||
} | |||||
.modal-dialog { | |||||
width: 768px; | |||||
} | |||||
.modal-body { | |||||
padding: 15px 27px; | |||||
} | |||||
.parent-link { | |||||
line-height: 4em; | |||||
&:before { | |||||
font-family: 'Octicons'; | |||||
content: '\f0a4'; | |||||
} | |||||
} | |||||
.edit-container { | |||||
padding-bottom: 12px; | |||||
} | |||||
@media (max-width: @screen-xs) { | |||||
.modal-dialog { | |||||
width: auto; | |||||
} | |||||
.modal-content { | |||||
height: auto !important; | |||||
} | |||||
iframe { | |||||
height: auto; | |||||
width: 100%; | |||||
} | |||||
} | |||||
} | |||||
.search-result { | |||||
margin-bottom: 24px; | |||||
} |
@@ -350,13 +350,6 @@ p { | |||||
border-radius: 15px; | border-radius: 15px; | ||||
} | } | ||||
.screenshot { | |||||
border: 1px solid @border-color; | |||||
box-shadow: 1px 1px 7px rgba(0,0,0,0.15); | |||||
margin: 8px 0px; | |||||
max-width: 100%; | |||||
} | |||||
hr { | hr { | ||||
margin: 48px 0px 30px; | margin: 48px 0px 30px; | ||||
} | } | ||||
@@ -37,7 +37,7 @@ | |||||
@media (max-width: 991px) { | @media (max-width: 991px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 40% !important; | |||||
width: 35% !important; | |||||
& ~ ul > li { | & ~ ul > li { | ||||
float: left; | float: left; | ||||
@@ -59,7 +59,7 @@ | |||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.navbar-desk { | .navbar-desk { | ||||
width: 60% !important; | |||||
width: 50% !important; | |||||
} | } | ||||
} | } | ||||
@@ -95,9 +95,43 @@ | |||||
} | } | ||||
} | } | ||||
.dropdown-help .dropdown-menu { | |||||
width: 350px !important; | |||||
max-height: 440px; | |||||
overflow: scroll; | |||||
.input-group { | |||||
width: 100%; | |||||
background-color: #f5f7fa; | |||||
padding: 8px 12px; | |||||
} | |||||
input { | |||||
width: 100%; | |||||
padding: 5px 10px; | |||||
outline: none; | |||||
border-radius: 3px 0 0 3px; | |||||
border: 1px solid #d1d8dd; | |||||
opacity: 0.9; | |||||
line-height: 1.5; | |||||
} | |||||
button { | |||||
border: 1px solid #d1d8dd; | |||||
&.loading { | |||||
} | |||||
} | |||||
@media (max-width: @screen-xs) { | |||||
position: fixed !important; | |||||
top: 40px; | |||||
width: 100% !important; | |||||
} | |||||
} | |||||
@media (max-width: 767px) { | @media (max-width: 767px) { | ||||
.dropdown-navbar-new-comments.open .dropdown-menu, | |||||
.dropdown-navbar-user.open .dropdown-menu { | |||||
.dropdown-mobile.open .dropdown-menu { | |||||
position: absolute; | position: absolute; | ||||
border-top: 1px solid rgba(0, 0, 0, 0.14902); | border-top: 1px solid rgba(0, 0, 0, 0.14902); | ||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175); | box-shadow: 0 6px 12px rgba(0, 0, 0, .175); | ||||
@@ -128,6 +162,10 @@ | |||||
#navbar-search { | #navbar-search { | ||||
width: 300px; | width: 300px; | ||||
background-color: rgba(255, 255, 255, 0.9); | background-color: rgba(255, 255, 255, 0.9); | ||||
@media (max-width: @screen-sm) { | |||||
width: 250px; | |||||
} | |||||
} | } | ||||
.navbar .navbar-search-icon { | .navbar .navbar-search-icon { | ||||
@@ -2,14 +2,10 @@ | |||||
<nav class="navbar navbar-default navbar-main" role="navigation"> | <nav class="navbar navbar-default navbar-main" role="navigation"> | ||||
<div class="container"> | <div class="container"> | ||||
<div class="navbar-header"> | <div class="navbar-header"> | ||||
<!-- <a class="navbar-brand ellipsis" href="{{ url_prefix }}{{ home_page or "/" }}"> --> | |||||
<a class="navbar-brand ellipsis" | <a class="navbar-brand ellipsis" | ||||
href="{{ url_prefix }}{{ home_page or "/"}}"> | href="{{ url_prefix }}{{ home_page or "/"}}"> | ||||
<span>{{ brand_html or (frappe.get_hooks("brand_html") or ["Home"])[0] }}</span> | <span>{{ brand_html or (frappe.get_hooks("brand_html") or ["Home"])[0] }}</span> | ||||
</a> | </a> | ||||
<!-- <a class="pull-right visible-xs navbar-toggle toggle-sidebar"> | |||||
<i class="octicon octicon-three-bars"></i> | |||||
</a> --> | |||||
<div class="dropdown"> | <div class="dropdown"> | ||||
<button class="btn btn-default navbar-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> | <button class="btn btn-default navbar-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> | ||||
<i class="octicon octicon-three-bars"></i> | <i class="octicon octicon-three-bars"></i> | ||||
@@ -9,6 +9,9 @@ from frappe.model.db_schema import DbManager | |||||
from frappe.installer import get_root_connection | from frappe.installer import get_root_connection | ||||
from frappe.database import Database | from frappe.database import Database | ||||
import os | import os | ||||
import re | |||||
from markdown2 import markdown | |||||
from bs4 import BeautifulSoup | |||||
help_db_name = '_frappe_help' | help_db_name = '_frappe_help' | ||||
@@ -22,6 +25,10 @@ def sync(): | |||||
def get_help(text): | def get_help(text): | ||||
return HelpDatabase().search(text) | return HelpDatabase().search(text) | ||||
@frappe.whitelist() | |||||
def get_help_content(path): | |||||
return HelpDatabase().get_content(path) | |||||
class HelpDatabase(object): | class HelpDatabase(object): | ||||
def __init__(self): | def __init__(self): | ||||
self.make_database() | self.make_database() | ||||
@@ -42,13 +49,23 @@ class HelpDatabase(object): | |||||
def make_table(self): | def make_table(self): | ||||
if not 'help' in self.db.get_tables(): | if not 'help' in self.db.get_tables(): | ||||
self.db.sql('''create table help(path text, content text, fulltext(content)) | |||||
self.db.sql('''create table help(path text, content text, title text, intro text, fulltext(title), fulltext(content)) | |||||
COLLATE=utf8mb4_unicode_ci | COLLATE=utf8mb4_unicode_ci | ||||
ENGINE=MyISAM | ENGINE=MyISAM | ||||
CHARACTER SET=utf8mb4''') | CHARACTER SET=utf8mb4''') | ||||
def search(self, words): | def search(self, words): | ||||
return self.db.sql('select path, content from help where match(content) against (%s) limit 10', words) | |||||
return self.db.sql('select title, intro, path from help where match(content) against (%s) limit 10', words) | |||||
def get_content(self, path): | |||||
query = 'select title, content from help where path like "%{path}%" order by path desc' | |||||
path2 = path | |||||
if not path2.endswith('index'): | |||||
path2 += "index" if path2.endswith('/') else "/index" | |||||
result = self.db.sql(query.format(path=path2)) | |||||
if not result: | |||||
result = self.db.sql(query.format(path=path)) | |||||
return result | |||||
def sync_pages(self): | def sync_pages(self): | ||||
self.db.sql('truncate help') | self.db.sql('truncate help') | ||||
@@ -56,11 +73,116 @@ class HelpDatabase(object): | |||||
docs_folder = '../apps/{app}/{app}/docs/user'.format(app=app) | docs_folder = '../apps/{app}/{app}/docs/user'.format(app=app) | ||||
if os.path.exists(docs_folder): | if os.path.exists(docs_folder): | ||||
for basepath, folders, files in os.walk(docs_folder): | for basepath, folders, files in os.walk(docs_folder): | ||||
files = self.reorder_files(files) | |||||
for fname in files: | for fname in files: | ||||
if fname.rsplit('.', 1)[-1] in ('md', 'html'): | if fname.rsplit('.', 1)[-1] in ('md', 'html'): | ||||
fpath = os.path.join(basepath, fname) | fpath = os.path.join(basepath, fname) | ||||
with open(fpath, 'r') as f: | with open(fpath, 'r') as f: | ||||
content = frappe.render_template(unicode(f.read(), 'utf-8'), | |||||
{'docs_base_url': '/assets/{app}_docs'.format(app=app)}) | |||||
content = self.make_content(content, fpath) | |||||
title = self.make_title(basepath, fname, content) | |||||
intro = self.make_intro(content) | |||||
#relpath = os.path.relpath(fpath, '../apps/{app}'.format(app=app)) | #relpath = os.path.relpath(fpath, '../apps/{app}'.format(app=app)) | ||||
self.db.sql('''insert into help(path, content) | |||||
values (%s, %s)''', (fpath, f.read())) | |||||
self.db.sql('''insert into help(path, content, title, intro) | |||||
values (%s, %s, %s, %s)''', (fpath, content, title, intro)) | |||||
def make_title(self, basepath, filename, html): | |||||
if '<h1>' in html: | |||||
title = html.split("<h1>", 1)[1].split("</h1>", 1)[0] | |||||
elif 'index' in filename: | |||||
title = basepath.rsplit('/', 1)[-1].title().replace("-", " ") | |||||
else: | |||||
title = filename.rsplit('.', 1)[0].title().replace("-", " ") | |||||
return title | |||||
def make_intro(self, html): | |||||
intro = "" | |||||
if '<p>' in html: | |||||
intro = html.split('<p>', 1)[1].split('</p>', 1)[0] | |||||
if 'Duration' in html: | |||||
intro = "Help Video: " + intro | |||||
return intro | |||||
def make_content(self, content, path): | |||||
html = markdown(content) | |||||
if '{index}' in html: | |||||
path = path.rsplit("/", 1)[0] | |||||
index_path = os.path.join(path, "index.txt") | |||||
if os.path.exists(index_path): | |||||
with open(index_path, 'r') as f: | |||||
lines = f.read().split('\n') | |||||
links_html = "<ol class='index-links'>" | |||||
for line in lines: | |||||
fpath = os.path.join(path, line) | |||||
title = line.title().replace("-", " ") | |||||
if title: | |||||
links_html += "<li><a data-path='{fpath}'> {title} </a></li>".format(fpath=fpath, title=title) | |||||
links_html += "</ol>" | |||||
html = html.replace('{index}', links_html) | |||||
if '{next}' in html: | |||||
html = html.replace('{next}', '') | |||||
target = path.split('/', 3)[-1] | |||||
app_name = path.split('/', 3)[2] | |||||
html += ''' | |||||
<div class="page-container"> | |||||
<div class="page-content"> | |||||
<div class="edit-container text-center"> | |||||
<i class="icon icon-smile"></i> | |||||
<a class="text-muted edit" href="https://github.com/frappe/{app_name}/blob/develop/{target}"> | |||||
Improve this page | |||||
</a> | |||||
</div> | |||||
</div> | |||||
</div>'''.format(app_name=app_name, target=target) | |||||
soup = BeautifulSoup(html, 'html.parser') | |||||
for link in soup.find_all('a'): | |||||
if link.has_attr('href'): | |||||
url = link['href'] | |||||
if '/user' in url: | |||||
data_path = url[url.index('/user'):] | |||||
if '.' in data_path: | |||||
data_path = data_path[: data_path.rindex('.')] | |||||
if data_path: | |||||
link['data-path'] = data_path | |||||
parent = self.get_parent(path) | |||||
if parent: | |||||
parent_tag = soup.new_tag('a') | |||||
parent_tag.string = parent['title'] | |||||
parent_tag['class'] = 'parent-link' | |||||
parent_tag['data-path'] = parent['path'] | |||||
soup.find().insert_before(parent_tag) | |||||
return soup.prettify() | |||||
def get_parent(self, child_path): | |||||
path = child_path | |||||
if 'index' in child_path: | |||||
child_path = child_path[: child_path.rindex('index')] | |||||
if child_path[-1] == '/': | |||||
child_path = child_path[:-1] | |||||
parent_path = child_path[: child_path.rindex('/')] + "/index" | |||||
result = self.get_content(parent_path) | |||||
if result: | |||||
title = result[0][0] | |||||
return { 'title': title, 'path': parent_path } | |||||
else: | |||||
return None | |||||
def reorder_files(self, files): | |||||
pos = 0 | |||||
if 'index.md' in files: | |||||
pos = files.index('index.md') | |||||
elif 'index.html' in files: | |||||
pos = files.index('index.html') | |||||
if pos: | |||||
files[0], files[pos] = files[pos], files[0] | |||||
return files |