@@ -329,3 +329,4 @@ frappe.core.doctype.page.patches.drop_unused_pages | |||
execute:frappe.get_doc('Role', 'Guest').save() # remove desk access | |||
frappe.patches.v13_0.rename_desk_page_to_workspace # 02.02.2021 | |||
frappe.patches.v13_0.delete_package_publish_tool | |||
frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings |
@@ -0,0 +1,20 @@ | |||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
def execute(): | |||
if frappe.db.table_exists('List View Setting'): | |||
existing_list_view_settings = frappe.get_all('List View Settings', as_list=True) | |||
for list_view_setting in frappe.get_all('List View Setting', fields = ['disable_count', 'disable_sidebar_stats', 'disable_auto_refresh', 'name']): | |||
name = list_view_setting.pop('name') | |||
if name not in [x[0] for x in existing_list_view_settings]: | |||
list_view_setting['doctype'] = 'List View Settings' | |||
list_view_settings = frappe.get_doc(list_view_setting) | |||
# setting name here is necessary because autoname is set as prompt | |||
list_view_settings.name = name | |||
list_view_settings.insert() | |||
frappe.delete_doc("DocType", "List View Setting", force=True) | |||
frappe.db.commit() |
@@ -93,14 +93,13 @@ | |||
"public/scss/print.scss" | |||
], | |||
"concat:js/libs.min.js": [ | |||
"public/js/lib/awesomplete/awesomplete.min.js", | |||
"public/js/lib/Sortable.min.js", | |||
"public/js/lib/jquery/jquery.hotkeys.js", | |||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js", | |||
"node_modules/vue/dist/vue.min.js", | |||
"node_modules/moment/min/moment-with-locales.min.js", | |||
"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js", | |||
"public/js/lib/socket.io.min.js", | |||
"node_modules/socket.io-client/dist/socket.io.slim.js", | |||
"public/js/lib/jSignature.min.js", | |||
"public/js/lib/leaflet/leaflet.js", | |||
"public/js/lib/leaflet/leaflet.draw.js", | |||
@@ -1,23 +0,0 @@ | |||
# 3rd party libraries | |||
Import / Upgrade Guide | |||
## JQuery UI | |||
Download modules | |||
- core | |||
- interactions | |||
- autocomplete | |||
- datepicker | |||
## JQuery UI Bootstrap theme | |||
Changes images urls | |||
- from: url(images | |||
- to: url(../lib/js/lib/jquery/bootstrap_theme/images | |||
## JQuery Gantt | |||
Not a very mature project. Please check css / js after updating |
@@ -1,104 +0,0 @@ | |||
.awesomplete [hidden] { | |||
display: none; | |||
} | |||
.awesomplete .visually-hidden { | |||
position: absolute; | |||
clip: rect(0, 0, 0, 0); | |||
} | |||
.awesomplete { | |||
display: inline-block; | |||
position: relative; | |||
} | |||
.awesomplete > input { | |||
display: block; | |||
} | |||
.awesomplete > ul { | |||
position: absolute; | |||
left: 0; | |||
z-index: 1; | |||
min-width: 100%; | |||
box-sizing: border-box; | |||
list-style: none; | |||
padding: 0; | |||
margin: 0; | |||
background: #fff; | |||
} | |||
.awesomplete > ul:empty { | |||
display: none; | |||
} | |||
.awesomplete > ul { | |||
border-radius: .3em; | |||
margin: .2em 0 0; | |||
background: hsla(0,0%,100%,.9); | |||
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); | |||
border: 1px solid rgba(0,0,0,.3); | |||
box-shadow: .05em .2em .6em rgba(0,0,0,.2); | |||
text-shadow: none; | |||
} | |||
@supports (transform: scale(0)) { | |||
.awesomplete > ul { | |||
transition: .3s cubic-bezier(.4,.2,.5,1.4); | |||
transform-origin: 1.43em -.43em; | |||
} | |||
.awesomplete > ul[hidden], | |||
.awesomplete > ul:empty { | |||
opacity: 0; | |||
transform: scale(0); | |||
display: block; | |||
transition-timing-function: ease; | |||
} | |||
} | |||
/* Pointer */ | |||
.awesomplete > ul:before { | |||
content: ""; | |||
position: absolute; | |||
top: -.43em; | |||
left: 1em; | |||
width: 0; height: 0; | |||
padding: .4em; | |||
background: white; | |||
border: inherit; | |||
border-right: 0; | |||
border-bottom: 0; | |||
-webkit-transform: rotate(45deg); | |||
transform: rotate(45deg); | |||
} | |||
.awesomplete > ul > li { | |||
position: relative; | |||
padding: .2em .5em; | |||
cursor: pointer; | |||
} | |||
.awesomplete > ul > li:hover { | |||
background: hsl(200, 40%, 80%); | |||
color: black; | |||
} | |||
.awesomplete > ul > li[aria-selected="true"] { | |||
background: hsl(205, 40%, 40%); | |||
color: white; | |||
} | |||
.awesomplete mark { | |||
background: hsl(65, 100%, 50%); | |||
} | |||
.awesomplete li:hover mark { | |||
background: hsl(68, 100%, 41%); | |||
} | |||
.awesomplete li[aria-selected="true"] mark { | |||
background: hsl(86, 100%, 21%); | |||
color: inherit; | |||
} | |||
/*# sourceMappingURL=awesomplete.css.map */ |
@@ -1,208 +0,0 @@ | |||
/*! | |||
* FullCalendar v3.4.0 Print Stylesheet | |||
* Docs & License: https://fullcalendar.io/ | |||
* (c) 2017 Adam Shaw | |||
*/ | |||
/* | |||
* Include this stylesheet on your page to get a more printer-friendly calendar. | |||
* When including this stylesheet, use the media='print' attribute of the <link> tag. | |||
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. | |||
*/ | |||
.fc { | |||
max-width: 100% !important; | |||
} | |||
/* Global Event Restyling | |||
--------------------------------------------------------------------------------------------------*/ | |||
.fc-event { | |||
background: #fff !important; | |||
color: #000 !important; | |||
page-break-inside: avoid; | |||
} | |||
.fc-event .fc-resizer { | |||
display: none; | |||
} | |||
/* Table & Day-Row Restyling | |||
--------------------------------------------------------------------------------------------------*/ | |||
.fc th, | |||
.fc td, | |||
.fc hr, | |||
.fc thead, | |||
.fc tbody, | |||
.fc-row { | |||
border-color: #ccc !important; | |||
background: #fff !important; | |||
} | |||
/* kill the overlaid, absolutely-positioned components */ | |||
/* common... */ | |||
.fc-bg, | |||
.fc-bgevent-skeleton, | |||
.fc-highlight-skeleton, | |||
.fc-helper-skeleton, | |||
/* for timegrid. within cells within table skeletons... */ | |||
.fc-bgevent-container, | |||
.fc-business-container, | |||
.fc-highlight-container, | |||
.fc-helper-container { | |||
display: none; | |||
} | |||
/* don't force a min-height on rows (for DayGrid) */ | |||
.fc tbody .fc-row { | |||
height: auto !important; /* undo height that JS set in distributeHeight */ | |||
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ | |||
} | |||
.fc tbody .fc-row .fc-content-skeleton { | |||
position: static; /* undo .fc-rigid */ | |||
padding-bottom: 0 !important; /* use a more border-friendly method for this... */ | |||
} | |||
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ | |||
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ | |||
} | |||
.fc tbody .fc-row .fc-content-skeleton table { | |||
/* provides a min-height for the row, but only effective for IE, which exaggerates this value, | |||
making it look more like 3em. for other browers, it will already be this tall */ | |||
height: 1em; | |||
} | |||
/* Undo month-view event limiting. Display all events and hide the "more" links | |||
--------------------------------------------------------------------------------------------------*/ | |||
.fc-more-cell, | |||
.fc-more { | |||
display: none !important; | |||
} | |||
.fc tr.fc-limited { | |||
display: table-row !important; | |||
} | |||
.fc td.fc-limited { | |||
display: table-cell !important; | |||
} | |||
.fc-popover { | |||
display: none; /* never display the "more.." popover in print mode */ | |||
} | |||
/* TimeGrid Restyling | |||
--------------------------------------------------------------------------------------------------*/ | |||
/* undo the min-height 100% trick used to fill the container's height */ | |||
.fc-time-grid { | |||
min-height: 0 !important; | |||
} | |||
/* don't display the side axis at all ("all-day" and time cells) */ | |||
.fc-agenda-view .fc-axis { | |||
display: none; | |||
} | |||
/* don't display the horizontal lines */ | |||
.fc-slats, | |||
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ | |||
display: none !important; /* important overrides inline declaration */ | |||
} | |||
/* let the container that holds the events be naturally positioned and create real height */ | |||
.fc-time-grid .fc-content-skeleton { | |||
position: static; | |||
} | |||
/* in case there are no events, we still want some height */ | |||
.fc-time-grid .fc-content-skeleton table { | |||
height: 4em; | |||
} | |||
/* kill the horizontal spacing made by the event container. event margins will be done below */ | |||
.fc-time-grid .fc-event-container { | |||
margin: 0 !important; | |||
} | |||
/* TimeGrid *Event* Restyling | |||
--------------------------------------------------------------------------------------------------*/ | |||
/* naturally position events, vertically stacking them */ | |||
.fc-time-grid .fc-event { | |||
position: static !important; | |||
margin: 3px 2px !important; | |||
} | |||
/* for events that continue to a future day, give the bottom border back */ | |||
.fc-time-grid .fc-event.fc-not-end { | |||
border-bottom-width: 1px !important; | |||
} | |||
/* indicate the event continues via "..." text */ | |||
.fc-time-grid .fc-event.fc-not-end:after { | |||
content: "..."; | |||
} | |||
/* for events that are continuations from previous days, give the top border back */ | |||
.fc-time-grid .fc-event.fc-not-start { | |||
border-top-width: 1px !important; | |||
} | |||
/* indicate the event is a continuation via "..." text */ | |||
.fc-time-grid .fc-event.fc-not-start:before { | |||
content: "..."; | |||
} | |||
/* time */ | |||
/* undo a previous declaration and let the time text span to a second line */ | |||
.fc-time-grid .fc-event .fc-time { | |||
white-space: normal !important; | |||
} | |||
/* hide the the time that is normally displayed... */ | |||
.fc-time-grid .fc-event .fc-time span { | |||
display: none; | |||
} | |||
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ | |||
.fc-time-grid .fc-event .fc-time:after { | |||
content: attr(data-full); | |||
} | |||
/* Vertical Scroller & Containers | |||
--------------------------------------------------------------------------------------------------*/ | |||
/* kill the scrollbars and allow natural height */ | |||
.fc-scroller, | |||
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ | |||
.fc-time-grid-container { /* */ | |||
overflow: visible !important; | |||
height: auto !important; | |||
} | |||
/* kill the horizontal border/padding used to compensate for scrollbars */ | |||
.fc-row { | |||
border: 0 !important; | |||
margin: 0 !important; | |||
} | |||
/* Button Controls | |||
--------------------------------------------------------------------------------------------------*/ | |||
.fc-button-group, | |||
.fc button { | |||
display: none; /* don't display any button-related controls */ | |||
} |
@@ -1,181 +0,0 @@ | |||
/*! | |||
* FullCalendar v3.4.0 Google Calendar Plugin | |||
* Docs & License: https://fullcalendar.io/ | |||
* (c) 2017 Adam Shaw | |||
*/ | |||
(function(factory) { | |||
if (typeof define === 'function' && define.amd) { | |||
define([ 'jquery' ], factory); | |||
} | |||
else if (typeof exports === 'object') { // Node/CommonJS | |||
module.exports = factory(require('jquery')); | |||
} | |||
else { | |||
factory(jQuery); | |||
} | |||
})(function($) { | |||
var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; | |||
var FC = $.fullCalendar; | |||
var applyAll = FC.applyAll; | |||
FC.sourceNormalizers.push(function(sourceOptions) { | |||
var googleCalendarId = sourceOptions.googleCalendarId; | |||
var url = sourceOptions.url; | |||
var match; | |||
// if the Google Calendar ID hasn't been explicitly defined | |||
if (!googleCalendarId && url) { | |||
// detect if the ID was specified as a single string. | |||
// will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. | |||
if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { | |||
googleCalendarId = url; | |||
} | |||
// try to scrape it out of a V1 or V3 API feed URL | |||
else if ( | |||
(match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) || | |||
(match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url)) | |||
) { | |||
googleCalendarId = decodeURIComponent(match[1]); | |||
} | |||
if (googleCalendarId) { | |||
sourceOptions.googleCalendarId = googleCalendarId; | |||
} | |||
} | |||
if (googleCalendarId) { // is this a Google Calendar? | |||
// make each Google Calendar source uneditable by default | |||
if (sourceOptions.editable == null) { | |||
sourceOptions.editable = false; | |||
} | |||
// We want removeEventSource to work, but it won't know about the googleCalendarId primitive. | |||
// Shoehorn it into the url, which will function as the unique primitive. Won't cause side effects. | |||
// This hack is obsolete since 2.2.3, but keep it so this plugin file is compatible with old versions. | |||
sourceOptions.url = googleCalendarId; | |||
} | |||
}); | |||
FC.sourceFetchers.push(function(sourceOptions, start, end, timezone) { | |||
if (sourceOptions.googleCalendarId) { | |||
return transformOptions(sourceOptions, start, end, timezone, this); // `this` is the calendar | |||
} | |||
}); | |||
function transformOptions(sourceOptions, start, end, timezone, calendar) { | |||
var url = API_BASE + '/' + encodeURIComponent(sourceOptions.googleCalendarId) + '/events?callback=?'; // jsonp | |||
var apiKey = sourceOptions.googleCalendarApiKey || calendar.opt('googleCalendarApiKey'); | |||
var success = sourceOptions.success; | |||
var data; | |||
var timezoneArg; // populated when a specific timezone. escaped to Google's liking | |||
function reportError(message, apiErrorObjs) { | |||
var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers | |||
// call error handlers | |||
(sourceOptions.googleCalendarError || $.noop).apply(calendar, errorObjs); | |||
(calendar.opt('googleCalendarError') || $.noop).apply(calendar, errorObjs); | |||
// print error to debug console | |||
FC.warn.apply(null, [ message ].concat(apiErrorObjs || [])); | |||
} | |||
if (!apiKey) { | |||
reportError("Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/"); | |||
return {}; // an empty source to use instead. won't fetch anything. | |||
} | |||
// The API expects an ISO8601 datetime with a time and timezone part. | |||
// Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each | |||
// side, guaranteeing we will receive all events in the desired range, albeit a superset. | |||
// .utc() will set a zone and give it a 00:00:00 time. | |||
if (!start.hasZone()) { | |||
start = start.clone().utc().add(-1, 'day'); | |||
} | |||
if (!end.hasZone()) { | |||
end = end.clone().utc().add(1, 'day'); | |||
} | |||
// when sending timezone names to Google, only accepts underscores, not spaces | |||
if (timezone && timezone != 'local') { | |||
timezoneArg = timezone.replace(' ', '_'); | |||
} | |||
data = $.extend({}, sourceOptions.data || {}, { | |||
key: apiKey, | |||
timeMin: start.format(), | |||
timeMax: end.format(), | |||
timeZone: timezoneArg, | |||
singleEvents: true, | |||
maxResults: 9999 | |||
}); | |||
return $.extend({}, sourceOptions, { | |||
googleCalendarId: null, // prevents source-normalizing from happening again | |||
url: url, | |||
data: data, | |||
startParam: false, // `false` omits this parameter. we already included it above | |||
endParam: false, // same | |||
timezoneParam: false, // same | |||
success: function(data) { | |||
var events = []; | |||
var successArgs; | |||
var successRes; | |||
if (data.error) { | |||
reportError('Google Calendar API: ' + data.error.message, data.error.errors); | |||
} | |||
else if (data.items) { | |||
$.each(data.items, function(i, entry) { | |||
var url = entry.htmlLink || null; | |||
// make the URLs for each event show times in the correct timezone | |||
if (timezoneArg && url !== null) { | |||
url = injectQsComponent(url, 'ctz=' + timezoneArg); | |||
} | |||
events.push({ | |||
id: entry.id, | |||
title: entry.summary, | |||
start: entry.start.dateTime || entry.start.date, // try timed. will fall back to all-day | |||
end: entry.end.dateTime || entry.end.date, // same | |||
url: url, | |||
location: entry.location, | |||
description: entry.description | |||
}); | |||
}); | |||
// call the success handler(s) and allow it to return a new events array | |||
successArgs = [ events ].concat(Array.prototype.slice.call(arguments, 1)); // forward other jq args | |||
successRes = applyAll(success, this, successArgs); | |||
if ($.isArray(successRes)) { | |||
return successRes; | |||
} | |||
} | |||
return events; | |||
} | |||
}); | |||
} | |||
// Injects a string like "arg=value" into the querystring of a URL | |||
function injectQsComponent(url, component) { | |||
// inject it after the querystring but before the fragment | |||
return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) { | |||
return (qs ? qs + '&' : '?') + component + hash; | |||
}); | |||
} | |||
}); |
@@ -1,480 +0,0 @@ | |||
/* | |||
http://www.JSON.org/json2.js | |||
2011-02-23 | |||
Public Domain. | |||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. | |||
See http://www.JSON.org/js.html | |||
This code should be minified before deployment. | |||
See http://javascript.crockford.com/jsmin.html | |||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO | |||
NOT CONTROL. | |||
This file creates a global JSON object containing two methods: stringify | |||
and parse. | |||
JSON.stringify(value, replacer, space) | |||
value any JavaScript value, usually an object or array. | |||
replacer an optional parameter that determines how object | |||
values are stringified for objects. It can be a | |||
function or an array of strings. | |||
space an optional parameter that specifies the indentation | |||
of nested structures. If it is omitted, the text will | |||
be packed without extra whitespace. If it is a number, | |||
it will specify the number of spaces to indent at each | |||
level. If it is a string (such as '\t' or ' '), | |||
it contains the characters used to indent at each level. | |||
This method produces a JSON text from a JavaScript value. | |||
When an object value is found, if the object contains a toJSON | |||
method, its toJSON method will be called and the result will be | |||
stringified. A toJSON method does not serialize: it returns the | |||
value represented by the name/value pair that should be serialized, | |||
or undefined if nothing should be serialized. The toJSON method | |||
will be passed the key associated with the value, and this will be | |||
bound to the value | |||
For example, this would serialize Dates as ISO strings. | |||
Date.prototype.toJSON = function (key) { | |||
function f(n) { | |||
// Format integers to have at least two digits. | |||
return n < 10 ? '0' + n : n; | |||
} | |||
return this.getUTCFullYear() + '-' + | |||
f(this.getUTCMonth() + 1) + '-' + | |||
f(this.getUTCDate()) + 'T' + | |||
f(this.getUTCHours()) + ':' + | |||
f(this.getUTCMinutes()) + ':' + | |||
f(this.getUTCSeconds()) + 'Z'; | |||
}; | |||
You can provide an optional replacer method. It will be passed the | |||
key and value of each member, with this bound to the containing | |||
object. The value that is returned from your method will be | |||
serialized. If your method returns undefined, then the member will | |||
be excluded from the serialization. | |||
If the replacer parameter is an array of strings, then it will be | |||
used to select the members to be serialized. It filters the results | |||
such that only members with keys listed in the replacer array are | |||
stringified. | |||
Values that do not have JSON representations, such as undefined or | |||
functions, will not be serialized. Such values in objects will be | |||
dropped; in arrays they will be replaced with null. You can use | |||
a replacer function to replace those with JSON values. | |||
JSON.stringify(undefined) returns undefined. | |||
The optional space parameter produces a stringification of the | |||
value that is filled with line breaks and indentation to make it | |||
easier to read. | |||
If the space parameter is a non-empty string, then that string will | |||
be used for indentation. If the space parameter is a number, then | |||
the indentation will be that many spaces. | |||
Example: | |||
text = JSON.stringify(['e', {pluribus: 'unum'}]); | |||
// text is '["e",{"pluribus":"unum"}]' | |||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); | |||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' | |||
text = JSON.stringify([new Date()], function (key, value) { | |||
return this[key] instanceof Date ? | |||
'Date(' + this[key] + ')' : value; | |||
}); | |||
// text is '["Date(---current time---)"]' | |||
JSON.parse(text, reviver) | |||
This method parses a JSON text to produce an object or array. | |||
It can throw a SyntaxError exception. | |||
The optional reviver parameter is a function that can filter and | |||
transform the results. It receives each of the keys and values, | |||
and its return value is used instead of the original value. | |||
If it returns what it received, then the structure is not modified. | |||
If it returns undefined then the member is deleted. | |||
Example: | |||
// Parse the text. Values that look like ISO date strings will | |||
// be converted to Date objects. | |||
myData = JSON.parse(text, function (key, value) { | |||
var a; | |||
if (typeof value === 'string') { | |||
a = | |||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); | |||
if (a) { | |||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], | |||
+a[5], +a[6])); | |||
} | |||
} | |||
return value; | |||
}); | |||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { | |||
var d; | |||
if (typeof value === 'string' && | |||
value.slice(0, 5) === 'Date(' && | |||
value.slice(-1) === ')') { | |||
d = new Date(value.slice(5, -1)); | |||
if (d) { | |||
return d; | |||
} | |||
} | |||
return value; | |||
}); | |||
This is a reference implementation. You are free to copy, modify, or | |||
redistribute. | |||
*/ | |||
/*jslint evil: true, strict: false, regexp: false */ | |||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, | |||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, | |||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, | |||
lastIndex, length, parse, prototype, push, replace, slice, stringify, | |||
test, toJSON, toString, valueOf | |||
*/ | |||
// Create a JSON object only if one does not already exist. We create the | |||
// methods in a closure to avoid creating global variables. | |||
var JSON; | |||
if (!JSON) { | |||
JSON = {}; | |||
} | |||
(function () { | |||
"use strict"; | |||
function f(n) { | |||
// Format integers to have at least two digits. | |||
return n < 10 ? '0' + n : n; | |||
} | |||
if (typeof Date.prototype.toJSON !== 'function') { | |||
Date.prototype.toJSON = function (key) { | |||
return isFinite(this.valueOf()) ? | |||
this.getUTCFullYear() + '-' + | |||
f(this.getUTCMonth() + 1) + '-' + | |||
f(this.getUTCDate()) + 'T' + | |||
f(this.getUTCHours()) + ':' + | |||
f(this.getUTCMinutes()) + ':' + | |||
f(this.getUTCSeconds()) + 'Z' : null; | |||
}; | |||
String.prototype.toJSON = | |||
Number.prototype.toJSON = | |||
Boolean.prototype.toJSON = function (key) { | |||
return this.valueOf(); | |||
}; | |||
} | |||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | |||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | |||
gap, | |||
indent, | |||
meta = { // table of character substitutions | |||
'\b': '\\b', | |||
'\t': '\\t', | |||
'\n': '\\n', | |||
'\f': '\\f', | |||
'\r': '\\r', | |||
'"' : '\\"', | |||
'\\': '\\\\' | |||
}, | |||
rep; | |||
function quote(string) { | |||
// If the string contains no control characters, no quote characters, and no | |||
// backslash characters, then we can safely slap some quotes around it. | |||
// Otherwise we must also replace the offending characters with safe escape | |||
// sequences. | |||
escapable.lastIndex = 0; | |||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) { | |||
var c = meta[a]; | |||
return typeof c === 'string' ? c : | |||
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | |||
}) + '"' : '"' + string + '"'; | |||
} | |||
function str(key, holder) { | |||
// Produce a string from holder[key]. | |||
var i, // The loop counter. | |||
k, // The member key. | |||
v, // The member value. | |||
length, | |||
mind = gap, | |||
partial, | |||
value = holder[key]; | |||
// If the value has a toJSON method, call it to obtain a replacement value. | |||
if (value && typeof value === 'object' && | |||
typeof value.toJSON === 'function') { | |||
value = value.toJSON(key); | |||
} | |||
// If we were called with a replacer function, then call the replacer to | |||
// obtain a replacement value. | |||
if (typeof rep === 'function') { | |||
value = rep.call(holder, key, value); | |||
} | |||
// What happens next depends on the value's type. | |||
switch (typeof value) { | |||
case 'string': | |||
return quote(value); | |||
case 'number': | |||
// JSON numbers must be finite. Encode non-finite numbers as null. | |||
return isFinite(value) ? String(value) : 'null'; | |||
case 'boolean': | |||
case 'null': | |||
// If the value is a boolean or null, convert it to a string. Note: | |||
// typeof null does not produce 'null'. The case is included here in | |||
// the remote chance that this gets fixed someday. | |||
return String(value); | |||
// If the type is 'object', we might be dealing with an object or an array or | |||
// null. | |||
case 'object': | |||
// Due to a specification blunder in ECMAScript, typeof null is 'object', | |||
// so watch out for that case. | |||
if (!value) { | |||
return 'null'; | |||
} | |||
// Make an array to hold the partial results of stringifying this object value. | |||
gap += indent; | |||
partial = []; | |||
// Is the value an array? | |||
if (Object.prototype.toString.apply(value) === '[object Array]') { | |||
// The value is an array. Stringify every element. Use null as a placeholder | |||
// for non-JSON values. | |||
length = value.length; | |||
for (i = 0; i < length; i += 1) { | |||
partial[i] = str(i, value) || 'null'; | |||
} | |||
// Join all of the elements together, separated with commas, and wrap them in | |||
// brackets. | |||
v = partial.length === 0 ? '[]' : gap ? | |||
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : | |||
'[' + partial.join(',') + ']'; | |||
gap = mind; | |||
return v; | |||
} | |||
// If the replacer is an array, use it to select the members to be stringified. | |||
if (rep && typeof rep === 'object') { | |||
length = rep.length; | |||
for (i = 0; i < length; i += 1) { | |||
if (typeof rep[i] === 'string') { | |||
k = rep[i]; | |||
v = str(k, value); | |||
if (v) { | |||
partial.push(quote(k) + (gap ? ': ' : ':') + v); | |||
} | |||
} | |||
} | |||
} else { | |||
// Otherwise, iterate through all of the keys in the object. | |||
for (k in value) { | |||
if (Object.prototype.hasOwnProperty.call(value, k)) { | |||
v = str(k, value); | |||
if (v) { | |||
partial.push(quote(k) + (gap ? ': ' : ':') + v); | |||
} | |||
} | |||
} | |||
} | |||
// Join all of the member texts together, separated with commas, | |||
// and wrap them in braces. | |||
v = partial.length === 0 ? '{}' : gap ? | |||
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : | |||
'{' + partial.join(',') + '}'; | |||
gap = mind; | |||
return v; | |||
} | |||
} | |||
// If the JSON object does not yet have a stringify method, give it one. | |||
if (typeof JSON.stringify !== 'function') { | |||
JSON.stringify = function (value, replacer, space) { | |||
// The stringify method takes a value and an optional replacer, and an optional | |||
// space parameter, and returns a JSON text. The replacer can be a function | |||
// that can replace values, or an array of strings that will select the keys. | |||
// A default replacer method can be provided. Use of the space parameter can | |||
// produce text that is more easily readable. | |||
var i; | |||
gap = ''; | |||
indent = ''; | |||
// If the space parameter is a number, make an indent string containing that | |||
// many spaces. | |||
if (typeof space === 'number') { | |||
for (i = 0; i < space; i += 1) { | |||
indent += ' '; | |||
} | |||
// If the space parameter is a string, it will be used as the indent string. | |||
} else if (typeof space === 'string') { | |||
indent = space; | |||
} | |||
// If there is a replacer, it must be a function or an array. | |||
// Otherwise, throw an error. | |||
rep = replacer; | |||
if (replacer && typeof replacer !== 'function' && | |||
(typeof replacer !== 'object' || | |||
typeof replacer.length !== 'number')) { | |||
throw new Error('JSON.stringify'); | |||
} | |||
// Make a fake root object containing our value under the key of ''. | |||
// Return the result of stringifying the value. | |||
return str('', {'': value}); | |||
}; | |||
} | |||
// If the JSON object does not yet have a parse method, give it one. | |||
if (typeof JSON.parse !== 'function') { | |||
JSON.parse = function (text, reviver) { | |||
// The parse method takes a text and an optional reviver function, and returns | |||
// a JavaScript value if the text is a valid JSON text. | |||
var j; | |||
function walk(holder, key) { | |||
// The walk method is used to recursively walk the resulting structure so | |||
// that modifications can be made. | |||
var k, v, value = holder[key]; | |||
if (value && typeof value === 'object') { | |||
for (k in value) { | |||
if (Object.prototype.hasOwnProperty.call(value, k)) { | |||
v = walk(value, k); | |||
if (v !== undefined) { | |||
value[k] = v; | |||
} else { | |||
delete value[k]; | |||
} | |||
} | |||
} | |||
} | |||
return reviver.call(holder, key, value); | |||
} | |||
// Parsing happens in four stages. In the first stage, we replace certain | |||
// Unicode characters with escape sequences. JavaScript handles many characters | |||
// incorrectly, either silently deleting them, or treating them as line endings. | |||
text = String(text); | |||
cx.lastIndex = 0; | |||
if (cx.test(text)) { | |||
text = text.replace(cx, function (a) { | |||
return '\\u' + | |||
('0000' + a.charCodeAt(0).toString(16)).slice(-4); | |||
}); | |||
} | |||
// In the second stage, we run the text against regular expressions that look | |||
// for non-JSON patterns. We are especially concerned with '()' and 'new' | |||
// because they can cause invocation, and '=' because it can cause mutation. | |||
// But just to be safe, we want to reject all unexpected forms. | |||
// We split the second stage into 4 regexp operations in order to work around | |||
// crippling inefficiencies in IE's and Safari's regexp engines. First we | |||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we | |||
// replace all simple value tokens with ']' characters. Third, we delete all | |||
// open brackets that follow a colon or comma or that begin the text. Finally, | |||
// we look to see that the remaining characters are only whitespace or ']' or | |||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. | |||
if (/^[\],:{}\s]*$/ | |||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') | |||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') | |||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | |||
// In the third stage we use the eval function to compile the text into a | |||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity | |||
// in JavaScript: it can begin a block or an object literal. We wrap the text | |||
// in parens to eliminate the ambiguity. | |||
j = eval('(' + text + ')'); | |||
// In the optional fourth stage, we recursively walk the new structure, passing | |||
// each name/value pair to a reviver function for possible transformation. | |||
return typeof reviver === 'function' ? | |||
walk({'': j}, '') : j; | |||
} | |||
// If the text is not JSON parseable, then a SyntaxError is thrown. | |||
throw new SyntaxError('JSON.parse'); | |||
}; | |||
} | |||
}()); |
@@ -169,14 +169,16 @@ def get_task_log_file_path(task_id, stream_type): | |||
@frappe.whitelist(allow_guest=True) | |||
def can_subscribe_doc(doctype, docname, sid): | |||
def can_subscribe_doc(doctype, docname): | |||
if os.environ.get('CI'): | |||
return True | |||
from frappe.sessions import Session | |||
from frappe.exceptions import PermissionError | |||
session = Session(None, resume=True).get_session_data() | |||
if not frappe.has_permission(user=session.user, doctype=doctype, doc=docname, ptype='read'): | |||
raise PermissionError() | |||
return True | |||
@frappe.whitelist(allow_guest=True) | |||
@@ -142,10 +142,10 @@ io.on('connection', function (socket) { | |||
socket.on('doc_open', function (doctype, docname) { | |||
can_subscribe_doc({ | |||
socket: socket, | |||
sid: sid, | |||
doctype: doctype, | |||
docname: docname, | |||
socket, | |||
sid, | |||
doctype, | |||
docname, | |||
callback: () => { | |||
var room = get_open_doc_room(socket, doctype, docname); | |||
socket.join(room); | |||