mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
up2k-cli: add turbo button
This commit is contained in:
parent
efbf8d7e0d
commit
22971a6be4
|
@ -29,10 +29,10 @@ body {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
max-width: 34em;
|
max-width: 34em;
|
||||||
background: #222;
|
background: #222;
|
||||||
border: 0 solid #555;
|
border: 0 solid #777;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
padding: 0 1em;
|
padding: 0 1.3em;
|
||||||
height: 0;
|
height: 0;
|
||||||
opacity: .1;
|
opacity: .1;
|
||||||
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
||||||
|
@ -40,19 +40,31 @@ body {
|
||||||
border-radius: .4em;
|
border-radius: .4em;
|
||||||
z-index: 9001;
|
z-index: 9001;
|
||||||
}
|
}
|
||||||
|
#tt.b {
|
||||||
|
padding: 0 2em;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: 0 .2em 1em #000;
|
||||||
|
}
|
||||||
#tt.show {
|
#tt.show {
|
||||||
padding: 1em;
|
padding: 1em 1.3em;
|
||||||
|
border-width: .4em 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-width: .2em 0;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
#tt.show.b {
|
||||||
|
padding: 1.5em 2em;
|
||||||
|
border-width: .5em 0;
|
||||||
|
}
|
||||||
#tt code {
|
#tt code {
|
||||||
background: #3c3c3c;
|
background: #3c3c3c;
|
||||||
padding: .2em .3em;
|
padding: .1em .3em;
|
||||||
border-top: 1px solid #777;
|
border-top: 1px solid #777;
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
line-height: 2em;
|
line-height: 1.7em;
|
||||||
|
}
|
||||||
|
#tt em {
|
||||||
|
color: #f6a;
|
||||||
}
|
}
|
||||||
#path,
|
#path,
|
||||||
#path * {
|
#path * {
|
||||||
|
@ -812,11 +824,13 @@ input.eq_gain {
|
||||||
border-bottom: 1px solid #555;
|
border-bottom: 1px solid #555;
|
||||||
}
|
}
|
||||||
#thumbs,
|
#thumbs,
|
||||||
#au_osd_cv {
|
#au_osd_cv,
|
||||||
|
#u2tdate {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs,
|
#griden.on+#thumbs,
|
||||||
#au_os_ctl.on+#au_osd_cv {
|
#au_os_ctl.on+#au_osd_cv,
|
||||||
|
#u2turbo.on+#u2tdate {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#ghead {
|
#ghead {
|
||||||
|
@ -921,13 +935,16 @@ html.light {
|
||||||
}
|
}
|
||||||
html.light #tt {
|
html.light #tt {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-color: #888;
|
border-color: #888 #000 #777 #000;
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
html.light #tt code {
|
html.light #tt code {
|
||||||
background: #060;
|
background: #060;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
html.light #tt em {
|
||||||
|
color: #d38;
|
||||||
|
}
|
||||||
html.light #ops,
|
html.light #ops,
|
||||||
html.light .opbox,
|
html.light .opbox,
|
||||||
html.light #srch_form {
|
html.light #srch_form {
|
||||||
|
|
|
@ -133,6 +133,13 @@ ebi('op_cfg').innerHTML = (
|
||||||
(have_zip ? (
|
(have_zip ? (
|
||||||
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
||||||
) : '') +
|
) : '') +
|
||||||
|
'<div>\n' +
|
||||||
|
' <h3>up2k switches</h3>\n' +
|
||||||
|
' <div>\n' +
|
||||||
|
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="the yolo button, you probably DO NOT want to enable this:$N$Nuse this if you were uploading a huge amount of files and had to restart for some reason, and want to continue the upload ASAP$N$Nthis replaces the hash-check with a simple <em>"does this have the same filesize on the server?"</em> so if the file contents are different it will NOT be uploaded$N$Nyou should turn this off when the upload is done, and then "upload" the same files again to let the client verify them">turbo</a>\n' +
|
||||||
|
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="has no effect unless the turbo button is enabled$N$Nreduces the yolo factor by a tiny amount; checks whether the file timestamps on the server matches yours$N$Nshould <em>theoretically</em> catch most unfinished/corrupted uploads, but is not a substitute for doing a verification pass with turbo disabled afterwards">date-chk</a>\n' +
|
||||||
|
' </div>\n' +
|
||||||
|
'</div>\n' +
|
||||||
'<div><h3>key notation</h3><div id="key_notation"></div></div>\n' +
|
'<div><h3>key notation</h3><div id="key_notation"></div></div>\n' +
|
||||||
'<div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>'
|
'<div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>'
|
||||||
);
|
);
|
||||||
|
|
|
@ -308,6 +308,12 @@ function U2pvis(act, btns) {
|
||||||
throw 42;
|
throw 42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log("oldcat %s %d, newcat %s %d, head=%d, tail=%d, file=%d, act.old=%s, act.new=%s, bz_act=%s",
|
||||||
|
// oldcat, this.ctr[oldcat],
|
||||||
|
// newcat, this.ctr[newcat],
|
||||||
|
// this.head, this.tail, nfile,
|
||||||
|
// this.is_act(oldcat), this.is_act(newcat), bz_act);
|
||||||
|
|
||||||
fo.in = newcat;
|
fo.in = newcat;
|
||||||
this.ctr[oldcat]--;
|
this.ctr[oldcat]--;
|
||||||
this.ctr[newcat]++;
|
this.ctr[newcat]++;
|
||||||
|
@ -319,7 +325,7 @@ function U2pvis(act, btns) {
|
||||||
this.addrow(nfile);
|
this.addrow(nfile);
|
||||||
}
|
}
|
||||||
else if (this.is_act(oldcat)) {
|
else if (this.is_act(oldcat)) {
|
||||||
while (this.head < Math.min(this.tab.length, this.tail) && (this.head == nfile || !this.is_act(this.tab[this.head].in)))
|
while (this.head < Math.min(this.tab.length, this.tail) && this.precard[this.tab[this.head].in])
|
||||||
this.head++;
|
this.head++;
|
||||||
|
|
||||||
if (!bz_act) {
|
if (!bz_act) {
|
||||||
|
@ -327,9 +333,10 @@ function U2pvis(act, btns) {
|
||||||
tr.parentNode.removeChild(tr);
|
tr.parentNode.removeChild(tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bz_act) {
|
else return;
|
||||||
|
|
||||||
|
if (bz_act)
|
||||||
this.bzw();
|
this.bzw();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.bzw = function () {
|
this.bzw = function () {
|
||||||
|
@ -343,7 +350,8 @@ function U2pvis(act, btns) {
|
||||||
|
|
||||||
while (this.head - first > this.wsz) {
|
while (this.head - first > this.wsz) {
|
||||||
var obj = ebi('f' + (first++));
|
var obj = ebi('f' + (first++));
|
||||||
obj.parentNode.removeChild(obj);
|
if (obj)
|
||||||
|
obj.parentNode.removeChild(obj);
|
||||||
}
|
}
|
||||||
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
||||||
var obj = ebi('f' + (++last));
|
var obj = ebi('f' + (++last));
|
||||||
|
@ -376,6 +384,8 @@ function U2pvis(act, btns) {
|
||||||
|
|
||||||
this.changecard = function (card) {
|
this.changecard = function (card) {
|
||||||
this.act = card;
|
this.act = card;
|
||||||
|
this.precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 };
|
||||||
|
this.postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
|
||||||
this.head = -1;
|
this.head = -1;
|
||||||
this.tail = -1;
|
this.tail = -1;
|
||||||
var html = [];
|
var html = [];
|
||||||
|
@ -390,16 +400,13 @@ function U2pvis(act, btns) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.head == -1) {
|
if (this.head == -1) {
|
||||||
var precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 },
|
|
||||||
postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
|
|
||||||
|
|
||||||
for (var a = 0; a < this.tab.length; a++) {
|
for (var a = 0; a < this.tab.length; a++) {
|
||||||
var rt = this.tab[a].in;
|
var rt = this.tab[a].in;
|
||||||
if (precard[rt]) {
|
if (this.precard[rt]) {
|
||||||
this.head = a + 1;
|
this.head = a + 1;
|
||||||
this.tail = a;
|
this.tail = a;
|
||||||
}
|
}
|
||||||
else if (postcard[rt]) {
|
else if (this.postcard[rt]) {
|
||||||
this.head = a;
|
this.head = a;
|
||||||
this.tail = a - 1;
|
this.tail = a - 1;
|
||||||
break;
|
break;
|
||||||
|
@ -452,6 +459,8 @@ function U2pvis(act, btns) {
|
||||||
that.changecard(newtab);
|
that.changecard(newtab);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.changecard(this.act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -548,17 +557,21 @@ function up2k_init(subtle) {
|
||||||
ask_up = bcfg_get('ask_up', true),
|
ask_up = bcfg_get('ask_up', true),
|
||||||
flag_en = bcfg_get('flag_en', false),
|
flag_en = bcfg_get('flag_en', false),
|
||||||
fsearch = bcfg_get('fsearch', false),
|
fsearch = bcfg_get('fsearch', false),
|
||||||
|
turbo = bcfg_get('u2turbo', false),
|
||||||
|
datechk = bcfg_get('u2tdate', true),
|
||||||
fdom_ctr = 0,
|
fdom_ctr = 0,
|
||||||
min_filebuf = 0;
|
min_filebuf = 0;
|
||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
"todo": {
|
"todo": {
|
||||||
|
"head": [],
|
||||||
"hash": [],
|
"hash": [],
|
||||||
"handshake": [],
|
"handshake": [],
|
||||||
"upload": []
|
"upload": []
|
||||||
},
|
},
|
||||||
"busy": {
|
"busy": {
|
||||||
|
"head": [],
|
||||||
"hash": [],
|
"hash": [],
|
||||||
"handshake": [],
|
"handshake": [],
|
||||||
"upload": []
|
"upload": []
|
||||||
|
@ -569,6 +582,15 @@ function up2k_init(subtle) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function push_t(arr, t) {
|
||||||
|
var sort = arr.length && arr[arr.length - 1].n > t.n;
|
||||||
|
arr.push(t);
|
||||||
|
if (sort)
|
||||||
|
arr.sort(function (a, b) {
|
||||||
|
return a.n < b.n ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var pvis = new U2pvis("bz", '#u2cards');
|
var pvis = new U2pvis("bz", '#u2cards');
|
||||||
|
|
||||||
var bobslice = null;
|
var bobslice = null;
|
||||||
|
@ -796,7 +818,10 @@ function up2k_init(subtle) {
|
||||||
], fobj.size, draw_each);
|
], fobj.size, draw_each);
|
||||||
|
|
||||||
st.files.push(entry);
|
st.files.push(entry);
|
||||||
st.todo.hash.push(entry);
|
if (turbo)
|
||||||
|
push_t(st.todo.head, entry);
|
||||||
|
else
|
||||||
|
push_t(st.todo.hash, entry);
|
||||||
}
|
}
|
||||||
if (!draw_each) {
|
if (!draw_each) {
|
||||||
pvis.drawcard("q");
|
pvis.drawcard("q");
|
||||||
|
@ -891,9 +916,11 @@ function up2k_init(subtle) {
|
||||||
while (window['vis_exh']) {
|
while (window['vis_exh']) {
|
||||||
var now = Date.now(),
|
var now = Date.now(),
|
||||||
is_busy = 0 !=
|
is_busy = 0 !=
|
||||||
|
st.todo.head.length +
|
||||||
st.todo.hash.length +
|
st.todo.hash.length +
|
||||||
st.todo.handshake.length +
|
st.todo.handshake.length +
|
||||||
st.todo.upload.length +
|
st.todo.upload.length +
|
||||||
|
st.busy.head.length +
|
||||||
st.busy.hash.length +
|
st.busy.hash.length +
|
||||||
st.busy.handshake.length +
|
st.busy.handshake.length +
|
||||||
st.busy.upload.length;
|
st.busy.upload.length;
|
||||||
|
@ -943,6 +970,12 @@ function up2k_init(subtle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (st.todo.head.length &&
|
||||||
|
st.busy.head.length < parallel_uploads) {
|
||||||
|
exec_head();
|
||||||
|
mou_ikkai = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (handshakes_permitted() &&
|
if (handshakes_permitted() &&
|
||||||
st.todo.handshake.length) {
|
st.todo.handshake.length) {
|
||||||
exec_handshake();
|
exec_handshake();
|
||||||
|
@ -1160,6 +1193,53 @@ function up2k_init(subtle) {
|
||||||
segm_next();
|
segm_next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////
|
||||||
|
////
|
||||||
|
/// head
|
||||||
|
//
|
||||||
|
|
||||||
|
function exec_head() {
|
||||||
|
var t = st.todo.head.shift();
|
||||||
|
st.busy.head.push(t);
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('head onerror, retrying', t);
|
||||||
|
apop(st.busy.head, t);
|
||||||
|
st.todo.head.unshift(t);
|
||||||
|
tasker();
|
||||||
|
};
|
||||||
|
function orz(e) {
|
||||||
|
var ok = false;
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
var srv_sz = xhr.getResponseHeader('Content-Length'),
|
||||||
|
srv_ts = xhr.getResponseHeader('Last-Modified');
|
||||||
|
|
||||||
|
ok = t.size == srv_sz;
|
||||||
|
if (ok && datechk) {
|
||||||
|
srv_ts = new Date(srv_ts) / 1000;
|
||||||
|
ok = Math.abs(srv_ts - t.lmod) < 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apop(st.busy.head, t);
|
||||||
|
if (!ok)
|
||||||
|
return push_t(st.todo.hash, t);
|
||||||
|
|
||||||
|
t.done = true;
|
||||||
|
st.bytes.hashed += t.size;
|
||||||
|
st.bytes.uploaded += t.size;
|
||||||
|
pvis.seth(t.n, 1, 'YOLO');
|
||||||
|
pvis.seth(t.n, 2, "turbo'd");
|
||||||
|
pvis.move(t.n, 'ok');
|
||||||
|
};
|
||||||
|
xhr.onload = function (e) {
|
||||||
|
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('HEAD', t.purl + t.name, true);
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
/////
|
/////
|
||||||
////
|
////
|
||||||
/// handshake
|
/// handshake
|
||||||
|
@ -1268,14 +1348,24 @@ function up2k_init(subtle) {
|
||||||
msg = '🎷🐛';
|
msg = '🎷🐛';
|
||||||
|
|
||||||
if (t.postlist.length) {
|
if (t.postlist.length) {
|
||||||
|
var arr = st.todo.upload,
|
||||||
|
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
||||||
|
|
||||||
for (var a = 0; a < t.postlist.length; a++)
|
for (var a = 0; a < t.postlist.length; a++)
|
||||||
st.todo.upload.push({
|
arr.push({
|
||||||
'nfile': t.n,
|
'nfile': t.n,
|
||||||
'npart': t.postlist[a]
|
'npart': t.postlist[a]
|
||||||
});
|
});
|
||||||
|
|
||||||
msg = 'uploading';
|
msg = 'uploading';
|
||||||
done = false;
|
done = false;
|
||||||
|
|
||||||
|
if (sort)
|
||||||
|
arr.sort(function (a, b) {
|
||||||
|
return a.nfile < b.nfile ? -1 :
|
||||||
|
/* */ a.nfile > b.nfile ? 1 :
|
||||||
|
a.npart < b.npart ? -1 : 1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
pvis.seth(t.n, 1, msg);
|
pvis.seth(t.n, 1, msg);
|
||||||
apop(st.busy.handshake, t);
|
apop(st.busy.handshake, t);
|
||||||
|
@ -1527,6 +1617,30 @@ function up2k_init(subtle) {
|
||||||
set_fsearch(!fsearch);
|
set_fsearch(!fsearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tgl_turbo() {
|
||||||
|
turbo = !turbo;
|
||||||
|
bcfg_set('u2turbo', turbo);
|
||||||
|
draw_turbo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tgl_datechk() {
|
||||||
|
datechk = !datechk;
|
||||||
|
bcfg_set('u2tdate', datechk);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_turbo() {
|
||||||
|
var msg = '<p class="warn">WARNING: turbo enabled, <span> client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
|
||||||
|
html = ebi('u2foot').innerHTML;
|
||||||
|
|
||||||
|
if (turbo && html.indexOf(msg) === -1)
|
||||||
|
html += msg;
|
||||||
|
else if (!turbo)
|
||||||
|
html = html.replace(msg, '');
|
||||||
|
|
||||||
|
ebi('u2foot').innerHTML = html;
|
||||||
|
}
|
||||||
|
draw_turbo();
|
||||||
|
|
||||||
function set_fsearch(new_state) {
|
function set_fsearch(new_state) {
|
||||||
var fixed = false;
|
var fixed = false;
|
||||||
|
|
||||||
|
@ -1608,6 +1722,8 @@ function up2k_init(subtle) {
|
||||||
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
||||||
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
||||||
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
||||||
|
ebi('u2turbo').addEventListener('click', tgl_turbo, false);
|
||||||
|
ebi('u2tdate').addEventListener('click', tgl_datechk, false);
|
||||||
var o = ebi('fsearch');
|
var o = ebi('fsearch');
|
||||||
if (o)
|
if (o)
|
||||||
o.addEventListener('click', tgl_fsearch, false);
|
o.addEventListener('click', tgl_fsearch, false);
|
||||||
|
|
|
@ -215,9 +215,31 @@
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
#u2foot .warn {
|
||||||
|
font-size: 1.3em;
|
||||||
|
padding: .5em .8em;
|
||||||
|
margin: 1em -.6em;
|
||||||
|
color: #f74;
|
||||||
|
background: #322;
|
||||||
|
border: 1px solid #633;
|
||||||
|
border-width: .1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#u2foot .warn span {
|
||||||
|
color: #f86;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn {
|
||||||
|
color: #b00;
|
||||||
|
background: #fca;
|
||||||
|
border-color: #f70;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn span {
|
||||||
|
color: #930;
|
||||||
|
}
|
||||||
#u2foot span {
|
#u2foot span {
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: .9em;
|
font-size: .9em;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
#u2footfoot {
|
#u2footfoot {
|
||||||
margin-bottom: -1em;
|
margin-bottom: -1em;
|
||||||
|
|
|
@ -514,8 +514,10 @@ var tt = (function () {
|
||||||
|
|
||||||
var pos = this.getBoundingClientRect(),
|
var pos = this.getBoundingClientRect(),
|
||||||
left = pos.left < window.innerWidth / 2,
|
left = pos.left < window.innerWidth / 2,
|
||||||
top = pos.top < window.innerHeight / 2;
|
top = pos.top < window.innerHeight / 2,
|
||||||
|
big = this.className.indexOf(' ttb') !== -1;
|
||||||
|
|
||||||
|
clmod(r.tt, 'b', big);
|
||||||
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
||||||
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
||||||
r.tt.style.left = left ? pos.left + 'px' : 'auto';
|
r.tt.style.left = left ? pos.left + 'px' : 'auto';
|
||||||
|
|
Loading…
Reference in a new issue