Help feature (#1966)
* [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 py
This commit is contained in:
parent
55d17a6aed
commit
e0a22a98bf
21 changed files with 627 additions and 71 deletions
|
|
@ -57,14 +57,19 @@ def make_asset_dirs(make_copy=False):
|
|||
# symlink app/public > assets/app
|
||||
for app_name in frappe.get_all_apps(True):
|
||||
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)
|
||||
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__))
|
||||
|
||||
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)
|
||||
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):
|
||||
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.
|
||||
|
||||
> 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,6 @@ Here is what the report looks like:
|
|||
1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded.
|
||||
1. You can use all global functions like `fmt_money` and dateutil.
|
||||
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 -->
|
||||
|
|
@ -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.
|
||||
|
||||
> 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,6 @@ Here is what the report looks like:
|
|||
1. [Bootstrap Stylesheet](http://getbootstrap.com) is pre-loaded.
|
||||
1. You can use all global functions like `fmt_money` and dateutil.
|
||||
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 -->
|
||||
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
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/number_format.js",
|
||||
"public/js/frappe/misc/help.js",
|
||||
"public/js/frappe/misc/help_links.js",
|
||||
|
||||
"public/js/frappe/ui/upload.html",
|
||||
"public/js/frappe/upload.js",
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ kbd {
|
|||
font-size: 12px;
|
||||
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 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ kbd {
|
|||
font-size: 12px;
|
||||
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 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
|
@ -582,3 +588,43 @@ fieldset[disabled] .form-control {
|
|||
.liked-by-popover li {
|
||||
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) {
|
||||
.navbar-desk {
|
||||
width: 40% !important;
|
||||
width: 35% !important;
|
||||
}
|
||||
.navbar-desk ~ ul > li {
|
||||
float: left;
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar-desk {
|
||||
width: 60% !important;
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
#search-modal .modal-dialog,
|
||||
|
|
@ -76,9 +76,37 @@
|
|||
.dropdown-navbar-new-comments .dropdown-menu {
|
||||
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) {
|
||||
.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;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.14902);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
|
|
@ -86,8 +114,7 @@
|
|||
right: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +134,11 @@
|
|||
width: 300px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
#navbar-search {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
.navbar .navbar-search-icon {
|
||||
color: #6C7680;
|
||||
font-size: inherit;
|
||||
|
|
@ -502,12 +534,6 @@ p {
|
|||
border: 1px solid #d1d8dd;
|
||||
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 {
|
||||
margin: 48px 0px 30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
@media (max-width: 991px) {
|
||||
.navbar-desk {
|
||||
width: 40% !important;
|
||||
width: 35% !important;
|
||||
}
|
||||
.navbar-desk ~ ul > li {
|
||||
float: left;
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar-desk {
|
||||
width: 60% !important;
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
#search-modal .modal-dialog,
|
||||
|
|
@ -76,9 +76,37 @@
|
|||
.dropdown-navbar-new-comments .dropdown-menu {
|
||||
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) {
|
||||
.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;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.14902);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
|
|
@ -86,8 +114,7 @@
|
|||
right: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +134,11 @@
|
|||
width: 300px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
#navbar-search {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
.navbar .navbar-search-icon {
|
||||
color: #6C7680;
|
||||
font-size: inherit;
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ kbd {
|
|||
font-size: 12px;
|
||||
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 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
|
|
|||
51
frappe/public/js/frappe/misc/help_links.js
Normal file
51
frappe/public/js/frappe/misc/help_links.js
Normal file
|
|
@ -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">
|
||||
<a class="navbar-search-button" href="#" data-toggle="modal" data-target="#search-modal"><i class="octicon octicon-search"></i></a>
|
||||
</li>
|
||||
<li class="dropdown dropdown-navbar-user">
|
||||
<li class="dropdown dropdown-navbar-user dropdown-mobile">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#"
|
||||
onclick="return false;">
|
||||
{{ avatar }}
|
||||
|
|
@ -44,7 +44,31 @@
|
|||
{%= __("Logout") %}</a></li>
|
||||
</ul>
|
||||
</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">
|
||||
<span class="navbar-new-comments">0</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,40 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
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 () {
|
||||
var layout_side_section = $('.layout-side-section');
|
||||
var overlay_sidebar = layout_side_section.find('.overlay-sidebar');
|
||||
|
||||
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();
|
||||
|
||||
|
|
@ -28,28 +56,149 @@ frappe.ui.toolbar.Toolbar = Class.extend({
|
|||
scroll_container.css("overflow-y", "");
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
$('.dropdown-toggle').dropdown();
|
||||
|
||||
$(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_help: function () {
|
||||
|
||||
$(".dropdown-help .dropdown-toggle").on("click", function () {
|
||||
$(".dropdown-help input").focus();
|
||||
});
|
||||
|
||||
$(".dropdown-help .dropdown-menu").on("click", "input, button", function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$("#input-help").on("keydown", function (e) {
|
||||
if(e.which == 13) {
|
||||
var keywords = $(this).val();
|
||||
show_help_results(keywords);
|
||||
$(this).val("");
|
||||
}
|
||||
});
|
||||
|
||||
$("#input-help + span").on("click", function () {
|
||||
var keywords = $(this).val();
|
||||
show_help_results(keywords);
|
||||
$(this).val("");
|
||||
});
|
||||
|
||||
$(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, {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ kbd {
|
|||
|
||||
// only rounded bottoms
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -449,3 +449,56 @@ textarea.form-control {
|
|||
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;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
border: 1px solid @border-color;
|
||||
box-shadow: 1px 1px 7px rgba(0,0,0,0.15);
|
||||
margin: 8px 0px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 48px 0px 30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
@media (max-width: 991px) {
|
||||
.navbar-desk {
|
||||
width: 40% !important;
|
||||
width: 35% !important;
|
||||
|
||||
& ~ ul > li {
|
||||
float: left;
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
|
||||
@media (max-width: 767px) {
|
||||
.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) {
|
||||
.dropdown-navbar-new-comments.open .dropdown-menu,
|
||||
.dropdown-navbar-user.open .dropdown-menu {
|
||||
.dropdown-mobile.open .dropdown-menu {
|
||||
position: absolute;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.14902);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
|
|
@ -128,6 +162,10 @@
|
|||
#navbar-search {
|
||||
width: 300px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
|
||||
@media (max-width: @screen-sm) {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar .navbar-search-icon {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,10 @@
|
|||
<nav class="navbar navbar-default navbar-main" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<!-- <a class="navbar-brand ellipsis" href="{{ url_prefix }}{{ home_page or "/" }}"> -->
|
||||
<a class="navbar-brand ellipsis"
|
||||
href="{{ url_prefix }}{{ home_page or "/"}}">
|
||||
<span>{{ brand_html or (frappe.get_hooks("brand_html") or ["Home"])[0] }}</span>
|
||||
</a>
|
||||
<!-- <a class="pull-right visible-xs navbar-toggle toggle-sidebar">
|
||||
<i class="octicon octicon-three-bars"></i>
|
||||
</a> -->
|
||||
<div class="dropdown">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ from frappe.model.db_schema import DbManager
|
|||
from frappe.installer import get_root_connection
|
||||
from frappe.database import Database
|
||||
import os
|
||||
import re
|
||||
from markdown2 import markdown
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
help_db_name = '_frappe_help'
|
||||
|
||||
|
|
@ -22,6 +25,10 @@ def sync():
|
|||
def get_help(text):
|
||||
return HelpDatabase().search(text)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_help_content(path):
|
||||
return HelpDatabase().get_content(path)
|
||||
|
||||
class HelpDatabase(object):
|
||||
def __init__(self):
|
||||
self.make_database()
|
||||
|
|
@ -42,13 +49,23 @@ class HelpDatabase(object):
|
|||
|
||||
def make_table(self):
|
||||
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
|
||||
ENGINE=MyISAM
|
||||
CHARACTER SET=utf8mb4''')
|
||||
|
||||
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):
|
||||
self.db.sql('truncate help')
|
||||
|
|
@ -56,11 +73,116 @@ class HelpDatabase(object):
|
|||
docs_folder = '../apps/{app}/{app}/docs/user'.format(app=app)
|
||||
if os.path.exists(docs_folder):
|
||||
for basepath, folders, files in os.walk(docs_folder):
|
||||
files = self.reorder_files(files)
|
||||
for fname in files:
|
||||
if fname.rsplit('.', 1)[-1] in ('md', 'html'):
|
||||
fpath = os.path.join(basepath, fname)
|
||||
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))
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue