efront 3.9.10 → 3.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pivot/api.yml +1 -1
- package/apps/pivot/auth/login.js +16 -10
- package/apps/pivot/auth/login.less +3 -0
- package/apps/pivot/home/welcome.js +1 -1
- package/apps/pivot/main.js +36 -8
- package/apps/pivot/task/invoke.js +1 -0
- package/apps/pivot/task/list.js +12 -3
- package/apps/pivot/task/rsync.html +16 -0
- package/apps/pivot/task/rsync.js +39 -0
- package/apps/pivot/task/rsync.less +10 -0
- package/apps/pivot/wow/root.js +3 -3
- package/coms/basic/cookie.js +91 -0
- package/coms/basic/parseURL.js +2 -2
- package/coms/basic/parseURL_test.js +7 -1
- package/coms/basic/seek.js +2 -1
- package/coms/basic/sortname.js +4 -4
- package/coms/frame/edit.js +5 -0
- package/coms/frame/left.html +4 -4
- package/coms/frame/left.js +17 -14
- package/coms/frame/left.less +2 -1
- package/coms/frame/list.js +0 -1
- package/coms/pivot/left-header.html +1 -0
- package/coms/pivot/left-header.js +15 -0
- package/coms/pivot/left-header.less +11 -0
- package/coms/zimoli/alert.js +2 -0
- package/coms/zimoli/bind.js +14 -0
- package/coms/zimoli/button.less +1 -0
- package/coms/zimoli/cless.js +1 -1
- package/coms/zimoli/cross.js +53 -71
- package/coms/zimoli/data.js +26 -16
- package/coms/zimoli/on.js +10 -0
- package/coms/zimoli/popup.js +8 -1
- package/coms/zimoli/select.js +9 -4
- package/coms/zimoli/view.less +8 -1
- package/package.json +1 -1
- package/public/efront.js +1 -1
package/coms/zimoli/cross.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
var FormData = this.FormData;
|
|
2
|
-
var cookiesMap =
|
|
2
|
+
var { cookiesMap, getCookies, addCookie } = cookie;
|
|
3
|
+
var saveCookie = lazy(function () {
|
|
4
|
+
sessionStorage.setItem(cookieItemsInSessionStorageKey, JSON.stringify(cookiesMap));
|
|
5
|
+
}, 20);
|
|
3
6
|
// ///// 1 ////////// 2 /////// 3 //// 4 //
|
|
4
7
|
var domainReg = /^(?:(https?)\:)?\/\/(.*?)(?:\/(.*?))?([\?#].*)?$/i;
|
|
5
8
|
var base = domainReg.test(location.href) ? '/' : "http://efront.cc/";
|
|
@@ -33,71 +36,6 @@ function getRequestProtocol(url) {
|
|
|
33
36
|
}
|
|
34
37
|
return "http:";
|
|
35
38
|
}
|
|
36
|
-
function getCookies(domainPath) {
|
|
37
|
-
var cookieObject = {};
|
|
38
|
-
var splited = domainPath.split("/");
|
|
39
|
-
var domain = splited[0];
|
|
40
|
-
do {
|
|
41
|
-
var copy = splited.slice(0);
|
|
42
|
-
do {
|
|
43
|
-
domainPath = copy.join("/");
|
|
44
|
-
var cookie = cookiesMap[domainPath];
|
|
45
|
-
if (cookie) {
|
|
46
|
-
for (var k in cookie) {
|
|
47
|
-
if (!cookieObject[k]) {
|
|
48
|
-
cookieObject[k] = cookie[k];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
copy.pop();
|
|
53
|
-
} while (copy.length);
|
|
54
|
-
domain = domain.replace(/^.*?(\.|$)/, "");
|
|
55
|
-
splited[0] = domain;
|
|
56
|
-
} while (domain.length);
|
|
57
|
-
return serialize(cookieObject, ";");
|
|
58
|
-
}
|
|
59
|
-
function addCookie(cookie, originDomain = "") {
|
|
60
|
-
if (cookie) {
|
|
61
|
-
clearTimeout(addCookie.save_timer);
|
|
62
|
-
addCookie.save_timer = setTimeout(function () {
|
|
63
|
-
sessionStorage.setItem(cookieItemsInSessionStorageKey, JSON.stringify(cookiesMap));
|
|
64
|
-
}, 20);
|
|
65
|
-
cookie.replace(/(^|;|,)\s*(expires)=(\w*),([^=]*)(;|$)/ig, "$1$2=$3.$4$5")
|
|
66
|
-
.split(/,\s*/).map(function (cookie) {
|
|
67
|
-
var cookieObject = {};
|
|
68
|
-
var result = cookie.split(/;\s*/);
|
|
69
|
-
result.slice(1).map(function (kev) {
|
|
70
|
-
var kvs = /^(.+?)\=(.+?)$/.exec(kev);
|
|
71
|
-
if (kvs) {
|
|
72
|
-
var [, k, v] = kvs;
|
|
73
|
-
cookieObject[k.toLowerCase()] = v;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
var { path, domain } = cookieObject;
|
|
77
|
-
if (!domain) {
|
|
78
|
-
domain = originDomain.replace(/[^\/]+$/, "");
|
|
79
|
-
}
|
|
80
|
-
if (/^\./.test(domain)) {
|
|
81
|
-
domain = domain.replace(/^\.+/, "");
|
|
82
|
-
}
|
|
83
|
-
var destPath;
|
|
84
|
-
if (/^\//.test(path)) {
|
|
85
|
-
destPath = domain.replace(/\/.*$/, "") + path;
|
|
86
|
-
} else {
|
|
87
|
-
destPath = domain;
|
|
88
|
-
}
|
|
89
|
-
destPath = destPath.replace(/\/+$/, "");
|
|
90
|
-
if (originDomain.indexOf(destPath) >= 0) {
|
|
91
|
-
var cookieMap = parseKV(result[0], ";");
|
|
92
|
-
if (!cookiesMap[destPath]) {
|
|
93
|
-
cookiesMap[destPath] = cookieMap;
|
|
94
|
-
} else {
|
|
95
|
-
extend(cookiesMap[destPath], cookieMap);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
39
|
function isChildPath(relative, path) {
|
|
102
40
|
return relative.replace(/^(.*\/)[^\/]*$/, path);
|
|
103
41
|
}
|
|
@@ -154,13 +92,19 @@ function cross(method, url, headers) {
|
|
|
154
92
|
xhr.onerror = function (e) {
|
|
155
93
|
onerror(e);
|
|
156
94
|
};
|
|
95
|
+
xhr.abort = function () {
|
|
96
|
+
removeFromList(requests, this);
|
|
97
|
+
remove(this);
|
|
98
|
+
};
|
|
157
99
|
} else {
|
|
158
100
|
var nocross = notCross(url);
|
|
159
101
|
var xhr = new XMLHttpRequest;
|
|
160
102
|
var abort = xhr.abort;
|
|
161
103
|
xhr.abort = function () {
|
|
162
104
|
xhr.onreadystatechange = null;
|
|
105
|
+
removeFromList(requests, this);
|
|
163
106
|
abort.call(this);
|
|
107
|
+
clearTimeout(sendtimer);
|
|
164
108
|
};
|
|
165
109
|
|
|
166
110
|
xhr.onreadystatechange = function () {
|
|
@@ -168,6 +112,7 @@ function cross(method, url, headers) {
|
|
|
168
112
|
if (xhr.getResponseHeader && !nocross) {
|
|
169
113
|
var cookie = xhr.getResponseHeader("efront-cookie");
|
|
170
114
|
addCookie(cookie, originDomain);
|
|
115
|
+
saveCookie();
|
|
171
116
|
}
|
|
172
117
|
switch (xhr.status) {
|
|
173
118
|
case 0:
|
|
@@ -217,8 +162,7 @@ function cross(method, url, headers) {
|
|
|
217
162
|
onerrors.push(oh);
|
|
218
163
|
flush();
|
|
219
164
|
};
|
|
220
|
-
|
|
221
|
-
digest();
|
|
165
|
+
var fire = function () {
|
|
222
166
|
var isform = /^f/i.test(method);
|
|
223
167
|
if (isform) {
|
|
224
168
|
if (method === 'form') method = 'post';
|
|
@@ -248,10 +192,13 @@ function cross(method, url, headers) {
|
|
|
248
192
|
}
|
|
249
193
|
Object.keys(realHeaders).forEach(key => setRequestHeader.call(xhr, key, realHeaders[key]));
|
|
250
194
|
send.call(xhr, datas);
|
|
251
|
-
|
|
195
|
+
digest();
|
|
196
|
+
};
|
|
197
|
+
var sendtimer = setTimeout(fire, 0);
|
|
252
198
|
}
|
|
253
199
|
var loaded, errored;
|
|
254
200
|
var onload = function (xhr) {
|
|
201
|
+
removeFromList(requests, xhr);
|
|
255
202
|
if (xhr.decoder) {
|
|
256
203
|
xhr = xhr.decoder(xhr);
|
|
257
204
|
}
|
|
@@ -259,11 +206,19 @@ function cross(method, url, headers) {
|
|
|
259
206
|
flush();
|
|
260
207
|
digest();
|
|
261
208
|
};
|
|
262
|
-
var
|
|
263
|
-
|
|
209
|
+
var onerror1 = function (e) {
|
|
210
|
+
removeFromList(requests, e);
|
|
211
|
+
errored = e || "未知错误!";
|
|
264
212
|
flush();
|
|
265
213
|
digest();
|
|
266
214
|
};
|
|
215
|
+
var onerror = async function (e) {
|
|
216
|
+
for (var r of reforms) {
|
|
217
|
+
var r = await reform(r, { method, url, status: xhr.status, headers: _headers }, fire, onerror1, e);
|
|
218
|
+
if (r === false) return;
|
|
219
|
+
}
|
|
220
|
+
onerror1(e);
|
|
221
|
+
};
|
|
267
222
|
var flush = function () {
|
|
268
223
|
var then = xhr.then;
|
|
269
224
|
delete xhr.then;
|
|
@@ -317,9 +272,11 @@ function cross(method, url, headers) {
|
|
|
317
272
|
flush();
|
|
318
273
|
return xhr;
|
|
319
274
|
};
|
|
275
|
+
requests.push(xhr);
|
|
320
276
|
return xhr;
|
|
321
277
|
}
|
|
322
278
|
function addDirect(a) {
|
|
279
|
+
if (cors_hosts.indexOf(a) >= 0) return;
|
|
323
280
|
if (typeof a === 'string' || a instanceof RegExp) cors_hosts.push(a);
|
|
324
281
|
}
|
|
325
282
|
function notCross(domain) {
|
|
@@ -335,9 +292,34 @@ function notCross(domain) {
|
|
|
335
292
|
}
|
|
336
293
|
return false;
|
|
337
294
|
}
|
|
295
|
+
|
|
296
|
+
var requests = [];
|
|
297
|
+
var reforms = [];
|
|
298
|
+
function reform(r, info, fire, cancel, e) {
|
|
299
|
+
var fired = false;
|
|
300
|
+
return r(info, function () {
|
|
301
|
+
if (fired) return;
|
|
302
|
+
fired = true;
|
|
303
|
+
fire();
|
|
304
|
+
}, function () {
|
|
305
|
+
if (fired) return;
|
|
306
|
+
fired = true;
|
|
307
|
+
cancel(e);
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
function addReform(r) {
|
|
311
|
+
if (isFunction(r)) reforms.push(r);
|
|
312
|
+
}
|
|
338
313
|
extend(cross, {
|
|
314
|
+
requests,
|
|
315
|
+
abortAll() {
|
|
316
|
+
var rs = requests.splice(0, requests.length);
|
|
317
|
+
for (var r of rs) r.abort();
|
|
318
|
+
},
|
|
339
319
|
setHost,
|
|
320
|
+
addReform,
|
|
340
321
|
getCookies,
|
|
322
|
+
saveCookie,
|
|
341
323
|
addCookie,
|
|
342
324
|
addDirect,
|
|
343
325
|
getCrossUrl
|
package/coms/zimoli/data.js
CHANGED
|
@@ -72,7 +72,13 @@ const formulaters = {
|
|
|
72
72
|
return formulate(data, params);
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
-
|
|
75
|
+
var seekFromSource = function (obj, base) {
|
|
76
|
+
var source = dataSourceMap;
|
|
77
|
+
if (base && base in dataSourceMap) source = source[base];
|
|
78
|
+
obj = seek(source, obj);
|
|
79
|
+
if (isObject(obj)) for (var k in obj) if (obj[k] === dataSourceMap) delete obj[k];
|
|
80
|
+
return obj;
|
|
81
|
+
};
|
|
76
82
|
function getErrorMessage(error = this) {
|
|
77
83
|
if (!isObject(error)) return String(error);
|
|
78
84
|
if (error instanceof Error) return String(error);
|
|
@@ -533,9 +539,9 @@ var privates = {
|
|
|
533
539
|
rest.forEach(k => delete params[k]);
|
|
534
540
|
return { method: realmethod, coinmethod, selector: method.slice(spliterIndex + 1), search, baseuri, uri, params };
|
|
535
541
|
},
|
|
536
|
-
loadIgnoreConfig(method, url,
|
|
542
|
+
loadIgnoreConfig(method, url, params1, api) {
|
|
537
543
|
var headers = api && api.headers;
|
|
538
|
-
var { method: realmethod, uri, baseuri, coinmethod, search, selector, params } = this.prepare(method, url,
|
|
544
|
+
var { method: realmethod, uri, baseuri, coinmethod, search, selector, params } = this.prepare(method, url, params1);
|
|
539
545
|
var id = realmethod + " " + baseuri;
|
|
540
546
|
var promise = cachedLoadingPromise[id];
|
|
541
547
|
var temp = JSON.stringify(params);
|
|
@@ -543,18 +549,19 @@ var privates = {
|
|
|
543
549
|
if (!promise || currentTime - promise.time > 60 || temp !== promise.params || promise.search !== search) {
|
|
544
550
|
var promise = new Promise(function (ok, oh) {
|
|
545
551
|
if (headers) {
|
|
546
|
-
headers =
|
|
552
|
+
headers = seekFromSource(headers, api.base);
|
|
547
553
|
}
|
|
548
554
|
cross(realmethod, uri, headers).send(params).done(e => {
|
|
549
555
|
ok(e.response || e.responseText);
|
|
550
556
|
}).error(xhr => {
|
|
551
557
|
try {
|
|
552
558
|
var e = getErrorMessage(parseData(xhr.response || xhr.responseText || xhr.statusText || xhr.status));
|
|
553
|
-
oh({ status: xhr.status, error: e, toString: getErrorMessage })
|
|
559
|
+
oh({ status: xhr.status, api, params: params1, error: e, toString: getErrorMessage })
|
|
554
560
|
} catch (error) {
|
|
555
561
|
oh(error);
|
|
556
562
|
}
|
|
557
563
|
});
|
|
564
|
+
updateLoadingCount();
|
|
558
565
|
});
|
|
559
566
|
promise.search = search;
|
|
560
567
|
promise.params = temp;
|
|
@@ -609,7 +616,6 @@ var loadInstance = function (storage, id) {
|
|
|
609
616
|
};
|
|
610
617
|
|
|
611
618
|
function responseCrash(e, data) {
|
|
612
|
-
if (data.is_loading) this.loading_count--;
|
|
613
619
|
data.is_errored = true;
|
|
614
620
|
data.is_loading = false;
|
|
615
621
|
data.error_message = getErrorMessage(e);
|
|
@@ -619,19 +625,24 @@ function responseCrash(e, data) {
|
|
|
619
625
|
} else {
|
|
620
626
|
data.error = e;
|
|
621
627
|
}
|
|
622
|
-
error_report(
|
|
628
|
+
error_report(e, e.status < 500 ? 'warn' : 'error');
|
|
629
|
+
updateLoadingCount();
|
|
623
630
|
}
|
|
624
631
|
var getData = function () { return this.data };
|
|
632
|
+
var updateLoadingCount = function () {
|
|
633
|
+
data.loading_count = cross.requests.length;
|
|
634
|
+
};
|
|
625
635
|
var data = {
|
|
626
636
|
decodeStructure,
|
|
627
637
|
encodeStructure,
|
|
638
|
+
abortAll: cross.abortAll,
|
|
628
639
|
responseLoaded(response) {
|
|
629
640
|
if (isObject(response)) {
|
|
630
641
|
response.is_loaded = true;
|
|
631
642
|
response.is_loading = false;
|
|
632
643
|
if (response.then === LoadingArray_then) delete response.then;
|
|
633
644
|
}
|
|
634
|
-
|
|
645
|
+
updateLoadingCount();
|
|
635
646
|
},
|
|
636
647
|
responseCrash,
|
|
637
648
|
responseLoading(response) {
|
|
@@ -640,7 +651,6 @@ var data = {
|
|
|
640
651
|
response.is_loading = true;
|
|
641
652
|
response.then = LoadingArray_then;
|
|
642
653
|
}
|
|
643
|
-
this.loading_count++;
|
|
644
654
|
},
|
|
645
655
|
setReporter(report, checker) {
|
|
646
656
|
if (report instanceof Function) {
|
|
@@ -840,19 +850,18 @@ var data = {
|
|
|
840
850
|
if (promise1 !== instance.loading_promise) throw outdate;
|
|
841
851
|
if (instance.loading) {
|
|
842
852
|
instance.loading.abort();
|
|
843
|
-
data.loading_count--;
|
|
844
853
|
}
|
|
845
854
|
this.responseLoading(instance);
|
|
846
|
-
var
|
|
847
|
-
if (!privates.validApi(api,
|
|
855
|
+
var params2 = privates.pack(sid, params1);
|
|
856
|
+
if (!privates.validApi(api, params2)) throw aborted;
|
|
848
857
|
let url = api.url;
|
|
849
858
|
var base = api.base;
|
|
850
859
|
if (base) url = base + api.path;
|
|
851
|
-
var { method, uri, params, selector } = privates.prepare(api.method, url,
|
|
860
|
+
var { method, uri, params, selector } = privates.prepare(api.method, url, params2);
|
|
852
861
|
var promise = new Promise(function (ok, oh) {
|
|
853
862
|
var headers = api.headers;
|
|
854
863
|
if (headers) {
|
|
855
|
-
headers =
|
|
864
|
+
headers = seekFromSource(headers, api.base);
|
|
856
865
|
}
|
|
857
866
|
instance.loading = cross(method, uri, headers).send(params).done(xhr => {
|
|
858
867
|
if (instance.loading !== xhr) return oh(aborted);
|
|
@@ -862,11 +871,13 @@ var data = {
|
|
|
862
871
|
if (instance.loading !== xhr) return oh(aborted);
|
|
863
872
|
instance.loading = null;
|
|
864
873
|
try {
|
|
865
|
-
|
|
874
|
+
var e = getErrorMessage(parseData(xhr.response || xhr.responseText || xhr.statusText || xhr.status));
|
|
875
|
+
oh({ status: xhr.status, error: e, api, params: params2, toString: getErrorMessage })
|
|
866
876
|
} catch (error) {
|
|
867
877
|
oh(error);
|
|
868
878
|
}
|
|
869
879
|
});
|
|
880
|
+
updateLoadingCount();
|
|
870
881
|
}).then(function (response) {
|
|
871
882
|
return transpile(seekResponse(parseData(response), selector), api.transpile, api.root);
|
|
872
883
|
});
|
|
@@ -884,7 +895,6 @@ var data = {
|
|
|
884
895
|
});
|
|
885
896
|
promise1.catch((e) => {
|
|
886
897
|
if (e === outdate) return;
|
|
887
|
-
if (e === aborted) return data.loading_count--;
|
|
888
898
|
this.responseCrash(e, instance);
|
|
889
899
|
});
|
|
890
900
|
|
package/coms/zimoli/on.js
CHANGED
|
@@ -167,8 +167,13 @@ if (is_addEventListener_enabled) {
|
|
|
167
167
|
var eventtypes = parseEventTypes(k);
|
|
168
168
|
k = k.replace(eventtypereg, '');
|
|
169
169
|
function addhandler(element, handler, firstmost) {
|
|
170
|
+
var target = this;
|
|
170
171
|
handler = wrapHandler(handler);
|
|
171
172
|
if (eventtypes.capture) firstmost = true;
|
|
173
|
+
if (target && element !== target) {
|
|
174
|
+
handler = handler.bind(element);
|
|
175
|
+
element = target;
|
|
176
|
+
}
|
|
172
177
|
if (k === changes_key) {
|
|
173
178
|
if (!element.needchanges) element.needchanges = 0;
|
|
174
179
|
element.needchanges++;
|
|
@@ -207,11 +212,16 @@ if (is_addEventListener_enabled) {
|
|
|
207
212
|
|
|
208
213
|
if (handlersMap[on_event_path]) return handlersMap[on_event_path];
|
|
209
214
|
function addhandler(element, handler, firstmost = false) {
|
|
215
|
+
var target = this;
|
|
210
216
|
handler = wrapHandler(handler);
|
|
211
217
|
if (eventtypes.capture) {
|
|
212
218
|
console.warn("当前运行环境不支持事件capture");
|
|
213
219
|
firstmost = true;
|
|
214
220
|
}
|
|
221
|
+
if (target && element !== target) {
|
|
222
|
+
handler = handler.bind(element);
|
|
223
|
+
element = target;
|
|
224
|
+
}
|
|
215
225
|
if (k === changes_key) {
|
|
216
226
|
if (!element.needchanges) element.needchanges = 0;
|
|
217
227
|
element.needchanges++;
|
package/coms/zimoli/popup.js
CHANGED
|
@@ -105,9 +105,16 @@ var popup_path = function (path = "", parameters, target) {
|
|
|
105
105
|
element.style.opacity = 1;
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
callbacks.forEach(f => f(element));
|
|
108
109
|
};
|
|
110
|
+
var callbacks = [];
|
|
109
111
|
popup.prepare(path, fullfill);
|
|
110
|
-
return element
|
|
112
|
+
return element || {
|
|
113
|
+
then(ok) {
|
|
114
|
+
if (element) return ok(element);
|
|
115
|
+
else callbacks.push(ok);
|
|
116
|
+
}, fullfill
|
|
117
|
+
};
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
var popup_view = function (element, target, style) {
|
package/coms/zimoli/select.js
CHANGED
|
@@ -42,6 +42,9 @@ function select(target, list, removeOnSelect, direction) {
|
|
|
42
42
|
direction = removeOnSelect;
|
|
43
43
|
removeOnSelect = arguments[3];
|
|
44
44
|
}
|
|
45
|
+
if (direction === undefined) {
|
|
46
|
+
direction = target.getAttribute("direction") || target.direction;
|
|
47
|
+
}
|
|
45
48
|
if (!target) {
|
|
46
49
|
target = document.createElement("select");
|
|
47
50
|
}
|
|
@@ -63,11 +66,13 @@ function select(target, list, removeOnSelect, direction) {
|
|
|
63
66
|
var onlistchange = function () {
|
|
64
67
|
if (target.multiple) {
|
|
65
68
|
} else {
|
|
66
|
-
if (
|
|
67
|
-
|
|
69
|
+
if (target.value !== this.value) {
|
|
70
|
+
if (!savedOptions) {
|
|
71
|
+
target.innerHTML = `<option selected value="${this.value}">${this.name || this.value}</option>`
|
|
72
|
+
}
|
|
73
|
+
target.value = this.value;
|
|
74
|
+
dispatch(target, "change");
|
|
68
75
|
}
|
|
69
|
-
target.value = this.value;
|
|
70
|
-
dispatch(target, "change");
|
|
71
76
|
}
|
|
72
77
|
};
|
|
73
78
|
var onlistclick = function (event) {
|
package/coms/zimoli/view.less
CHANGED
|
@@ -52,12 +52,19 @@ body>& {
|
|
|
52
52
|
position: relative;
|
|
53
53
|
position: sticky;
|
|
54
54
|
line-height: 20px;
|
|
55
|
-
white-space: nowrap;
|
|
56
55
|
overflow: hidden;
|
|
57
56
|
text-overflow: ellipsis;
|
|
58
57
|
color: #333;
|
|
59
58
|
padding: 12px 16px 10px 16px;
|
|
60
59
|
|
|
60
|
+
&:before {
|
|
61
|
+
display: block;
|
|
62
|
+
content: "";
|
|
63
|
+
width: 20px;
|
|
64
|
+
height: 20px;
|
|
65
|
+
float: right;
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
&:not(:last-child) {
|
|
62
69
|
margin: 0 0 -42px;
|
|
63
70
|
}
|