Просмотр исходного кода

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
version-14
Faris Ansari 9 лет назад
committed by Rushabh Mehta
Родитель
Сommit
e0a22a98bf
21 измененных файлов: 622 добавлений и 66 удалений
  1. +13
    -8
      frappe/build.py
  2. +1
    -1
      frappe/docs/user/en/guides/app-development/using-html-templates-in-javascript.md
  3. +1
    -1
      frappe/docs/user/en/guides/reports-and-printing/print-format-for-reports.md
  4. +1
    -1
      frappe/docs/user/fr/guides/app-development/using-html-templates-in-javascript.md
  5. +1
    -1
      frappe/docs/user/fr/guides/reports-and-printing/print-format-for-reports.md
  6. +2
    -2
      frappe/docs/user/index.md
  7. +1
    -0
      frappe/public/build.json
  8. +6
    -0
      frappe/public/css/common.css
  9. +46
    -0
      frappe/public/css/desk.css
  10. +38
    -12
      frappe/public/css/docs.css
  11. +38
    -6
      frappe/public/css/navbar.css
  12. +6
    -0
      frappe/public/css/website.css
  13. +51
    -0
      frappe/public/js/frappe/misc/help_links.js
  14. +26
    -2
      frappe/public/js/frappe/ui/toolbar/navbar.html
  15. +162
    -13
      frappe/public/js/frappe/ui/toolbar/toolbar.js
  16. +8
    -0
      frappe/public/less/common.less
  17. +53
    -0
      frappe/public/less/desk.less
  18. +0
    -7
      frappe/public/less/docs.less
  19. +42
    -4
      frappe/public/less/navbar.less
  20. +0
    -4
      frappe/templates/includes/navbar/navbar.html
  21. +126
    -4
      frappe/utils/help.py

+ 13
- 8
frappe/build.py Просмотреть файл

@@ -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)

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):
assets_path = os.path.join(frappe.local.sites_path, "assets")


+ 1
- 1
frappe/docs/user/en/guides/app-development/using-html-templates-in-javascript.md Просмотреть файл

@@ -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.



+ 1
- 1
frappe/docs/user/en/guides/reports-and-printing/print-format-for-reports.md Просмотреть файл

@@ -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
- 1
frappe/docs/user/fr/guides/app-development/using-html-templates-in-javascript.md Просмотреть файл

@@ -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.



+ 1
- 1
frappe/docs/user/fr/guides/reports-and-printing/print-format-for-reports.md Просмотреть файл

@@ -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
- 2
frappe/docs/user/index.md Просмотреть файл

@@ -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)

+ 1
- 0
frappe/public/build.json Просмотреть файл

@@ -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",


+ 6
- 0
frappe/public/css/common.css Просмотреть файл

@@ -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;
}


+ 46
- 0
frappe/public/css/desk.css Просмотреть файл

@@ -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;
}

+ 38
- 12
frappe/public/css/docs.css Просмотреть файл

@@ -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;
}


+ 38
- 6
frappe/public/css/navbar.css Просмотреть файл

@@ -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;


+ 6
- 0
frappe/public/css/website.css Просмотреть файл

@@ -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
- 0
frappe/public/js/frappe/misc/help_links.js Просмотреть файл

@@ -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' },
]


+ 26
- 2
frappe/public/js/frappe/ui/toolbar/navbar.html Просмотреть файл

@@ -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>


+ 162
- 13
frappe/public/js/frappe/ui/toolbar/toolbar.js Просмотреть файл

@@ -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();
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, {


+ 8
- 0
frappe/public/less/common.less Просмотреть файл

@@ -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 {


+ 53
- 0
frappe/public/less/desk.less Просмотреть файл

@@ -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;
}

+ 0
- 7
frappe/public/less/docs.less Просмотреть файл

@@ -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;
}


+ 42
- 4
frappe/public/less/navbar.less Просмотреть файл

@@ -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 {


+ 0
- 4
frappe/templates/includes/navbar/navbar.html Просмотреть файл

@@ -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>


+ 126
- 4
frappe/utils/help.py Просмотреть файл

@@ -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

Загрузка…
Отмена
Сохранить