commit
f926a2aa48
38 changed files with 616 additions and 490 deletions
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"creation": "2016-05-25 09:43:44.767581",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "tag_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Tags",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-05-31 08:29:01.773065",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Tag",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Tag(Document):
|
||||
def validate(self):
|
||||
self.tag_name = self.tag_name.title()
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright (c) 2016, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Tag', {
|
||||
tag_name:function(frm){
|
||||
for (var i = 0 ;i<frm.doc.tags.length;i++){
|
||||
frm.doc.tags[i].tag_name = toTitle(frm.doc.tags[i].tag_name)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:category_name",
|
||||
"beta": 0,
|
||||
"creation": "2016-05-25 09:49:07.125394",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "tags",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tags",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tag",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "tagdocs",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Doctypes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tag Doc Category",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:37.489085",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Tag Category",
|
||||
"name_case": "Title Case",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Tag Categories')
|
||||
|
||||
class TestTagCategories(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2016-05-25 13:09:20.996154",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "tagdoc",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Doctype to Assign Tags",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-05-30 15:04:45.454688",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Tag Doc Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TagDocCategory(Document):
|
||||
pass
|
||||
|
|
@ -961,6 +961,26 @@ class Database(object):
|
|||
frappe.flags.touched_tables = set()
|
||||
frappe.flags.touched_tables.update(tables)
|
||||
|
||||
def bulk_insert(self, doctype, fields, values):
|
||||
"""
|
||||
Insert multiple records at a time
|
||||
|
||||
:param doctype: Doctype name
|
||||
:param fields: list of fields
|
||||
:params values: list of list of values
|
||||
"""
|
||||
insert_list = []
|
||||
fields = ", ".join(["`"+field+"`" for field in fields])
|
||||
|
||||
for idx, value in enumerate(values):
|
||||
insert_list.append(tuple(value))
|
||||
if idx and (idx%10000 == 0 or idx < len(values)-1):
|
||||
self.sql("""INSERT INTO `tab{doctype}` ({fields}) VALUES {values}""".format(
|
||||
doctype=doctype,
|
||||
fields=fields,
|
||||
values=", ".join(['%s'] * len(insert_list))
|
||||
), tuple(insert_list))
|
||||
insert_list = []
|
||||
|
||||
def enqueue_jobs_after_commit():
|
||||
if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0:
|
||||
|
|
|
|||
8
frappe/desk/doctype/tag/tag.js
Normal file
8
frappe/desk/doctype/tag/tag.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Tag', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
49
frappe/desk/doctype/tag/tag.json
Normal file
49
frappe/desk/doctype/tag/tag.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"autoname": "Prompt",
|
||||
"creation": "2016-05-25 09:43:44.767581",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"modified": "2019-09-25 17:47:41.712237",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Tag",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
179
frappe/desk/doctype/tag/tag.py
Normal file
179
frappe/desk/doctype/tag/tag.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Tag(Document):
|
||||
pass
|
||||
|
||||
def check_user_tags(dt):
|
||||
"if the user does not have a tags column, then it creates one"
|
||||
try:
|
||||
frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
DocTags(dt).setup()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_tag(tag, dt, dn, color=None):
|
||||
"adds a new tag to a record, and creates the Tag master"
|
||||
DocTags(dt).add(dn, tag)
|
||||
|
||||
return tag
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_tag(tag, dt, dn):
|
||||
"removes tag from the record"
|
||||
DocTags(dt).remove(dn, tag)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tagged_docs(doctype, tag):
|
||||
frappe.has_permission(doctype, throw=True)
|
||||
|
||||
return frappe.db.sql("""SELECT name
|
||||
FROM `tab{0}`
|
||||
WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags(doctype, txt):
|
||||
tag = frappe.get_list("Tag", filters=[["name", "like", "%{}%".format(txt)]])
|
||||
tags = [t.name for t in tag]
|
||||
|
||||
return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags))))
|
||||
|
||||
class DocTags:
|
||||
"""Tags for a particular doctype"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
|
||||
def get_tag_fields(self):
|
||||
"""returns tag_fields property"""
|
||||
return frappe.db.get_value('DocType', self.dt, 'tag_fields')
|
||||
|
||||
def get_tags(self, dn):
|
||||
"""returns tag for a particular item"""
|
||||
return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip()
|
||||
|
||||
def add(self, dn, tag):
|
||||
"""add a new user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
if not tag in tl:
|
||||
tl.append(tag)
|
||||
if not frappe.db.exists("Tag", tag):
|
||||
frappe.get_doc({"doctype": "Tag", "name": tag}).insert(ignore_permissions=True)
|
||||
self.update(dn, tl)
|
||||
|
||||
def remove(self, dn, tag):
|
||||
"""remove a user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl))
|
||||
|
||||
def remove_all(self, dn):
|
||||
"""remove all user tags (call before delete)"""
|
||||
self.update(dn, [])
|
||||
|
||||
def update(self, dn, tl):
|
||||
"""updates the _user_tag column in the table"""
|
||||
|
||||
if not tl:
|
||||
tags = ''
|
||||
else:
|
||||
tl = list(set(filter(lambda x: x, tl)))
|
||||
tags = ',' + ','.join(tl)
|
||||
try:
|
||||
frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \
|
||||
(self.dt,'%s','%s'), (tags , dn))
|
||||
doc= frappe.get_doc(self.dt, dn)
|
||||
update_tags(doc, tags)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
if not tags:
|
||||
# no tags, nothing to do
|
||||
return
|
||||
|
||||
self.setup()
|
||||
self.update(dn, tl)
|
||||
else: raise
|
||||
|
||||
def setup(self):
|
||||
"""adds the _user_tags column if not exists"""
|
||||
from frappe.database.schema import add_column
|
||||
add_column(self.dt, "_user_tags", "Data")
|
||||
|
||||
def delete_tags_for_document(doc):
|
||||
"""
|
||||
Delete the Tag Link entry of a document that has
|
||||
been deleted
|
||||
:param doc: Deleted document
|
||||
"""
|
||||
if not frappe.db.table_exists("Tag Link"):
|
||||
return
|
||||
|
||||
frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s AND `document_name`=%s""", (doc.doctype, doc.name))
|
||||
|
||||
def update_tags(doc, tags):
|
||||
"""
|
||||
Adds tags for documents
|
||||
:param doc: Document to be added to global tags
|
||||
"""
|
||||
|
||||
new_tags = list(set([tag.strip() for tag in tags.split(",") if tag]))
|
||||
|
||||
for tag in new_tags:
|
||||
if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tag Link",
|
||||
"document_type": doc.doctype,
|
||||
"document_name": doc.name,
|
||||
"parenttype": doc.doctype,
|
||||
"parent": doc.name,
|
||||
"title": doc.get_title() or '',
|
||||
"tag": tag
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
existing_tags = [tag.tag for tag in frappe.get_list("Tag Link", filters={
|
||||
"document_type": doc.doctype,
|
||||
"document_name": doc.name
|
||||
}, fields=["tag"])]
|
||||
|
||||
deleted_tags = get_deleted_tags(new_tags, existing_tags)
|
||||
|
||||
if deleted_tags:
|
||||
for tag in deleted_tags:
|
||||
delete_tag_for_document(doc.doctype, doc.name, tag)
|
||||
|
||||
def get_deleted_tags(new_tags, existing_tags):
|
||||
|
||||
return list(set(existing_tags) - set(new_tags))
|
||||
|
||||
def delete_tag_for_document(dt, dn, tag):
|
||||
frappe.db.sql("""DELETE FROM `tabTag Link` WHERE `document_type`=%s AND `document_name`=%s AND tag=%s""", (dt, dn, tag))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_documents_for_tag(tag):
|
||||
"""
|
||||
Search for given text in Tag Link
|
||||
:param tag: tag to be searched
|
||||
"""
|
||||
# remove hastag `#` from tag
|
||||
tag = tag[1:]
|
||||
results = []
|
||||
|
||||
result = frappe.get_list("Tag Link", filters={"tag": tag}, fields=["document_type", "document_name", "title", "tag"])
|
||||
|
||||
for res in result:
|
||||
results.append({
|
||||
"doctype": res.document_type,
|
||||
"name": res.document_name,
|
||||
"content": res.title
|
||||
})
|
||||
|
||||
print(results)
|
||||
return results
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags_list_for_awesomebar():
|
||||
return [t.name for t in frappe.get_list("Tag")]
|
||||
10
frappe/desk/doctype/tag/test_tag.py
Normal file
10
frappe/desk/doctype/tag/test_tag.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestTag(unittest.TestCase):
|
||||
pass
|
||||
8
frappe/desk/doctype/tag_link/tag_link.js
Normal file
8
frappe/desk/doctype/tag_link/tag_link.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Tag Link', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
70
frappe/desk/doctype/tag_link/tag_link.json
Normal file
70
frappe/desk/doctype/tag_link/tag_link.json
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"creation": "2019-09-24 13:25:36.435685",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"document_name",
|
||||
"tag",
|
||||
"title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Document Title",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tag",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Document Tag",
|
||||
"options": "Tag",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "document_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Document Name",
|
||||
"options": "document_type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-10-03 16:42:35.932409",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Tag Link",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TagCategory(Document):
|
||||
class TagLink(Document):
|
||||
pass
|
||||
10
frappe/desk/doctype/tag_link/test_tag_link.py
Normal file
10
frappe/desk/doctype/tag_link/test_tag_link.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestTagLink(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -100,7 +100,8 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
"views": get_view_logs(doc.doctype, doc.name),
|
||||
"energy_point_logs": get_point_logs(doc.doctype, doc.name),
|
||||
"milestones": get_milestones(doc.doctype, doc.name),
|
||||
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user)
|
||||
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user),
|
||||
"tags": get_tags(doc.doctype, doc.name)
|
||||
}
|
||||
|
||||
def get_milestones(doctype, name):
|
||||
|
|
@ -255,3 +256,11 @@ def get_view_logs(doctype, docname):
|
|||
if view_logs:
|
||||
logs = view_logs
|
||||
return logs
|
||||
|
||||
def get_tags(doctype, name):
|
||||
tags = [tag.tag for tag in frappe.get_all("Tag Link", filters={
|
||||
"document_type": doctype,
|
||||
"document_name": name
|
||||
}, fields=["tag"])]
|
||||
|
||||
return ",".join([tag for tag in tags])
|
||||
|
|
@ -261,13 +261,17 @@ def delete_bulk(doctype, items):
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
cat_tags = frappe.db.sql("""select `tag`.parent as `category`, `tag`.tag_name as `tag`
|
||||
from `tabTag Doc Category` as `docCat`
|
||||
INNER JOIN `tabTag` as `tag` on `tag`.parent = `docCat`.parent
|
||||
where `docCat`.tagdoc=%s
|
||||
ORDER BY `tag`.parent asc, `tag`.idx""", doctype, as_dict=1)
|
||||
|
||||
return {"defined_cat":cat_tags, "stats":get_stats(stats, doctype, filters)}
|
||||
if not frappe.cache().hget("tags_count", doctype):
|
||||
tags = [tag.name for tag in frappe.get_list("Tag")]
|
||||
_user_tags = []
|
||||
for tag in tags:
|
||||
count = frappe.db.count("Tag Link", filters={"document_type": doctype, "tag": tag})
|
||||
if count > 0:
|
||||
_user_tags.append([tag, count])
|
||||
frappe.cache().hset("tags_count", doctype, _user_tags)
|
||||
|
||||
return {"stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
import json
|
||||
"""
|
||||
Server side functions for tagging.
|
||||
|
||||
- Tags can be added to any record (doctype, name) in the system.
|
||||
- Items are filtered by tags
|
||||
- Top tags are shown in the sidebar (?)
|
||||
- Tags are also identified by the tag_fields property of the DocType
|
||||
|
||||
Discussion:
|
||||
|
||||
Tags are shown in the docbrowser and ideally where-ever items are searched.
|
||||
There should also be statistics available for tags (like top tags etc)
|
||||
|
||||
|
||||
Design:
|
||||
|
||||
- free tags (user_tags) are stored in __user_tags
|
||||
- doctype tags are set in tag_fields property of the doctype
|
||||
- top tags merges the tags from both the lists (only refreshes once an hour (max))
|
||||
|
||||
"""
|
||||
|
||||
import frappe
|
||||
from frappe.utils.global_search import update_global_search
|
||||
|
||||
def check_user_tags(dt):
|
||||
"if the user does not have a tags column, then it creates one"
|
||||
try:
|
||||
frappe.db.sql("select `_user_tags` from `tab%s` limit 1" % dt)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
DocTags(dt).setup()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_tag(tag, dt, dn, color=None):
|
||||
"adds a new tag to a record, and creates the Tag master"
|
||||
DocTags(dt).add(dn, tag)
|
||||
|
||||
return tag
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_tag(tag, dt, dn):
|
||||
"removes tag from the record"
|
||||
DocTags(dt).remove(dn, tag)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tagged_docs(doctype, tag):
|
||||
frappe.has_permission(doctype, throw=True)
|
||||
|
||||
return frappe.db.sql("""SELECT name
|
||||
FROM `tab{0}`
|
||||
WHERE _user_tags LIKE '%{1}%'""".format(doctype, tag))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tags(doctype, txt, cat_tags):
|
||||
tags = json.loads(cat_tags)
|
||||
try:
|
||||
for _user_tags in frappe.db.sql_list("""select DISTINCT `_user_tags`
|
||||
from `tab{0}`
|
||||
where _user_tags like {1}
|
||||
limit 50""".format(doctype, frappe.db.escape('%' + txt + '%'))):
|
||||
tags.extend(_user_tags[1:].split(","))
|
||||
except Exception as e:
|
||||
if not frappe.db.is_column_missing(e): raise
|
||||
return sorted(filter(lambda t: t and txt.lower() in t.lower(), list(set(tags))))
|
||||
|
||||
class DocTags:
|
||||
"""Tags for a particular doctype"""
|
||||
def __init__(self, dt):
|
||||
self.dt = dt
|
||||
|
||||
def get_tag_fields(self):
|
||||
"""returns tag_fields property"""
|
||||
return frappe.db.get_value('DocType', self.dt, 'tag_fields')
|
||||
|
||||
def get_tags(self, dn):
|
||||
"""returns tag for a particular item"""
|
||||
return (frappe.db.get_value(self.dt, dn, '_user_tags', ignore=1) or '').strip()
|
||||
|
||||
def add(self, dn, tag):
|
||||
"""add a new user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
if not tag in tl:
|
||||
tl.append(tag)
|
||||
self.update(dn, tl)
|
||||
|
||||
def remove(self, dn, tag):
|
||||
"""remove a user tag"""
|
||||
tl = self.get_tags(dn).split(',')
|
||||
self.update(dn, filter(lambda x:x.lower()!=tag.lower(), tl))
|
||||
|
||||
def remove_all(self, dn):
|
||||
"""remove all user tags (call before delete)"""
|
||||
self.update(dn, [])
|
||||
|
||||
def update(self, dn, tl):
|
||||
"""updates the _user_tag column in the table"""
|
||||
|
||||
if not tl:
|
||||
tags = ''
|
||||
else:
|
||||
tl = list(set(filter(lambda x: x, tl)))
|
||||
tags = ',' + ','.join(tl)
|
||||
try:
|
||||
frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \
|
||||
(self.dt,'%s','%s'), (tags , dn))
|
||||
doc= frappe.get_doc(self.dt, dn)
|
||||
update_global_search(doc)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
if not tags:
|
||||
# no tags, nothing to do
|
||||
return
|
||||
|
||||
self.setup()
|
||||
self.update(dn, tl)
|
||||
else: raise
|
||||
|
||||
def setup(self):
|
||||
"""adds the _user_tags column if not exists"""
|
||||
from frappe.database.schema import add_column
|
||||
add_column(self.dt, "_user_tags", "Data")
|
||||
|
|
@ -16,10 +16,11 @@ from frappe.core.doctype.file.file import remove_all
|
|||
from frappe.utils.password import delete_all_passwords_for
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
from frappe.utils.global_search import delete_for_document
|
||||
from frappe.desk.doctype.tag.tag import delete_tags_for_document
|
||||
from frappe.exceptions import FileNotFoundError
|
||||
|
||||
|
||||
doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log")
|
||||
doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log", "Tag Link")
|
||||
|
||||
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
|
||||
ignore_permissions=False, flags=None, ignore_on_trash=False, ignore_missing=True):
|
||||
|
|
@ -116,6 +117,8 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
|
|||
|
||||
# delete global search entry
|
||||
delete_for_document(doc)
|
||||
# delete tag link entry
|
||||
delete_tags_for_document(doc)
|
||||
|
||||
if doc and not for_reload:
|
||||
add_to_deleted_document(doc)
|
||||
|
|
|
|||
|
|
@ -252,3 +252,4 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table
|
|||
frappe.patches.v12_0.delete_duplicate_indexes
|
||||
frappe.patches.v12_0.set_default_incoming_email_port
|
||||
frappe.patches.v12_0.update_global_search
|
||||
frappe.patches.v12_0.setup_tags
|
||||
|
|
|
|||
31
frappe/patches/v12_0/setup_tags.py
Normal file
31
frappe/patches/v12_0/setup_tags.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.delete_doc_if_exists("DocType", "Tag Category")
|
||||
frappe.delete_doc_if_exists("DocType", "Tag Doc Category")
|
||||
|
||||
frappe.reload_doc("desk", "doctype", "tag")
|
||||
frappe.reload_doc("desk", "doctype", "tag_link")
|
||||
|
||||
tag_list = []
|
||||
tag_links = []
|
||||
time = frappe.utils.get_datetime()
|
||||
|
||||
for doctype in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}):
|
||||
for dt_tags in frappe.db.sql("select `name`, `_user_tags` from `tab{0}`".format(doctype.name), as_dict=True):
|
||||
tags = dt_tags.get("_user_tags").split(",") if dt_tags.get("_user_tags") else None
|
||||
if not tags:
|
||||
continue
|
||||
|
||||
for tag in tags:
|
||||
if not tag:
|
||||
continue
|
||||
|
||||
escaped_tag = frappe.db.escape(tag.strip())
|
||||
tag_list.append((escaped_tag, time, time, 'Administrator'))
|
||||
|
||||
tag_link_name = frappe.generate_hash(dt_tags.name + escaped_tag, 10),
|
||||
tag_links.append((tag_link_name, doctype.name, dt_tags.name, escaped_tag, time, time, 'Administrator'))
|
||||
|
||||
frappe.db.bulk_insert("Tag", fields=["name", "creation", "modified", "modified_by"], values=tag_list)
|
||||
frappe.db.bulk_insert("Tag Link", fields=["name", "document_type", "document_name", "tag", "creation", "modified", "modified_by"], values=tag_links)
|
||||
|
|
@ -186,6 +186,7 @@
|
|||
"public/js/frappe/ui/toolbar/awesome_bar.js",
|
||||
"public/js/frappe/ui/toolbar/energy_points_notifications.js",
|
||||
"public/js/frappe/ui/toolbar/search.js",
|
||||
"public/js/frappe/ui/toolbar/tag_utils.js",
|
||||
"public/js/frappe/ui/toolbar/search.html",
|
||||
"public/js/frappe/ui/toolbar/search_header.html",
|
||||
"public/js/frappe/ui/toolbar/search_utils.js",
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ frappe.Application = Class.extend({
|
|||
});
|
||||
}, 300000); // check every 5 minutes
|
||||
}
|
||||
|
||||
this.fetch_tags();
|
||||
},
|
||||
|
||||
setup_frappe_vue() {
|
||||
|
|
@ -599,6 +601,10 @@ frappe.Application = Class.extend({
|
|||
frappe.show_alert(message);
|
||||
});
|
||||
},
|
||||
|
||||
fetch_tags() {
|
||||
frappe.tags.utils.fetch_tags();
|
||||
}
|
||||
});
|
||||
|
||||
frappe.get_module = function(m, default_module) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
this.frm.shared.refresh();
|
||||
this.frm.follow.refresh();
|
||||
this.frm.viewers.refresh();
|
||||
this.frm.tags && this.frm.tags.refresh(this.frm.doc._user_tags);
|
||||
this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags);
|
||||
this.sidebar.find(".modified-by").html(__("{0} edited this {1}",
|
||||
["<strong>" + frappe.user.full_name(this.frm.doc.modified_by) + "</strong>",
|
||||
"<br>" + comment_when(this.frm.doc.modified)]));
|
||||
|
|
@ -119,7 +119,6 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
},
|
||||
|
||||
make_tags: function() {
|
||||
var me = this;
|
||||
if (this.frm.meta.issingle) {
|
||||
this.sidebar.find(".form-tags").toggle(false);
|
||||
return;
|
||||
|
|
@ -129,7 +128,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
parent: this.sidebar.find(".tag-area"),
|
||||
frm: this.frm,
|
||||
on_change: function(user_tags) {
|
||||
me.frm.doc._user_tags = user_tags;
|
||||
this.frm.tags && this.frm.tags.refresh(user_tags);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -288,29 +288,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
filters: me.default_filters || []
|
||||
},
|
||||
callback: function(r) {
|
||||
me.defined_category = r.message;
|
||||
if (r.message.defined_cat) {
|
||||
me.defined_category = r.message.defined_cat;
|
||||
me.cats = {};
|
||||
//structure the tag categories
|
||||
for (var i in me.defined_category) {
|
||||
if (me.cats[me.defined_category[i].category] === undefined) {
|
||||
me.cats[me.defined_category[i].category] = [me.defined_category[i].tag];
|
||||
} else {
|
||||
me.cats[me.defined_category[i].category].push(me.defined_category[i].tag);
|
||||
}
|
||||
me.cat_tags[i] = me.defined_category[i].tag;
|
||||
}
|
||||
me.tempstats = r.message.stats;
|
||||
|
||||
$.each(me.cats, function(i, v) {
|
||||
me.render_stat(i, (me.tempstats || {})["_user_tags"], v);
|
||||
});
|
||||
me.render_stat("_user_tags", (me.tempstats || {})["_user_tags"]);
|
||||
} else {
|
||||
//render normal stats
|
||||
me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]);
|
||||
}
|
||||
me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]);
|
||||
let stats_dropdown = me.sidebar.find('.list-stats-dropdown');
|
||||
me.setup_dropdown_search(stats_dropdown,'.stat-label');
|
||||
}
|
||||
|
|
@ -355,13 +333,14 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
field: field,
|
||||
stat: stats,
|
||||
sum: sum,
|
||||
label: field === '_user_tags' ? (tags ? __(label) : __("Tags")) : __(label),
|
||||
label: field === '_user_tags' ? (tags ? __(label) : __("Tag")) : __(label),
|
||||
};
|
||||
$(frappe.render_template("list_sidebar_stat", context))
|
||||
.on("click", ".stat-link", function() {
|
||||
var doctype = "Tag Link";
|
||||
var fieldname = $(this).attr('data-field');
|
||||
var label = $(this).attr('data-label');
|
||||
var condition = "like";
|
||||
var condition = "=";
|
||||
var existing = me.list_view.filter_area.filter_list.get_filter(fieldname);
|
||||
if(existing) {
|
||||
existing.remove();
|
||||
|
|
@ -370,7 +349,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
label = "%,%";
|
||||
condition = "not like";
|
||||
}
|
||||
me.list_view.filter_area.filter_list.add_filter(me.list_view.doctype, fieldname, condition, label)
|
||||
me.list_view.filter_area.filter_list.add_filter(doctype, fieldname, condition, label)
|
||||
.then(function() {
|
||||
me.list_view.refresh();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
tag_editor.wrapper.on('click', '.tagit-label', (e) => {
|
||||
const $this = $(e.currentTarget);
|
||||
this.filter_area.add(this.doctype, '_user_tags', '=', $this.text());
|
||||
this.filter_area.add('Tag Link', 'tag', '=', $this.text());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,13 @@ frappe.ui.Filter = class {
|
|||
if(this.field) for(let k in this.field.df) cur[k] = this.field.df[k];
|
||||
|
||||
let original_docfield = (this.fieldselect.fields_by_name[doctype] || {})[fieldname];
|
||||
|
||||
if (doctype === "Tag Link" || fieldname === "_user_tags") {
|
||||
original_docfield = {fieldname: "tag", fieldtype: "Data", label: "Tags", parent: "Tag Link"};
|
||||
doctype = "Tag Link";
|
||||
condition = "=";
|
||||
}
|
||||
|
||||
if(!original_docfield) {
|
||||
console.warn(`Field ${fieldname} is not selectable.`);
|
||||
this.remove();
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ frappe.ui.TagEditor = Class.extend({
|
|||
onTagAdd: (tag) => {
|
||||
if(me.initialized && !me.refreshing) {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.tags.add_tag',
|
||||
method: "frappe.desk.doctype.tag.tag.add_tag",
|
||||
args: me.get_args(tag),
|
||||
callback: function(r) {
|
||||
var user_tags = me.user_tags ? me.user_tags.split(",") : [];
|
||||
user_tags.push(tag)
|
||||
me.user_tags = user_tags.join(",");
|
||||
me.on_change && me.on_change(me.user_tags);
|
||||
frappe.tags.utils.fetch_tags();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -49,13 +50,14 @@ frappe.ui.TagEditor = Class.extend({
|
|||
onTagRemove: (tag) => {
|
||||
if(!me.refreshing) {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.tags.remove_tag',
|
||||
method: "frappe.desk.doctype.tag.tag.remove_tag",
|
||||
args: me.get_args(tag),
|
||||
callback: function(r) {
|
||||
var user_tags = me.user_tags.split(",");
|
||||
user_tags.splice(user_tags.indexOf(tag), 1);
|
||||
me.user_tags = user_tags.join(",");
|
||||
me.on_change && me.on_change(me.user_tags);
|
||||
frappe.tags.utils.fetch_tags();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -82,12 +84,10 @@ frappe.ui.TagEditor = Class.extend({
|
|||
$input.on("input", function(e) {
|
||||
var value = e.target.value;
|
||||
frappe.call({
|
||||
method:"frappe.desk.tags.get_tags",
|
||||
method: "frappe.desk.doctype.tag.tag.get_tags",
|
||||
args:{
|
||||
doctype: me.frm.doctype,
|
||||
txt: value.toLowerCase(),
|
||||
cat_tags: me.list_sidebar ?
|
||||
JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]'
|
||||
},
|
||||
callback: function(r) {
|
||||
me.awesomplete.list = r.message;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ frappe.ui.Tags = class {
|
|||
}
|
||||
|
||||
addTag(label) {
|
||||
label = toTitle(label);
|
||||
if(label && label!== '' && !this.tagsList.includes(label)) {
|
||||
let $tag = this.getTag(label);
|
||||
this.getListElement($tag).insertBefore(this.$inputWrapper);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
frappe.provide('frappe.search');
|
||||
frappe.provide('frappe.tags');
|
||||
|
||||
frappe.search.AwesomeBar = Class.extend({
|
||||
setup: function(element) {
|
||||
|
|
@ -140,6 +141,8 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
__("document type..., e.g. customer")+'</td></tr>\
|
||||
<tr><td>'+__("Search in a document type")+'</td><td>'+
|
||||
__("text in document type")+'</td></tr>\
|
||||
<tr><td>'+__("Tags")+'</td><td>'+
|
||||
__("tag name..., e.g. #tag")+'</td></tr>\
|
||||
<tr><td>'+__("Open a module or tool")+'</td><td>'+
|
||||
__("module name...")+'</td></tr>\
|
||||
<tr><td>'+__("Calculate")+'</td><td>'+
|
||||
|
|
@ -177,6 +180,9 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
frappe.search.utils.get_recent_pages(txt || ""),
|
||||
frappe.search.utils.get_executables(txt)
|
||||
);
|
||||
if (txt.charAt(0) === "#") {
|
||||
options = frappe.tags.utils.get_tags(txt);
|
||||
}
|
||||
var out = this.deduplicate(options);
|
||||
return out.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
|
|
@ -215,6 +221,11 @@ frappe.search.AwesomeBar = Class.extend({
|
|||
|
||||
make_global_search: function(txt) {
|
||||
var me = this;
|
||||
|
||||
if (txt.charAt(0) === "#") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.push({
|
||||
label: __("Search for '{0}'", [txt.bold()]),
|
||||
value: __("Search for '{0}'", [txt]),
|
||||
|
|
|
|||
|
|
@ -124,6 +124,11 @@ frappe.search.SearchDialog = Class.extend({
|
|||
// Help results
|
||||
// this.$modal_body.on('click', 'a[data-path]', frappe.help.show_results);
|
||||
this.bind_keyboard_events();
|
||||
|
||||
// Setup Minimizable functionality
|
||||
this.search_dialog.minimizable = true;
|
||||
this.search_dialog.is_minimized = false;
|
||||
this.search_dialog.$wrapper.find('.btn-modal-minimize').click(() => this.toggle_minimize());
|
||||
},
|
||||
|
||||
bind_keyboard_events: function() {
|
||||
|
|
@ -178,7 +183,17 @@ frappe.search.SearchDialog = Class.extend({
|
|||
} else {
|
||||
this.$search_modal.find('.loading-state').removeClass('hide');
|
||||
}
|
||||
|
||||
if (this.current_keyword.charAt(0) === "#") {
|
||||
this.search = this.searches["tags"];
|
||||
} else {
|
||||
this.search = this.searches["global_search"];
|
||||
}
|
||||
|
||||
this.search.get_results(keywords, this.parse_results.bind(this));
|
||||
if (this.search_dialog.is_minimized) {
|
||||
this.toggle_minimize();
|
||||
}
|
||||
},
|
||||
|
||||
parse_results: function(result_sets, keyword) {
|
||||
|
|
@ -308,7 +323,7 @@ frappe.search.SearchDialog = Class.extend({
|
|||
frappe.route_options = result.route_options;
|
||||
}
|
||||
$result.on('click', (e) => {
|
||||
this.search_dialog.hide();
|
||||
this.toggle_minimize();
|
||||
if(result.onclick) {
|
||||
result.onclick(result.match);
|
||||
} else {
|
||||
|
|
@ -353,12 +368,25 @@ frappe.search.SearchDialog = Class.extend({
|
|||
this.$modal_body.find('.more-results.last').slideDown(200, function() {});
|
||||
},
|
||||
|
||||
get_minimize_btn: function() {
|
||||
return this.search_dialog.$wrapper.find(".modal-header .btn-modal-minimize");
|
||||
},
|
||||
|
||||
toggle_minimize: function() {
|
||||
let modal = this.search_dialog.$wrapper.closest('.modal').toggleClass('modal-minimize');
|
||||
modal.attr('tabindex') ? modal.removeAttr('tabindex') : modal.attr('tabindex', -1);
|
||||
this.get_minimize_btn().find('i').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
|
||||
this.search_dialog.is_minimized = !this.search_dialog.is_minimized;
|
||||
this.on_minimize_toggle && this.on_minimize_toggle(this.search_dialog.is_minimized);
|
||||
this.search_dialog.header.find('.modal-title').toggleClass('cursor-pointer');
|
||||
},
|
||||
|
||||
// Search objects
|
||||
searches: {
|
||||
global_search: {
|
||||
input_placeholder: __("Global Search"),
|
||||
input_placeholder: __("Search"),
|
||||
empty_state_text: __("Search for anything"),
|
||||
no_results_status: (keyword) => __("<p>No results found for '" + keyword + "' in Global Search</p>"),
|
||||
no_results_status: (keyword) => "<p>" + __("No results found for {0} in Global Search", [keyword]) + "</p>",
|
||||
|
||||
get_results: function(keywords, callback) {
|
||||
var start = 0, limit = 1000;
|
||||
|
|
@ -372,6 +400,22 @@ frappe.search.SearchDialog = Class.extend({
|
|||
});
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
input_placeholder: __("Search"),
|
||||
empty_state_text: __("Search for anything"),
|
||||
no_results_status: (keyword) => "<p>" + __("No documents found tagged with {0}", [keyword]) + "</p>",
|
||||
|
||||
get_results: function(keywords, callback) {
|
||||
var results = frappe.search.utils.get_nav_results(keywords);
|
||||
frappe.tags.utils.get_tag_results(keywords)
|
||||
.then(function(global_results) {
|
||||
results = results.concat(global_results);
|
||||
callback(results, keywords);
|
||||
}, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
<div class="search-header">
|
||||
<i class="octicon octicon-search"></i>
|
||||
<input type="text" class="form-control search-input" style="padding-left: 15px">
|
||||
<p class="loading-state hide" style="margin: 0px 20px; color:#d4d9dd">{%= __("Searching")%} ...</p>
|
||||
<a type="button" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<i class="octicon octicon-search"></i>
|
||||
<input type="text" class="form-control search-input" style="padding-left: 15px">
|
||||
<p class="loading-state hide" style="margin: 0px 20px; color:#d4d9dd">{%= __("Searching")%} ...</p>
|
||||
<a type="button" class="btn btn-default btn-sm btn-modal-minimize" style="margin-right: 2px;">
|
||||
<i class="octicon octicon-chevron-down" style="padding: 1px 0px;"></i>
|
||||
</a>
|
||||
<a type="button" class="btn btn-default btn-sm btn-modal-close" data-dismiss="modal" aria-hidden="true">
|
||||
<i class="octicon octicon-x visible-xs" style="padding: 1px 0px;"></i>
|
||||
<span class="hidden-xs">Close</span>
|
||||
</a>
|
||||
</div>
|
||||
108
frappe/public/js/frappe/ui/toolbar/tag_utils.js
Normal file
108
frappe/public/js/frappe/ui/toolbar/tag_utils.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide("frappe.tags");
|
||||
|
||||
frappe.tags.utils = {
|
||||
get_tags: function(txt) {
|
||||
txt = txt.slice(1);
|
||||
let out = [];
|
||||
|
||||
for (let i in frappe.tags.tags) {
|
||||
let tag = frappe.tags.tags[i];
|
||||
let level = frappe.search.utils.fuzzy_search(txt, tag);
|
||||
if (level) {
|
||||
out.push({
|
||||
type: "Tag",
|
||||
label: __("#{0}", [frappe.search.utils.bolden_match_part(__(tag), txt)]),
|
||||
value: __("#{0}", [__(tag)]),
|
||||
index: 1 + level,
|
||||
match: tag,
|
||||
onclick() {
|
||||
// Use Global Search Dialog for tag search too.
|
||||
frappe.searchdialog.search.init_search("#".concat(tag), "tags");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
fetch_tags() {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.tag.tag.get_tags_list_for_awesomebar",
|
||||
callback: function(r) {
|
||||
if (r && r.message) {
|
||||
frappe.tags.tags = $.extend([], r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_tag_results: function(tag) {
|
||||
function get_results_sets(data) {
|
||||
var results_sets = [], result, set;
|
||||
function get_existing_set(doctype) {
|
||||
return results_sets.find(function(set) {
|
||||
return set.title === doctype;
|
||||
});
|
||||
}
|
||||
|
||||
function make_description(content) {
|
||||
var field_length = 110;
|
||||
var field_value = null;
|
||||
if (content.length > field_length) {
|
||||
field_value = content.slice(0, field_length) + "...";
|
||||
} else {
|
||||
var length = content.length;
|
||||
field_value = content.slice(0, length) + "...";
|
||||
}
|
||||
return field_value;
|
||||
}
|
||||
|
||||
data.forEach(function(d) {
|
||||
// more properties
|
||||
var description = "";
|
||||
if (d.content) {
|
||||
description = make_description(d.content);
|
||||
}
|
||||
result = {
|
||||
label: d.name,
|
||||
value: d.name,
|
||||
description: description,
|
||||
route: ['Form', d.doctype, d.name],
|
||||
|
||||
};
|
||||
set = get_existing_set(d.doctype);
|
||||
if (set) {
|
||||
set.results.push(result);
|
||||
} else {
|
||||
set = {
|
||||
title: d.doctype,
|
||||
results: [result],
|
||||
fetch_type: "Global"
|
||||
};
|
||||
results_sets.push(set);
|
||||
}
|
||||
|
||||
});
|
||||
return results_sets;
|
||||
}
|
||||
return new Promise(function(resolve) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.tag.tag.get_documents_for_tag",
|
||||
args: {
|
||||
tag: tag
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
resolve(get_results_sets(r.message));
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -242,10 +242,6 @@ def update_global_search(doc):
|
|||
if doc.get(field.fieldname) and field.fieldtype not in frappe.model.table_fields:
|
||||
content.append(get_formatted_value(doc.get(field.fieldname), field))
|
||||
|
||||
tags = (doc.get('_user_tags') or '').strip()
|
||||
if tags:
|
||||
content.extend(list(filter(lambda x: x, tags.split(','))))
|
||||
|
||||
# Get children
|
||||
for child in doc.meta.get_table_fields():
|
||||
for d in doc.get(child.fieldname):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue