feat(doctype-urls): pretty doctype urls and removed social JS code
This commit is contained in:
parent
c826d489e4
commit
248e9cd7d1
30 changed files with 222 additions and 1931 deletions
|
|
@ -18,7 +18,7 @@ global_cache_keys = ("app_hooks", "installed_apps",
|
|||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
|
||||
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts',
|
||||
'sitemap_routes', 'db_tables') + doctype_map_keys
|
||||
'sitemap_routes', 'db_tables', 'doctype_name_map') + doctype_map_keys
|
||||
|
||||
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
"defaults", "user_permissions", "home_page", "linked_with",
|
||||
|
|
@ -67,19 +67,17 @@ def clear_defaults_cache(user=None):
|
|||
elif frappe.flags.in_install!="frappe":
|
||||
frappe.cache().delete_key("defaults")
|
||||
|
||||
def clear_document_cache():
|
||||
frappe.local.document_cache = {}
|
||||
frappe.cache().delete_key("document_cache")
|
||||
|
||||
def clear_doctype_cache(doctype=None):
|
||||
cache = frappe.cache()
|
||||
|
||||
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
|
||||
del frappe.local.meta_cache[doctype]
|
||||
|
||||
for key in ('is_table', 'doctype_modules'):
|
||||
for key in ('is_table', 'doctype_modules', 'doctype_name_map', 'document_cache'):
|
||||
cache.delete_value(key)
|
||||
|
||||
frappe.local.document_cache = {}
|
||||
|
||||
def clear_single(dt):
|
||||
for name in doctype_cache_keys:
|
||||
cache.hdel(name, dt)
|
||||
|
|
@ -101,9 +99,6 @@ def clear_doctype_cache(doctype=None):
|
|||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
|
||||
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
|
||||
clear_document_cache()
|
||||
|
||||
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
||||
cache = frappe.cache()
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
|
|
|||
16
frappe/desk/utils.py
Normal file
16
frappe/desk/utils.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_doctype_name(name):
|
||||
# translates the doctype name from url to name `sales-order` to `Sales Order`
|
||||
def get_name_map():
|
||||
name_map = {}
|
||||
for d in frappe.get_all('DocType'):
|
||||
name_map[d.name.lower().replace(' ', '-')] = d.name
|
||||
|
||||
return name_map
|
||||
|
||||
return frappe.cache().get_value('doctype_name_map', get_name_map).get(name, name)
|
||||
|
|
@ -85,7 +85,6 @@
|
|||
"public/less/mobile.less",
|
||||
"public/less/controls.less",
|
||||
"public/less/chat.less",
|
||||
"public/less/social.less",
|
||||
"public/css/fonts/inter/inter.css",
|
||||
"public/scss/desk.scss",
|
||||
"node_modules/frappe-charts/dist/frappe-charts.min.css",
|
||||
|
|
@ -217,7 +216,6 @@
|
|||
"public/js/frappe/utils/rating_icons.html",
|
||||
|
||||
"public/js/frappe/chat.js",
|
||||
"public/js/frappe/social/social_factory.js",
|
||||
"public/js/frappe/utils/energy_point_utils.js",
|
||||
"public/js/frappe/utils/dashboard_utils.js",
|
||||
"public/js/frappe/ui/chart.js",
|
||||
|
|
@ -303,7 +301,6 @@
|
|||
"node_modules/frappe-datatable/dist/frappe-datatable.css"
|
||||
],
|
||||
"css/email.css": "public/less/email.less",
|
||||
"js/social.min.js": "public/js/frappe/social/social_home.js",
|
||||
"js/barcode_scanner.min.js": "public/js/frappe/barcode_scanner/quagga.js",
|
||||
"js/data_import_tools.min.js": "public/js/frappe/data_import/index.js",
|
||||
"css/login.css": "public/scss/login.scss"
|
||||
|
|
|
|||
|
|
@ -173,9 +173,6 @@ frappe.Application = Class.extend({
|
|||
if (frappe.boot && localStorage.getItem("session_last_route")) {
|
||||
frappe.set_route(localStorage.getItem("session_last_route"));
|
||||
localStorage.removeItem("session_last_route");
|
||||
} else if (frappe._cur_route) {
|
||||
// go to the appropriate sub-path
|
||||
frappe.set_route(frappe._cur_route);
|
||||
} else {
|
||||
// route to home page
|
||||
frappe.route();
|
||||
|
|
|
|||
|
|
@ -1007,7 +1007,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
return;
|
||||
}
|
||||
|
||||
frappe.re_route[frappe.get_sub_path()] = 'Form/' + encodeURIComponent(this.doctype) + '/' + encodeURIComponent(name);
|
||||
frappe.re_route[frappe.router.get_sub_path()] = 'Form/' + encodeURIComponent(this.doctype) + '/' + encodeURIComponent(name);
|
||||
frappe.set_route('Form', this.doctype, name);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ frappe.view_factory = {};
|
|||
frappe.view_factories = [];
|
||||
frappe.route_options = null;
|
||||
frappe.route_hooks = {};
|
||||
frappe._cur_route = null;
|
||||
|
||||
$(window).on('hashchange', function() {
|
||||
// v1 style routing, route is in hash
|
||||
if (window.location.hash) {
|
||||
let sub_path = frappe.get_sub_path(window.location.hash);
|
||||
let sub_path = frappe.router.get_sub_path(window.location.hash);
|
||||
window.location.hash = '';
|
||||
frappe.push_state(sub_path);
|
||||
frappe.router.push_state(sub_path);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ window.addEventListener('popstate', (event) => {
|
|||
$('body').on('click', 'a', function(e) {
|
||||
let override = (e, route) => {
|
||||
e.preventDefault();
|
||||
frappe.push_state(frappe.get_sub_path(route));
|
||||
frappe.router.push_state(frappe.router.get_sub_path(route));
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -58,51 +57,83 @@ $('body').on('click', 'a', function(e) {
|
|||
}
|
||||
});
|
||||
|
||||
frappe.route = function() {
|
||||
frappe.router = {
|
||||
current_route: null,
|
||||
doctype_names: {},
|
||||
route() {
|
||||
// resolve the route from the URL or hash
|
||||
// translate it so the objects are well defined
|
||||
// and render the page as required
|
||||
|
||||
// Application is not yet initiated
|
||||
if (!frappe.app) return;
|
||||
if (!frappe.app) return;
|
||||
|
||||
let sub_path = frappe.get_sub_path();
|
||||
let sub_path = frappe.router.get_sub_path();
|
||||
if (frappe.router.re_route(sub_path)) return;
|
||||
|
||||
if (frappe.re_route[sub_path] !== undefined) {
|
||||
// after saving a doc, for example,
|
||||
// "New DocType 1" and the renamed "TestDocType", both exist in history
|
||||
// now if we try to go back,
|
||||
// it doesn't allow us to go back to the one prior to "New DocType 1"
|
||||
// Hence if this check is true, instead of changing location hash,
|
||||
// we just do a back to go to the doc previous to the "New DocType 1"
|
||||
var re_route_val = frappe.get_sub_path(frappe.re_route[sub_path]);
|
||||
if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) {
|
||||
window.history.back();
|
||||
return;
|
||||
} else {
|
||||
frappe.set_route(re_route_val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
frappe.router.translate_doctype_name().then(() => {
|
||||
let route = frappe.router.current_route;
|
||||
|
||||
frappe._cur_route = sub_path;
|
||||
frappe.router.set_history(route, sub_path);
|
||||
|
||||
let route = frappe.get_route();
|
||||
if (route[0]) {
|
||||
frappe.router.render_page(route);
|
||||
} else {
|
||||
// Show home
|
||||
frappe.views.pageview.show('');
|
||||
}
|
||||
|
||||
frappe.route_history.push(route);
|
||||
frappe.router.set_title();
|
||||
frappe.route.trigger('change');
|
||||
});
|
||||
},
|
||||
|
||||
// set title
|
||||
frappe.route_titles[sub_path] = frappe._original_title || document.title;
|
||||
translate_doctype_name() {
|
||||
return new Promise((resolve) => {
|
||||
let route = frappe.router.parse();
|
||||
let factory = route[0].toLowerCase();
|
||||
let done = () => {
|
||||
route[1] = frappe.router.doctype_names[route[1]];
|
||||
frappe.router.current_route = route;
|
||||
resolve();
|
||||
};
|
||||
|
||||
// hide open dialog
|
||||
frappe.ui.hide_open_dialog();
|
||||
if (['form', 'list', 'report', 'tree'].includes(factory)) {
|
||||
// translate the doctype to its original name
|
||||
if (frappe.router.doctype_names[route[1]]) {
|
||||
done();
|
||||
} else {
|
||||
frappe.xcall('frappe.desk.utils.get_doctype_name', {name: route[1]}).then((data) => {
|
||||
frappe.router.doctype_names[route[1]] = data;
|
||||
done();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_history(route, sub_path) {
|
||||
frappe.route_history.push(route);
|
||||
frappe.route_titles[sub_path] = frappe._original_title || document.title;
|
||||
frappe.ui.hide_open_dialog();
|
||||
},
|
||||
|
||||
render_page(route) {
|
||||
// create the page generator (factory) object and call `show`
|
||||
// if there is no generator, render the `Page` object
|
||||
|
||||
// first the router needs to know if its a "page", "doctype", "workspace"
|
||||
|
||||
if (route[0]) {
|
||||
const title_cased_route = frappe.utils.to_title_case(route[0]);
|
||||
if (title_cased_route === 'Workspace') {
|
||||
frappe.views.pageview.show('');
|
||||
return;
|
||||
}
|
||||
|
||||
if (route[1] && frappe.views[title_cased_route + "Factory"]) {
|
||||
// has a view generator, generate!
|
||||
if(!frappe.view_factory[title_cased_route]) {
|
||||
if (!frappe.view_factory[title_cased_route]) {
|
||||
frappe.view_factory[title_cased_route] = new frappe.views[title_cased_route + "Factory"]();
|
||||
}
|
||||
|
||||
|
|
@ -114,44 +145,127 @@ frappe.route = function() {
|
|||
frappe.views.pageview.show(route_name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Show home
|
||||
frappe.views.pageview.show('');
|
||||
},
|
||||
|
||||
re_route(sub_path) {
|
||||
if (frappe.re_route[sub_path] !== undefined) {
|
||||
// after saving a doc, for example,
|
||||
// "New DocType 1" and the renamed "TestDocType", both exist in history
|
||||
// now if we try to go back,
|
||||
// it doesn't allow us to go back to the one prior to "New DocType 1"
|
||||
// Hence if this check is true, instead of changing location hash,
|
||||
// we just do a back to go to the doc previous to the "New DocType 1"
|
||||
var re_route_val = frappe.router.get_sub_path(frappe.re_route[sub_path]);
|
||||
if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) {
|
||||
window.history.back();
|
||||
return true;
|
||||
} else {
|
||||
frappe.set_route(re_route_val);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
set_title(sub_path) {
|
||||
if (frappe.route_titles[sub_path]) {
|
||||
frappe.utils.set_title(frappe.route_titles[sub_path]);
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
frappe.route_titles[frappe.get_route_str()] = frappe._original_title || document.title;
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
push_state(route) {
|
||||
// change the URL and call the router
|
||||
let url = route;
|
||||
if (!route.startsWith('/app/')) {
|
||||
url = `/app/${route}`;
|
||||
}
|
||||
if (window.location.pathname !== url) {
|
||||
// cleanup any remenants of v1 routing
|
||||
window.location.hash = '';
|
||||
|
||||
// push state so the browser looks fine
|
||||
history.pushState(null, null, url);
|
||||
|
||||
// now process the route
|
||||
frappe.router.route();
|
||||
}
|
||||
},
|
||||
|
||||
parse(route) {
|
||||
route = frappe.router.get_sub_path_string(route).split('/');
|
||||
route = $.map(route, frappe.router.decode_component);
|
||||
|
||||
frappe.router.set_route_options(route);
|
||||
|
||||
return route;
|
||||
},
|
||||
|
||||
get_sub_path_string(route) {
|
||||
// return clean sub_path from hash or url
|
||||
// supports both v1 and v2 routing
|
||||
if (!route) {
|
||||
route = window.location.hash || window.location.pathname;
|
||||
}
|
||||
|
||||
return frappe.router.strip_prefix(route);
|
||||
},
|
||||
|
||||
strip_prefix(route) {
|
||||
if (route.substr(0, 1)=='/') route = route.substr(1); // for /app/sub
|
||||
if (route.startsWith('app')) route = route.substr(4); // for desk/sub
|
||||
if (route.substr(0, 1)=='/') route = route.substr(1);
|
||||
if (route.substr(0, 1)=='#') route = route.substr(1);
|
||||
if (route.substr(0, 1)=='!') route = route.substr(1);
|
||||
return route;
|
||||
},
|
||||
|
||||
get_sub_path(route) {
|
||||
var sub_path = frappe.router.get_sub_path_string(route);
|
||||
route = $.map(sub_path.split('/'), frappe.router.decode_component).join('/');
|
||||
|
||||
return route;
|
||||
},
|
||||
|
||||
set_route_options(route) {
|
||||
// set query parameters as frappe.route_options
|
||||
var last_part = route[route.length - 1];
|
||||
if (last_part.indexOf("?") < last_part.indexOf("=")) {
|
||||
// has ? followed by =
|
||||
let parts = last_part.split("?");
|
||||
|
||||
// route should not contain string after ?
|
||||
route[route.length - 1] = parts[0];
|
||||
|
||||
let query_params = frappe.utils.get_query_params(parts[1]);
|
||||
frappe.route_options = $.extend(frappe.route_options || {}, query_params);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
decode_component(r) {
|
||||
try {
|
||||
return decodeURIComponent(r);
|
||||
} catch (e) {
|
||||
if (e instanceof URIError) {
|
||||
// legacy: not sure why URIError is ignored.
|
||||
return r;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
slug(name) {
|
||||
return name.toLowerCase().replace(/ /g, '-');
|
||||
}
|
||||
|
||||
|
||||
if (frappe.route_titles[sub_path]) {
|
||||
frappe.utils.set_title(frappe.route_titles[sub_path]);
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
frappe.route_titles[frappe.get_route_str()] = frappe._original_title || document.title;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
frappe.route.trigger('change');
|
||||
|
||||
};
|
||||
|
||||
frappe.get_route = function(route) {
|
||||
// for app
|
||||
route = frappe.get_sub_path_string(route).split('/');
|
||||
route = $.map(route, frappe._decode_str);
|
||||
var parts = null;
|
||||
var doc_name = route[route.length - 1];
|
||||
// if the last part contains ? then check if it is valid query string
|
||||
if (doc_name.indexOf("?") < doc_name.indexOf("=")) {
|
||||
parts = doc_name.split("?");
|
||||
route[route.length - 1] = parts[0];
|
||||
} else {
|
||||
parts = doc_name;
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
var query_params = frappe.utils.get_query_params(parts[1]);
|
||||
frappe.route_options = $.extend(frappe.route_options || {}, query_params);
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
frappe.route = frappe.router.route;
|
||||
frappe.get_route = () => frappe.router.current_route;
|
||||
frappe.get_route_str = () => frappe.router.current_route.join('/');
|
||||
|
||||
frappe.get_prev_route = function() {
|
||||
if (frappe.route_history && frappe.route_history.length > 1) {
|
||||
|
|
@ -161,44 +275,6 @@ frappe.get_prev_route = function() {
|
|||
}
|
||||
}
|
||||
|
||||
frappe._decode_str = function(r) {
|
||||
try {
|
||||
return decodeURIComponent(r);
|
||||
} catch (e) {
|
||||
if (e instanceof URIError) {
|
||||
return r;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frappe.get_sub_path_string = function(route) {
|
||||
// return clean sub_path from hash or url
|
||||
// supports both v1 and v2 routing
|
||||
|
||||
if (!route) {
|
||||
route = window.location.hash;
|
||||
}
|
||||
if (!route) {
|
||||
route = window.location.pathname;
|
||||
}
|
||||
|
||||
if (route.substr(0, 1)=='/') route = route.substr(1); // for /app/sub
|
||||
if (route.startsWith('app')) route = route.substr(4); // for desk/sub
|
||||
if (route.substr(0, 1)=='/') route = route.substr(1);
|
||||
if (route.substr(0, 1)=='#') route = route.substr(1);
|
||||
if (route.substr(0, 1)=='!') route = route.substr(1);
|
||||
|
||||
return route;
|
||||
};
|
||||
|
||||
frappe.get_sub_path = frappe.get_route_str = function(route) {
|
||||
var sub_path = frappe.get_sub_path_string(route);
|
||||
route = $.map(sub_path.split('/'), frappe._decode_str).join('/');
|
||||
|
||||
return route;
|
||||
};
|
||||
|
||||
frappe.set_route = function() {
|
||||
return new Promise(resolve => {
|
||||
|
|
@ -230,7 +306,7 @@ frappe.set_route = function() {
|
|||
// window.location.hash = route;
|
||||
|
||||
// routing v2
|
||||
frappe.push_state(route);
|
||||
frappe.router.push_state(route);
|
||||
}
|
||||
|
||||
// Set favicon (app.js)
|
||||
|
|
@ -245,24 +321,10 @@ frappe.set_route = function() {
|
|||
});
|
||||
};
|
||||
|
||||
frappe.push_state = function (route) {
|
||||
let url = `/app/${route}`;
|
||||
if (window.location.pathname !== url) {
|
||||
// cleanup any remenants of v1 routing
|
||||
window.location.hash = '';
|
||||
|
||||
// push state so the browser looks fine
|
||||
history.pushState(null, null, url);
|
||||
|
||||
// now process the route
|
||||
frappe.route();
|
||||
}
|
||||
};
|
||||
|
||||
frappe.set_re_route = function() {
|
||||
var tmp = frappe.get_sub_path();
|
||||
var tmp = frappe.router.get_sub_path();
|
||||
frappe.set_route.apply(null, arguments);
|
||||
frappe.re_route[tmp] = frappe.get_sub_path();
|
||||
frappe.re_route[tmp] = frappe.router.get_sub_path();
|
||||
};
|
||||
|
||||
frappe.has_route_options = function() {
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
<template>
|
||||
<div ref="social" class="social">
|
||||
<keep-alive>
|
||||
<component :is="current_page.component" v-bind="current_page.props"></component>
|
||||
</keep-alive>
|
||||
<image-viewer :src="preview_image_src" v-if="show_preview"></image-viewer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Wall from './pages/Wall.vue';
|
||||
import Profile from './pages/Profile.vue';
|
||||
import UserList from './pages/UserList.vue';
|
||||
import NotFound from './components/NotFound.vue';
|
||||
import ImageViewer from './components/ImageViewer.vue';
|
||||
|
||||
function get_route_map() {
|
||||
return {
|
||||
'social/home': {
|
||||
'component': Wall,
|
||||
'props': {}
|
||||
},
|
||||
'social/profile/*': {
|
||||
'component': Profile,
|
||||
'props': {
|
||||
'user_id': frappe.get_route()[2],
|
||||
'key': frappe.get_route()[2]
|
||||
}
|
||||
},
|
||||
'social/users': {
|
||||
'component': UserList,
|
||||
'props': {}
|
||||
},
|
||||
'not_found': {
|
||||
'component': NotFound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ImageViewer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
current_page: this.get_current_page(),
|
||||
show_preview: false,
|
||||
preview_image_src: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$root.$on('show_preview', (src) => {
|
||||
this.preview_image_src = src;
|
||||
this.show_preview = true;
|
||||
})
|
||||
|
||||
this.$root.$on('hide_preview', () => {
|
||||
this.preview_image_src = '';
|
||||
this.show_preview = false;
|
||||
})
|
||||
|
||||
this.update_primary_action(frappe.get_route()[1])
|
||||
},
|
||||
mounted() {
|
||||
frappe.route.on('change', () => {
|
||||
if (frappe.get_route()[0] === 'social') {
|
||||
this.set_current_page();
|
||||
this.update_primary_action(frappe.get_route()[1])
|
||||
frappe.utils.scroll_to(0);
|
||||
$("body").attr("data-route", frappe.get_route_str());
|
||||
}
|
||||
});
|
||||
frappe.ui.setup_like_popover($(this.$refs.social), '.likes', false);
|
||||
},
|
||||
methods: {
|
||||
set_current_page() {
|
||||
this.current_page = this.get_current_page();
|
||||
},
|
||||
update_primary_action(current_route) {
|
||||
if (current_route === 'home') {
|
||||
this.$root.page.set_title(__('Social'));
|
||||
frappe.breadcrumbs.update();
|
||||
this.$root.page.set_primary_action(__('Post'), () => {
|
||||
frappe.social.post_dialog.show();
|
||||
});
|
||||
} else {
|
||||
frappe.breadcrumbs.add({
|
||||
type: 'Custom',
|
||||
label: __('Social Home'),
|
||||
route: '#social/home'
|
||||
});
|
||||
this.$root.page.clear_primary_action();
|
||||
}
|
||||
|
||||
if (current_route === 'users') {
|
||||
this.$root.page.set_title(__('Leaderboard'));
|
||||
}
|
||||
},
|
||||
get_current_page() {
|
||||
const route_map = get_route_map();
|
||||
const route = frappe.get_route_str();
|
||||
if (route_map[route]) {
|
||||
return route_map[route];
|
||||
} else {
|
||||
return route_map[route.substring(0, route.lastIndexOf('/')) + '/*'] || route_map['not_found']
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="muted-title">{{ __('Upcoming Events') }}</div>
|
||||
<div class="event" v-for="event in events" :key="event.name">
|
||||
<span class="bold">{{ get_time(event.starts_on) }}</span>
|
||||
<a @click="open_event(event)"> {{ event.subject }}</a>
|
||||
</div>
|
||||
<div class="event" v-if="!events.length">
|
||||
{{ __('No Upcoming Events') }}
|
||||
</div>
|
||||
<div class="muted-title">{{ __('Chat') }}</div>
|
||||
<a @click="open_chat">
|
||||
{{ __('Open Chat') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
'events': []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.get_events().then((events) => {
|
||||
this.events = events
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
get_events() {
|
||||
const today = frappe.datetime.now_date();
|
||||
return frappe.xcall('frappe.desk.doctype.event.event.get_events', {
|
||||
start: today,
|
||||
end: today
|
||||
})
|
||||
},
|
||||
open_chat() {
|
||||
setTimeout(frappe.chat.widget.toggle);
|
||||
},
|
||||
get_time(timestamp) {
|
||||
return frappe.datetime.get_time(timestamp)
|
||||
},
|
||||
open_event(event) {
|
||||
frappe.set_route('Form', 'Event', event.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<div class="backdrop">
|
||||
<img
|
||||
:src="src"
|
||||
:class="{'psuedo-zoom': full_size}"
|
||||
@dblclick="full_size = !full_size"
|
||||
/>
|
||||
<i class="fa fa-close close" @click="$root.$emit('hide_preview')"></i>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['src'],
|
||||
data() {
|
||||
return {
|
||||
full_size: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('keyup', this.close_preview_on_escape);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('keyup', this.close_preview_on_escape);
|
||||
},
|
||||
methods: {
|
||||
close_preview_on_escape(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.$root.$emit('hide_preview')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #0000006e;
|
||||
z-index: 1030;
|
||||
top: 0;
|
||||
right: 0;
|
||||
img {
|
||||
margin: auto;
|
||||
display: block;
|
||||
width: 80%;
|
||||
max-width: 700px;
|
||||
padding-top: 100px;
|
||||
}
|
||||
.psuedo-zoom {
|
||||
padding: 10px 0px;
|
||||
width: auto;
|
||||
height: 100vh;
|
||||
max-width: initial;
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 35px;
|
||||
color: black;
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
Not Found
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
<template>
|
||||
<div class="post-card">
|
||||
<div class="post-body" :class="{'pinned ': is_pinned}">
|
||||
<div class="pull-right flex">
|
||||
<div
|
||||
class="pin-option"
|
||||
v-if="is_pinned">
|
||||
Globally Pinned
|
||||
</div>
|
||||
<post-dropdown-menu
|
||||
class="post-dropdown-menu"
|
||||
v-if="options.length"
|
||||
:options="options"
|
||||
/>
|
||||
<transition name="fade">
|
||||
<span
|
||||
class="indicator blue"
|
||||
v-if="!this.post.seen">
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="user-avatar" v-html="user_avatar" @click="goto_profile(post.owner)"></div>
|
||||
<a class="user-name" @click="goto_profile(post.owner)">{{ user_name }}</a>
|
||||
<div class="text-muted" v-html="post_time"></div>
|
||||
<div ref="content" class="content" v-html="post.content"></div>
|
||||
</div>
|
||||
<post-action
|
||||
:liked_by="post.liked_by"
|
||||
:comment_count="comments.length"
|
||||
@toggle_comment="toggle_comment"
|
||||
@toggle_like="toggle_like"
|
||||
/>
|
||||
<post-comment
|
||||
v-if="show_comments"
|
||||
class="post-comments"
|
||||
:comments="comments"
|
||||
@create_comment="create_comment"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import PostAction from './PostAction.vue';
|
||||
import PostComment from './PostComment.vue';
|
||||
import PostDropdownMenu from './PostDropdownMenu.vue';
|
||||
|
||||
export default {
|
||||
props: ['post'],
|
||||
components: {
|
||||
PostAction,
|
||||
PostComment,
|
||||
PostDropdownMenu
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user_avatar: frappe.avatar(this.post.owner, 'avatar-medium'),
|
||||
post_time: comment_when(this.post.creation),
|
||||
user_name: frappe.user_info(this.post.owner).fullname,
|
||||
comment_count: 0,
|
||||
comments: [],
|
||||
show_comments: false,
|
||||
is_globally_pinnable: frappe.user_roles.includes('System Manager') && frappe.social.is_home_page(),
|
||||
is_pinnable: false,
|
||||
is_user_post_owner: this.post.owner === frappe.session.user
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
can_pin() {
|
||||
return this.is_globally_pinnable || this.is_pinnable
|
||||
},
|
||||
is_pinned() {
|
||||
return false && frappe.social.is_profile_page(this.post.owner)
|
||||
|| this.post.is_globally_pinned && frappe.social.is_home_page()
|
||||
},
|
||||
options() {
|
||||
const options = []
|
||||
if (this.can_pin) {
|
||||
if (this.is_pinned) {
|
||||
options.push({
|
||||
'label': __('Unpin'),
|
||||
'action': this.toggle_pin
|
||||
})
|
||||
} else {
|
||||
options.push({
|
||||
'label': __('Pin Globally'),
|
||||
'action': this.toggle_pin
|
||||
})
|
||||
}
|
||||
}
|
||||
if (this.is_user_post_owner) {
|
||||
options.push({
|
||||
'label': __('Delete'),
|
||||
'action': this.delete_post
|
||||
})
|
||||
}
|
||||
return options;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
frappe.db.get_list('Post Comment', {
|
||||
fields: ['name', 'content', 'owner', 'creation'],
|
||||
order_by: 'creation desc',
|
||||
filters: {
|
||||
parent: this.post.name
|
||||
}
|
||||
}).then(comments => {
|
||||
this.comments = comments;
|
||||
})
|
||||
|
||||
if (!this.post.liked_by) {
|
||||
this.$set(this.post, 'liked_by', '')
|
||||
}
|
||||
|
||||
frappe.realtime.on('new_post_comment' + this.post.name, (comment) => {
|
||||
this.comments = [comment].concat(this.comments);
|
||||
})
|
||||
frappe.realtime.on('update_liked_by' + this.post.name, this.update_liked_by)
|
||||
|
||||
frappe.realtime.on('delete_post' + this.post.name, () => {
|
||||
this.$emit('delete-post')
|
||||
})
|
||||
|
||||
this.$root.$on('user_image_updated', () => {
|
||||
this.user_avatar = frappe.avatar(this.post.owner, 'avatar-medium')
|
||||
})
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.$refs['content'].querySelectorAll('img').forEach((img) => {
|
||||
img.addEventListener('click', () => {
|
||||
this.$root.$emit('show_preview', img.src);
|
||||
})
|
||||
});
|
||||
|
||||
this.$refs['content'].querySelectorAll('a').forEach(link_element => {
|
||||
// to open link in new tab
|
||||
link_element.target = 'blank';
|
||||
this.generate_preview(link_element);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
goto_profile(user) {
|
||||
frappe.set_route('social', 'profile/' + user)
|
||||
},
|
||||
toggle_comment() {
|
||||
this.show_comments = !this.show_comments
|
||||
},
|
||||
update_liked_by(liked_by) {
|
||||
this.post.liked_by = liked_by;
|
||||
},
|
||||
toggle_like() {
|
||||
frappe.xcall('frappe.social.doctype.post.post.toggle_like', {
|
||||
post_name: this.post.name,
|
||||
})
|
||||
},
|
||||
toggle_pin() {
|
||||
if (this.is_globally_pinnable) {
|
||||
frappe.db.set_value('Post', this.post.name, 'is_globally_pinned', cint(!this.is_pinned))
|
||||
.then(res => this.post.is_globally_pinned = cint(res.message.is_globally_pinned))
|
||||
}
|
||||
if (this.is_pinnable) {
|
||||
frappe.db.set_value('Post', this.post.name, 'is_pinned', cint(!this.is_pinned))
|
||||
.then(res => this.post.is_pinned = cint(res.message.is_pinned))
|
||||
}
|
||||
},
|
||||
create_comment(content) {
|
||||
const comment = frappe.model.get_new_doc('Post Comment');
|
||||
comment.content = content
|
||||
comment.parent = this.post.name;
|
||||
frappe.db.insert(comment);
|
||||
},
|
||||
delete_post() {
|
||||
frappe.confirm(__("Are you sure you want to delete this post?"), () => {
|
||||
frappe.dom.freeze();
|
||||
frappe.xcall('frappe.social.doctype.post.post.delete_post', {
|
||||
'post_name': this.post.name
|
||||
}).then(frappe.dom.unfreeze)
|
||||
})
|
||||
},
|
||||
update_seen() {
|
||||
frappe.xcall('frappe.social.doctype.post.post.set_seen', {
|
||||
post_name: this.post.name
|
||||
}).then(() => this.post.seen = true)
|
||||
},
|
||||
generate_preview(link_element) {
|
||||
// TODO: move the code to separate component
|
||||
frappe.xcall('frappe.social.doctype.post.post.get_link_info', {
|
||||
'url': link_element.href
|
||||
}).then(info => {
|
||||
const title = frappe.ellipsis(info['og:title'] || info['title'], 60)
|
||||
const description = frappe.ellipsis(info['og:description'] || info['description'], 280)
|
||||
const image = info['og:image'];
|
||||
const url = info['og:url'];
|
||||
|
||||
if (title) {
|
||||
link_element.insertAdjacentHTML('afterend', `
|
||||
<a href="${url}" target="blank" class="preview-card" class="flex">
|
||||
<img src="${image}"/>
|
||||
<div class="flex-column">
|
||||
<h5>${title}</h5>
|
||||
<p class="text-muted">${description}</p>
|
||||
</div>
|
||||
</a>
|
||||
` );
|
||||
}
|
||||
})
|
||||
.catch(console.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.post-comments {
|
||||
padding: 15px 46px;
|
||||
padding-top: 0px;
|
||||
background: #F6F6F6;
|
||||
}
|
||||
.indicator {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .8s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<div class="post-action-container">
|
||||
<div class="like" @click="$emit('toggle_like')">
|
||||
<i class="octicon octicon-heart" :class="{'liked': post_liked}"></i>
|
||||
<span class="likes" :data-liked-by="liked_by_data">{{ like_count }}</span>
|
||||
</div>
|
||||
<div class="comment" @click="$emit('toggle_comment')">
|
||||
<i class="octicon octicon-comment"></i>
|
||||
<span class="comment_count">{{ comment_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'liked_by',
|
||||
'comment_count',
|
||||
],
|
||||
computed: {
|
||||
like_count() {
|
||||
return this.split_string(this.liked_by).length;
|
||||
},
|
||||
post_liked() {
|
||||
return this.split_string(this.liked_by).includes(frappe.session.user);
|
||||
},
|
||||
liked_by_data() {
|
||||
return JSON.stringify(this.split_string(this.liked_by));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
split_string(str) {
|
||||
return str && str !== '' ? str.split('\n') : []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='less' scoped>
|
||||
.post-action-container {
|
||||
display: flex;
|
||||
background-color: #F6F6F6;
|
||||
padding: 10px;
|
||||
.comment, .like {
|
||||
padding-right: 20px;
|
||||
cursor: pointer;
|
||||
color: #8d99a6;
|
||||
span {
|
||||
padding: 5px;
|
||||
}
|
||||
&:hover {
|
||||
color: darken(#8d99a6, 10%);
|
||||
}
|
||||
}
|
||||
.likes {
|
||||
cursor: pointer;
|
||||
}
|
||||
.liked {
|
||||
color: #fc4f51;
|
||||
&:hover {
|
||||
color: lighten(#fc4f51, 10%) !important;
|
||||
}
|
||||
}
|
||||
.pinned {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="comment-box flex-column">
|
||||
<div class="text-muted comment-label">{{ __('Add a comment') }}</div>
|
||||
<div ref="comment-section"></div>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-muted small">
|
||||
{{ __("Ctrl+Enter to add comment") }}
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
@click="create_comment">
|
||||
{{ __('Comment') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="comments" v-if="comments.length" class="comment-list">
|
||||
<div class="comment" v-for="comment in comments" :key="comment.name">
|
||||
<span
|
||||
class="cursor-pointer"
|
||||
@click="go_to_profile_page(comment.owner)"
|
||||
v-html="get_avatar(comment.owner)">
|
||||
</span>
|
||||
<span class="content" v-html="comment.content"/>
|
||||
<span
|
||||
class="text-muted"
|
||||
v-html="get_time(comment.creation)">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['comments'],
|
||||
mounted() {
|
||||
this.make_comment_section();
|
||||
this.make_mentions_clickable(this.$refs['comments']);
|
||||
},
|
||||
methods: {
|
||||
get_avatar(user) {
|
||||
return frappe.avatar(user)
|
||||
},
|
||||
get_time(timestamp) {
|
||||
return comment_when(timestamp, true)
|
||||
},
|
||||
go_to_profile_page(user) {
|
||||
frappe.set_route('social', 'profile', user)
|
||||
},
|
||||
make_comment_section() {
|
||||
this.comment_section = frappe.ui.form.make_control({
|
||||
parent: this.$refs['comment-section'],
|
||||
only_input: true,
|
||||
render_input: true,
|
||||
no_wrapper: true,
|
||||
mentions: this.get_names_for_mentions(),
|
||||
df: {
|
||||
fieldtype: 'Comment',
|
||||
fieldname: 'comment'
|
||||
},
|
||||
on_submit: this.create_comment.bind(this)
|
||||
});
|
||||
},
|
||||
create_comment() {
|
||||
const message = this.comment_section.get_value().replace('<div><br></div>', '');
|
||||
if (!strip_html(message)) return
|
||||
frappe.utils.play_sound("click");
|
||||
this.$emit('create_comment', message);
|
||||
this.comment_section.clear();
|
||||
},
|
||||
get_names_for_mentions() {
|
||||
var valid_users = Object.keys(frappe.boot.user_info)
|
||||
.filter(user => !["Administrator", "Guest"].includes(user));
|
||||
valid_users = valid_users
|
||||
.filter(user => frappe.boot.user_info[user].allowed_in_mentions==1);
|
||||
return valid_users.map(user => frappe.boot.user_info[user].name);
|
||||
},
|
||||
make_mentions_clickable(parent_element) {
|
||||
Array.from(parent_element.getElementsByClassName('mention'))
|
||||
.forEach((mention) => {
|
||||
mention.classList.add('cursor-pointer');
|
||||
mention.addEventListener('click', () => {
|
||||
this.go_to_profile_page(mention.dataset.value)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.comment-box {
|
||||
.comment-label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
::v-deep .ql-editor {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
min-height: 60px !important;
|
||||
border: 1px solid #d1d8dd;
|
||||
}
|
||||
button {
|
||||
padding: 2px 5px;
|
||||
font-size: 10px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
.comment-list {
|
||||
margin-top: 10px;
|
||||
.comment {
|
||||
.comment-input-wrapper {
|
||||
margin-top: -6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
display: flex;
|
||||
padding: 5px 0;
|
||||
.content {
|
||||
align-self: center;
|
||||
font-size: 12px;
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<template>
|
||||
<div class="dropdown">
|
||||
<span class="caret cursor-pointer dropdown-toggle" data-toggle="dropdown"></span>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-for="option in options" :key="option.label">
|
||||
<a @click="option.action">{{option.label}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['options'],
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.dropdown-menu {
|
||||
min-width: 100px;
|
||||
left: auto;
|
||||
right: 0;
|
||||
a {
|
||||
padding: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="loading_posts && !posts.length">
|
||||
<post-skeleton v-for="index in 5" :key="index"/>
|
||||
</div>
|
||||
<transition-group name="flip-list">
|
||||
<post ref="posts"
|
||||
:post="post"
|
||||
v-for="(post, index) in posts"
|
||||
:key="post.name"
|
||||
@delete-post="delete_post(index)"
|
||||
/>
|
||||
</transition-group>
|
||||
<div v-if="!loading_posts && !posts.length" class="no-post-message text-muted">
|
||||
{{ __('No posts yet') }}
|
||||
</div>
|
||||
<div
|
||||
v-show="loading_posts && posts.length"
|
||||
class="text-center text-muted padding">
|
||||
{{ __('Fetching posts...') }}
|
||||
</div>
|
||||
<div
|
||||
v-show="posts.length && !loading_posts && !more_posts_available"
|
||||
class="text-center text-muted padding">
|
||||
{{ __("No more posts") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Post from './Post.vue';
|
||||
import PostSkeleton from './PostSkeleton.vue';
|
||||
|
||||
export default {
|
||||
props: ['post_list_filter'],
|
||||
components: {
|
||||
Post,
|
||||
PostSkeleton
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
posts: [],
|
||||
more_posts_available: true,
|
||||
loading_posts: false,
|
||||
load_new: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('scroll', this.handle_scroll);
|
||||
this.update_posts();
|
||||
this.$parent.$on('load_new_posts', () => {
|
||||
this.update_posts()
|
||||
})
|
||||
frappe.realtime.on('global_pin', () => {
|
||||
this.update_posts()
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
post_list_filter(old_val, new_val) {
|
||||
if (JSON.stringify(old_val) !== JSON.stringify(new_val)){
|
||||
this.update_posts()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
get_posts(filters, load_old) {
|
||||
return frappe.xcall('frappe.social.doctype.post.post.get_posts', {
|
||||
filters,
|
||||
limit_start: load_old ? this.posts.length : 0
|
||||
})
|
||||
},
|
||||
update_posts(load_old = false) {
|
||||
if (!this.post_list_filter) return
|
||||
this.loading_posts = true;
|
||||
|
||||
const filters = Object.assign({}, this.post_list_filter);
|
||||
|
||||
this.get_posts(filters, load_old).then((res) => {
|
||||
if (load_old) {
|
||||
if (!res.length) {
|
||||
this.more_posts_available = false;
|
||||
}
|
||||
this.posts = this.posts.concat(res);
|
||||
} else {
|
||||
this.posts = res;
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading_posts = false;
|
||||
this.track_seen()
|
||||
});
|
||||
},
|
||||
handle_scroll: frappe.utils.debounce(function() {
|
||||
this.track_seen()
|
||||
const screen_bottom = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
|
||||
if (screen_bottom && this.more_posts_available) {
|
||||
if (!this.loading_posts) {
|
||||
this.update_posts(true);
|
||||
}
|
||||
}
|
||||
}, 500),
|
||||
track_seen() {
|
||||
const posts = this.$refs.posts || []
|
||||
posts.forEach((post_component) => {
|
||||
if(!post_component.post.seen
|
||||
&& frappe.dom.is_element_in_viewport(post_component.$el, 50)) {
|
||||
post_component.update_seen()
|
||||
}
|
||||
})
|
||||
},
|
||||
delete_post(index) {
|
||||
this.posts.splice(index, 1);
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handle_scroll);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.no-post-message {
|
||||
height: 200px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 200px;
|
||||
}
|
||||
.flip-list-move, .flip-list-to {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<template>
|
||||
<div class="flex flex-column">
|
||||
<a class="leaderboard-link"
|
||||
@click.prevent="go_to_user_list()">
|
||||
{{ __('Leaderboard') }}
|
||||
</a>
|
||||
<div class="links" v-if="frequently_visited_list.length">
|
||||
<div class="muted-title">
|
||||
{{ __('Frequently Visited Links') }}
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<a class="route-link"
|
||||
@click.prevent="goto_list(route_obj.route)"
|
||||
v-for="route_obj in frequently_visited_list"
|
||||
:key="route_obj.route">
|
||||
{{ get_label(route_obj.route) }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
frequently_visited_list: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.set_frequently_visited_list()
|
||||
},
|
||||
methods: {
|
||||
goto_list(route) {
|
||||
frappe.set_route(route);
|
||||
},
|
||||
set_frequently_visited_list() {
|
||||
frappe.xcall('frappe.social.doctype.post.post.frequently_visited_links')
|
||||
.then(data => {
|
||||
this.frequently_visited_list = data;
|
||||
})
|
||||
},
|
||||
get_label(route) {
|
||||
return frappe.utils.get_route_label(route);
|
||||
},
|
||||
go_to_profile_page() {
|
||||
frappe.set_route('social', 'profile', frappe.session.user)
|
||||
},
|
||||
go_to_user_list() {
|
||||
frappe.set_route('social', 'users')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.route-link {
|
||||
margin: 0px 10px 10px 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.leaderboard-link {
|
||||
.route-link;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.stats {
|
||||
min-height: 150px
|
||||
}
|
||||
.user-details {
|
||||
.user-avatar {
|
||||
/deep/.avatar-xl {
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
.user_name {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-size: 2rem;
|
||||
font-weight: 600
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<div class="post-skeleton flex-column justify-between">
|
||||
<div class="post-skeleton-body">
|
||||
<div class="flex">
|
||||
<div class="avatar avatar-medium"></div>
|
||||
<div class="user-name"></div>
|
||||
</div>
|
||||
<div class="content"></div>
|
||||
</div>
|
||||
<div class="post-skeleton-foot"></div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@base-color: #f8f8f8;
|
||||
@shine-color: #fcfcfc;
|
||||
@animation-duration: 3s;
|
||||
|
||||
@keyframes load {
|
||||
0% {
|
||||
background-position: -100px
|
||||
}
|
||||
|
||||
40%, 100% {
|
||||
background-position: 500px
|
||||
}
|
||||
}
|
||||
|
||||
.background-img() {
|
||||
background-image: linear-gradient(90deg, @base-color 0px, @shine-color 40px, @base-color 80px);
|
||||
background-size: 600px
|
||||
}
|
||||
.post-skeleton {
|
||||
height: 250px;
|
||||
border-radius: 4px;
|
||||
max-width: 600px;
|
||||
border: 1px solid #ededed;
|
||||
margin-bottom: 15px;
|
||||
.post-skeleton-body {
|
||||
padding: 15px;
|
||||
.user-name {
|
||||
height: 10px;
|
||||
width: 100px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.content {
|
||||
height: 100px;
|
||||
margin: 15px 0 0 46px;
|
||||
}
|
||||
.user-name, .avatar, .content {
|
||||
.background-img();
|
||||
border-radius: 4px;
|
||||
animation: load @animation-duration infinite linear;
|
||||
}
|
||||
}
|
||||
.post-skeleton-foot {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: @base-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
<template>
|
||||
<div ref="banner" class="banner" :style="background_style">
|
||||
<div
|
||||
class="user-avatar container"
|
||||
v-html="user_avatar">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['user_id'],
|
||||
data() {
|
||||
return {
|
||||
user_avatar: frappe.avatar(this.user_id, 'avatar-xl'),
|
||||
user_banner: frappe.user_info(this.user_id).banner_image
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$root.$on('user_image_updated', () => {
|
||||
this.user_avatar = frappe.avatar(this.user_id, 'avatar-xl')
|
||||
this.user_banner = frappe.user_info(this.user_id).banner_image
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
background_style() {
|
||||
const style = {}
|
||||
if (this.user_banner) {
|
||||
style['background-image'] = `url('${this.user_banner}')`
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.banner {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
z-index: 101;
|
||||
position: absolute;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #262626;
|
||||
.user-avatar {
|
||||
position: relative;
|
||||
/deep/ .avatar {
|
||||
top: 220px;
|
||||
left: 10px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
position: absolute !important;
|
||||
}
|
||||
}
|
||||
.editable-image {
|
||||
/deep/ .avatar {
|
||||
cursor: pointer;
|
||||
:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
<template>
|
||||
<div class="profile-sidebar flex flex-column">
|
||||
<div class="user-details">
|
||||
<h3>{{ user.fullname }}</h3>
|
||||
<p><a @click="view_energy_point_list(user)" class="text-muted">
|
||||
{{ __("Energy Points") }}: {{ energy_points }}</a></p>
|
||||
<p>{{ user.bio }}</p>
|
||||
<div class="location" v-if="user.location">
|
||||
<span class="text-muted">
|
||||
<i class="fa fa-map-marker"> </i>
|
||||
{{ user.location }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="interest" v-if="user.interest">
|
||||
<span class="text-muted">
|
||||
<i class="fa fa-puzzle-piece"> </i>
|
||||
{{ user.interest }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
class="edit-profile-link"
|
||||
v-if="can_edit_profile"
|
||||
@click="edit_profile()"
|
||||
>{{ __('Edit Profile') }}</a>
|
||||
<a
|
||||
class="edit-profile-link"
|
||||
v-if="can_edit_user"
|
||||
@click="edit_user()"
|
||||
>{{ __('User Settings') }}</a>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
user_id: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: frappe.user_info(this.user_id),
|
||||
can_edit_profile: frappe.social.is_session_user_page(),
|
||||
can_edit_user: frappe.session.user === this.user_id,
|
||||
energy_points: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points', {user: this.user_id}).then(r => {
|
||||
this.energy_points = r[this.user_id].energy_points;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
edit_user() {
|
||||
frappe.set_route('Form', 'User', this.user_id);
|
||||
},
|
||||
edit_profile() {
|
||||
const edit_profile_dialog = new frappe.ui.Dialog({
|
||||
title: __('Edit Profile'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: 'Attach Image',
|
||||
fieldname: 'user_image',
|
||||
label: 'Profile Image',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'interest',
|
||||
label: 'Interests',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Column Break'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Attach Image',
|
||||
fieldname: 'banner_image',
|
||||
label: 'Banner Image',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'location',
|
||||
label: 'Location',
|
||||
},
|
||||
{
|
||||
fieldtype: 'Section Break',
|
||||
fieldname: 'Interest'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Small Text',
|
||||
fieldname: 'bio',
|
||||
label: 'Bio',
|
||||
}
|
||||
],
|
||||
primary_action: values => {
|
||||
edit_profile_dialog.disable_primary_action();
|
||||
frappe
|
||||
.xcall('frappe.core.doctype.user.user.update_profile_info', {
|
||||
profile_info: values
|
||||
})
|
||||
.then(user => {
|
||||
user.image = user.user_image;
|
||||
let user_info = frappe.user_info(this.user_id);
|
||||
this.user = Object.assign(user_info, user);
|
||||
this.$root.$emit('user_image_updated');
|
||||
edit_profile_dialog.hide();
|
||||
})
|
||||
.finally(() => {
|
||||
edit_profile_dialog.enable_primary_action();
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Save')
|
||||
});
|
||||
edit_profile_dialog.set_values({
|
||||
user_image: this.user.image,
|
||||
banner_image: this.user.banner_image,
|
||||
location: this.user.location,
|
||||
interest: this.user.interest,
|
||||
bio: this.user.bio
|
||||
});
|
||||
edit_profile_dialog.show();
|
||||
},
|
||||
view_energy_point_list(user) {
|
||||
frappe.set_route('List', 'Energy Point Log', {user:user.name});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.profile-sidebar {
|
||||
padding: 10px 10px 0 0;
|
||||
}
|
||||
.user-details {
|
||||
min-height: 150px;
|
||||
.location,
|
||||
.interest {
|
||||
margin-bottom: 10px;
|
||||
i {
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.edit-profile-link {
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="profile-head">
|
||||
<profile-banner :user_id="user_id"></profile-banner>
|
||||
</div>
|
||||
<div class="profile-container">
|
||||
<profile-sidebar
|
||||
:user_id="user_id"
|
||||
class="profile-sidebar"
|
||||
/>
|
||||
<div class="post-container">
|
||||
<div class="list-options">
|
||||
<div
|
||||
class="option"
|
||||
:class="{'bold': show_list === 'user_posts'}"
|
||||
@click="show_list = 'user_posts'">
|
||||
<span>{{__('Posts')}}</span>
|
||||
<span>({{ user_posts_count }})</span>
|
||||
</div>
|
||||
<div
|
||||
class="option"
|
||||
:class="{'bold': show_list === 'liked_posts'}"
|
||||
@click="show_list = 'liked_posts'">
|
||||
<span>{{__('Likes')}}</span>
|
||||
<span>({{ liked_posts_count }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<post-loader :post_list_filter="post_list_filter"></post-loader>
|
||||
</div>
|
||||
<activity-sidebar class="activity-sidebar hidden-xs"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import PostLoader from '../components/PostLoader.vue';
|
||||
import ProfileSidebar from '../components/ProfileSidebar.vue';
|
||||
import ActivitySidebar from '../components/ActivitySidebar.vue';
|
||||
import ProfileBanner from '../components/ProfileBanner.vue';
|
||||
export default {
|
||||
props: ['user_id'],
|
||||
components: {
|
||||
PostLoader,
|
||||
ProfileSidebar,
|
||||
ProfileBanner,
|
||||
ActivitySidebar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show_list: 'user_posts',
|
||||
post_list_filter : null,
|
||||
user_posts_count: 0,
|
||||
liked_posts_count: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show_list() {
|
||||
if (this.show_list == 'user_posts') {
|
||||
this.post_list_filter = this.get_user_posts_filter();
|
||||
} else if (this.show_list == 'liked_posts') {
|
||||
this.post_list_filter = this.get_liked_posts_filter();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.post_list_filter = this.get_user_posts_filter();
|
||||
this.set_post_count()
|
||||
},
|
||||
methods: {
|
||||
get_user_posts_filter() {
|
||||
return {
|
||||
'owner': this.user_id
|
||||
}
|
||||
},
|
||||
get_liked_posts_filter() {
|
||||
return {
|
||||
'liked_by': ['like', '%' + this.user_id + '%']
|
||||
}
|
||||
},
|
||||
set_post_count() {
|
||||
frappe.db.count('Post', { filters: this.get_user_posts_filter() })
|
||||
.then(count => this.user_posts_count = count)
|
||||
|
||||
frappe.db.count('Post', { filters: this.get_liked_posts_filter() })
|
||||
.then(count => this.liked_posts_count = count)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.profile-head {
|
||||
height: 190px;
|
||||
}
|
||||
.profile-sidebar {
|
||||
margin-top: 60px;
|
||||
flex: 20%;
|
||||
}
|
||||
.right-sidebar {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.list-options {
|
||||
display: flex;
|
||||
.option {
|
||||
cursor: pointer;
|
||||
padding: 0px 10px 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,219 +0,0 @@
|
|||
<template>
|
||||
<div class="user-list-container">
|
||||
<ul class="list-unstyled user-list">
|
||||
<li class="user-card user-list-header text-medium">
|
||||
<span class="rank-column"></span>
|
||||
<span class="user-details text-muted">
|
||||
<input
|
||||
class="form-control"
|
||||
type="search"
|
||||
placeholder="Search User"
|
||||
v-model="filter_users_by"
|
||||
>
|
||||
</span>
|
||||
<span class="flex-40"></span>
|
||||
<span class="flex-20 text-muted">
|
||||
<select class="form-control" data-toggle="tooltip" title="Period" v-model="period">
|
||||
<option v-for="value in period_options" :key="value" :value="value">{{ value }}</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
<li class="user-card user-list-header text-medium">
|
||||
<span class="rank-column">#</span>
|
||||
<span class="user-details text-muted">{{ __('User') }}</span>
|
||||
<span
|
||||
class="flex-20 text-muted"
|
||||
v-for="title in ['Energy Points', 'Review Points', 'Points Given']"
|
||||
:key="title"
|
||||
>{{ __(title) }}</span>
|
||||
</li>
|
||||
<li v-for="(user, index) in filtered_users" :key="user.name">
|
||||
<div class="user-card">
|
||||
<span class="user-details flex" @click="go_to_profile_page(user.name)">
|
||||
<span class="rank-column">{{ index + 1 }}</span>
|
||||
<span v-html="get_avatar(user.name)"></span>
|
||||
<span>
|
||||
{{ user.fullname }}
|
||||
<div
|
||||
class="text-muted text-medium"
|
||||
:class="{'italic': !user.bio}"
|
||||
>{{ frappe.ellipsis(user.bio, 100) || 'No Bio'}}</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="text-muted text-nowrap flex-20"
|
||||
v-for="key in ['energy_points', 'review_points', 'given_points']"
|
||||
:key="key"
|
||||
@click="toggle_log(user.name)"
|
||||
>{{ user[key] }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="user-card text-muted" v-if="!filtered_users.length">{{__('No user found')}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
filter_users_by: null,
|
||||
sort_users_by: 'energy_points',
|
||||
sort_order: 'desc',
|
||||
show_log_for: null,
|
||||
period_options: ['Lifetime', 'This Month', 'This Week', 'Today'],
|
||||
period: 'This Month'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
from_date() {
|
||||
if (this.period === 'This Month') {
|
||||
return frappe.datetime.month_start();
|
||||
}
|
||||
if (this.period === 'This Week') {
|
||||
return frappe.datetime.week_start();
|
||||
}
|
||||
if (this.period === 'Today') {
|
||||
return frappe.datetime.get_today();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
filtered_users() {
|
||||
let filtered = this.users.slice();
|
||||
if (this.filter_users_by) {
|
||||
filtered = filtered.filter(user =>
|
||||
user.fullname
|
||||
.toLowerCase()
|
||||
.includes(this.filter_users_by.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if (this.sort_users_by) {
|
||||
filtered.sort((a, b) => {
|
||||
const value_a = a[this.sort_users_by];
|
||||
const value_b = b[this.sort_users_by];
|
||||
|
||||
let return_value = 0;
|
||||
if (value_a > value_b) {
|
||||
return_value = 1;
|
||||
}
|
||||
|
||||
if (value_a < value_b) {
|
||||
return_value = -1;
|
||||
}
|
||||
|
||||
if (this.sort_order === 'desc') {
|
||||
return_value = -return_value;
|
||||
}
|
||||
|
||||
return return_value;
|
||||
});
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
period() {
|
||||
this.fetch_users_energy_points_and_update_users();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
},
|
||||
created() {
|
||||
const standard_users = ['Administrator', 'Guest', 'guest@example.com'];
|
||||
this.users = frappe.boot.user_info;
|
||||
// delete standard users from the list
|
||||
standard_users.forEach(user => delete this.users[user]);
|
||||
this.users = Object.values(this.users);
|
||||
this.fetch_users_energy_points_and_update_users();
|
||||
frappe.realtime.on('update_points', () => {
|
||||
this.fetch_users_energy_points_and_update_users();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
get_avatar(user) {
|
||||
return frappe.avatar(user, 'avatar-medium');
|
||||
},
|
||||
go_to_profile_page(user) {
|
||||
frappe.set_route('social', 'profile', user);
|
||||
},
|
||||
fetch_users_energy_points_and_update_users() {
|
||||
frappe
|
||||
.xcall(
|
||||
'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points',
|
||||
{
|
||||
from_date: this.from_date
|
||||
}
|
||||
)
|
||||
.then(data => {
|
||||
let users = this.users.slice();
|
||||
this.users = users.map(user => {
|
||||
const points = data[user.name] || {};
|
||||
user.energy_points = points.energy_points || 0;
|
||||
user.review_points = points.review_points || 0;
|
||||
user.given_points = points.given_points || 0;
|
||||
return user;
|
||||
});
|
||||
});
|
||||
},
|
||||
toggle_log(user) {
|
||||
frappe.set_route('List', 'Energy Point Log', {user:user});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import 'frappe/public/less/common';
|
||||
.user-list {
|
||||
border-left: 1px solid @border-color;
|
||||
border-right: 1px solid @border-color;
|
||||
.user-card {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid @border-color;
|
||||
.user-details {
|
||||
flex: 1;
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.rank-column {
|
||||
flex: 0 0 30px;
|
||||
align-self: center;
|
||||
.text-muted
|
||||
}
|
||||
.flex-20 {
|
||||
flex: 0 0 20%;
|
||||
text-align: right;
|
||||
align-self: center;
|
||||
}
|
||||
.flex-40 {
|
||||
flex: 0 0 40%;
|
||||
}
|
||||
.user-list-header {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
.search-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
height: 75px;
|
||||
text-align: center;
|
||||
div {
|
||||
margin: auto;
|
||||
}
|
||||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
.energy-point-history {
|
||||
border-bottom: 1px solid @border-color;
|
||||
background-color: @light-bg;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<template>
|
||||
<div class="wall-container">
|
||||
<post-sidebar class="post-sidebar hidden-xs"></post-sidebar>
|
||||
<div class="post-container">
|
||||
<div
|
||||
class="new_posts_count"
|
||||
@click="load_new_posts"
|
||||
v-show='new_posts_count'>
|
||||
{{ new_posts_count + ' new post'}}
|
||||
</div>
|
||||
<post-loader :post_list_filter="{}"></post-loader>
|
||||
</div>
|
||||
<activity-sidebar class="activity-sidebar hidden-xs"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import PostLoader from '../components/PostLoader.vue';
|
||||
import PostSidebar from '../components/PostSidebar.vue';
|
||||
import ActivitySidebar from '../components/ActivitySidebar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PostLoader,
|
||||
PostSidebar,
|
||||
ActivitySidebar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
'posts': [],
|
||||
'events': [],
|
||||
'new_posts_count': 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
frappe.realtime.on('new_post', (post_owner) => {
|
||||
if (post_owner === frappe.session.user) {
|
||||
this.load_new_posts()
|
||||
} else {
|
||||
this.new_posts_count += 1;
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
load_new_posts() {
|
||||
this.$emit('load_new_posts');
|
||||
this.new_posts_count = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
frappe.views.SocialFactory = class SocialFactory extends frappe.views.Factory {
|
||||
show() {
|
||||
if (frappe.pages.social) {
|
||||
frappe.container.change_to('social');
|
||||
} else {
|
||||
this.make('social');
|
||||
}
|
||||
}
|
||||
|
||||
make(page_name) {
|
||||
const assets = [
|
||||
'/assets/js/social.min.js'
|
||||
];
|
||||
|
||||
frappe.require(assets, () => {
|
||||
frappe.social.home = new frappe.social.Home({
|
||||
parent: this.make_page(true, page_name)
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import Home from './Home.vue';
|
||||
|
||||
frappe.provide('frappe.social');
|
||||
|
||||
frappe.social.Home = class SocialHome {
|
||||
constructor({ parent }) {
|
||||
this.$parent = $(parent);
|
||||
this.page = parent.page;
|
||||
this.setup_header();
|
||||
this.make_body();
|
||||
}
|
||||
make_body() {
|
||||
this.$social_container = this.$parent.find('.layout-main');
|
||||
new Vue({
|
||||
el: this.$social_container[0],
|
||||
render: h => h(Home),
|
||||
data: {
|
||||
'page': this.page
|
||||
}
|
||||
});
|
||||
}
|
||||
setup_header() {
|
||||
this.page.set_title(__('Social'));
|
||||
}
|
||||
};
|
||||
|
||||
frappe.social.post_dialog = new frappe.ui.Dialog({
|
||||
title: __('Create Post'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Text Editor",
|
||||
fieldname: "content",
|
||||
label: __("Content"),
|
||||
reqd: 1
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Post'),
|
||||
primary_action: (values) => {
|
||||
frappe.social.post_dialog.disable_primary_action();
|
||||
const post = frappe.model.get_new_doc('Post');
|
||||
post.content = values.content;
|
||||
frappe.db.insert(post).then(() => {
|
||||
frappe.social.post_dialog.clear();
|
||||
frappe.social.post_dialog.hide();
|
||||
}).finally(() => {
|
||||
frappe.social.post_dialog.enable_primary_action();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.social.is_home_page = () => {
|
||||
return frappe.get_route()[0] === 'social' && frappe.get_route()[1] === 'home';
|
||||
};
|
||||
|
||||
frappe.social.is_profile_page = (user) => {
|
||||
return frappe.get_route()[0] === 'social'
|
||||
&& frappe.get_route()[1] === 'profile'
|
||||
&& (user ? frappe.get_route()[2] === user : true);
|
||||
};
|
||||
|
||||
frappe.social.is_session_user_page = () => {
|
||||
return frappe.social.is_profile_page() && frappe.get_route()[2] === frappe.session.user;
|
||||
};
|
||||
|
||||
frappe.provide('frappe.app_updates');
|
||||
|
||||
frappe.utils.make_event_emitter(frappe.app_updates);
|
||||
|
|
@ -82,7 +82,7 @@ frappe.views.Container = Class.extend({
|
|||
|
||||
$(document).trigger("page-change");
|
||||
|
||||
this.page._route = frappe.get_sub_path();
|
||||
this.page._route = frappe.router.get_sub_path();
|
||||
$(this.page).trigger('show');
|
||||
!this.page.disable_scroll_to_top && frappe.utils.scroll_to(0);
|
||||
frappe.breadcrumbs.update();
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ frappe.views.Workspace = class Workspace {
|
|||
|
||||
const get_sidebar_item = function (item) {
|
||||
return $(`<a
|
||||
href="/desk/workspace/${item.name}"
|
||||
href="/app/workspace/${item.name}"
|
||||
class="desk-sidebar-item standard-sidebar-item ${item.selected ? "selected" : ""}"
|
||||
>
|
||||
<div> ${frappe.utils.icon(item.icon || "folder-normal", "md")} </div>
|
||||
|
|
|
|||
|
|
@ -90,11 +90,8 @@ export default class LinksWidget extends Widget {
|
|||
if (this.in_customize_mode) return;
|
||||
|
||||
if (link_label.hasClass("help-video-link")) {
|
||||
let yt_id = event.target.dataset.youtubeid;
|
||||
let yt_id = event.currentTarget.dataset.youtubeid;
|
||||
frappe.help.show_video(yt_id);
|
||||
} else {
|
||||
let route = event.target.dataset.route;
|
||||
frappe.set_route(route);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ function generate_route(item) {
|
|||
if (item.link) {
|
||||
route = strip(item.link, "#");
|
||||
} else if (type === "doctype") {
|
||||
let doctype_slug = frappe.router.slug(item.doctype);
|
||||
|
||||
if (frappe.model.is_single(item.doctype)) {
|
||||
route = "Form/" + item.doctype;
|
||||
route = "Form/" + doctype_slug;
|
||||
} else {
|
||||
if (!item.doc_view) {
|
||||
if (frappe.model.is_tree(item.doctype)) {
|
||||
|
|
@ -18,27 +20,28 @@ function generate_route(item) {
|
|||
item.doc_view = "List";
|
||||
}
|
||||
}
|
||||
|
||||
switch (item.doc_view) {
|
||||
case "List":
|
||||
if (item.filters) {
|
||||
frappe.route_options = item.filters;
|
||||
}
|
||||
route = "List/" + item.doctype;
|
||||
route = "List/" + doctype_slug;
|
||||
break;
|
||||
case "Tree":
|
||||
route = "Tree/" + item.doctype;
|
||||
route = "Tree/" + doctype_slug;
|
||||
break;
|
||||
case "Report Builder":
|
||||
route = "List/" + item.doctype + "/Report";
|
||||
route = "List/" + doctype_slug + "/Report";
|
||||
break;
|
||||
case "Dashboard":
|
||||
route = "List/" + item.doctype + "/Dashboard";
|
||||
route = "List/" + doctype_slug + "/Dashboard";
|
||||
break;
|
||||
case "New":
|
||||
route = "Form/" + item.doctype + "/New " + item.doctype;
|
||||
route = "Form/" + doctype_slug + "/New " + item.doctype;
|
||||
break;
|
||||
case "Calendar":
|
||||
route = "List/" + item.doctype + "/Calendar/Default";
|
||||
route = "List/" + doctype_slug + "/Calendar/Default";
|
||||
break;
|
||||
default:
|
||||
frappe.throw({ message: __("Not a valid DocType view:") + item.doc_view, title: __("Unknown View") });
|
||||
|
|
@ -48,7 +51,7 @@ function generate_route(item) {
|
|||
} else if (type === "report" && item.is_query_report) {
|
||||
route = "query-report/" + item.name;
|
||||
} else if (type === "report") {
|
||||
route = "List/" + item.doctype + "/Report/" + item.name;
|
||||
route = "List/" + frappe.router.slug(item.doctype) + "/Report/" + item.name;
|
||||
} else if (type === "page") {
|
||||
route = item.name;
|
||||
} else if (type === "dashboard") {
|
||||
|
|
@ -73,7 +76,7 @@ function generate_route(item) {
|
|||
// (item.doctype && frappe.model.can_read(item.doctype))) {
|
||||
// item.shown = true;
|
||||
// }
|
||||
return route;
|
||||
return `/app/${route}`;
|
||||
}
|
||||
|
||||
function generate_grid(data) {
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
@import "variables.less";
|
||||
@import (reference) 'common.less';
|
||||
|
||||
body[data-route*="social"] {
|
||||
|
||||
.layout-main-section {
|
||||
border: none;
|
||||
}
|
||||
a {
|
||||
transition: none;
|
||||
}
|
||||
.liked-by-popover {
|
||||
font-size: @text-small;
|
||||
}
|
||||
|
||||
.wall-container {
|
||||
.post-container {
|
||||
.new_posts_count {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wall-container, .profile-container {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
.post-sidebar {
|
||||
padding-top: 20px;
|
||||
flex: 20%
|
||||
}
|
||||
.post-container {
|
||||
flex: 55%;
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
.activity-sidebar {
|
||||
padding: 15px;
|
||||
flex: 25%;
|
||||
.event {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.generic-card() {
|
||||
background: white;
|
||||
font-size: 12px;
|
||||
margin-bottom: 15px;
|
||||
min-height: 70px;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
.content {
|
||||
font-size: 14px;
|
||||
img, iframe {
|
||||
border-radius: 5px;
|
||||
border: 1px solid @light-border-color;
|
||||
margin: 5px 0;
|
||||
}
|
||||
img {
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-card {
|
||||
.generic-card();
|
||||
max-width: 600px;
|
||||
.post-body {
|
||||
padding: 15px;
|
||||
.user-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
.user-avatar {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.user-avatar, .user-name {
|
||||
cursor: pointer;
|
||||
}
|
||||
.content {
|
||||
margin: 15px 0 0 46px;
|
||||
}
|
||||
.post-dropdown-menu {
|
||||
display: none;
|
||||
padding-left: 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
.pin-option {
|
||||
.text-muted;
|
||||
text-transform: uppercase;
|
||||
font-size: @text-small;
|
||||
display: none;
|
||||
}
|
||||
.pinned {
|
||||
background: #fefdf2;
|
||||
.pin-option {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.post-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.post-action-container {
|
||||
padding-left: 56px;
|
||||
background-color: #F6F6F6;
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
.preview-card {
|
||||
text-decoration: none;
|
||||
max-height: 150px;
|
||||
border: 1px solid @light-border-color;
|
||||
border-radius: 5px;
|
||||
margin-top: 15px;
|
||||
padding-right: 10px;
|
||||
background: white;
|
||||
display: flex;
|
||||
img {
|
||||
border: none;
|
||||
margin: 10px;
|
||||
max-width: 160px;
|
||||
max-height: 140px;
|
||||
align-self: center;
|
||||
}
|
||||
p {
|
||||
overflow: hidden;
|
||||
.text-medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.muted-title {
|
||||
&:extend(.text-muted);
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
body[data-route*="social/profile"] {
|
||||
.page-head {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue