From eacbdd2df0d132fa3d6af1b1b3c84201b97a751c Mon Sep 17 00:00:00 2001 From: cutls Date: Sat, 10 Mar 2018 23:22:59 +0900 Subject: [PATCH] TheDesk Airi (ver.1) --- LATEST.md | 13 +-- README.md | 7 +- app/acct.html | 5 +- app/css/master.css | 31 +++++- app/css/tl.css | 14 ++- app/img/osushi_qr.png | Bin 0 -> 434 bytes app/index.html | 30 +++-- app/js/login/login.js | 15 ++- app/js/login/manager.js | 9 +- app/js/platform/end.js | 16 ++- app/js/platform/nano.js | 134 ++++++++++++++++++++++ app/js/platform/screenshot.js | 56 ++++++++++ app/js/post/status.js | 59 ++++++++++ app/js/post/suggest.js | 1 + app/js/tl/card.js | 5 +- app/js/tl/datails.js | 16 ++- app/js/tl/mix.js | 59 ++++++---- app/js/tl/notification.js | 13 +-- app/js/tl/parse.js | 203 +++++++++++++++++++++++++++++++--- app/js/tl/tag.js | 60 ++++++++++ app/js/tl/tl.js | 30 +++-- app/js/ui/img.js | 2 +- app/js/ui/layout.js | 3 +- app/js/ui/pip.js | 32 ++++++ app/js/ui/settings.js | 79 ++++++++++++- app/js/userdata/showOnTL.js | 5 + app/main.js | 52 +++++++-- app/nano.html | 98 ++++++++++++++++ app/package.json | 2 +- app/screenshot.html | 29 +++++ app/setting.html | 31 +++++- ver.json | 2 +- 32 files changed, 1004 insertions(+), 107 deletions(-) create mode 100644 app/img/osushi_qr.png create mode 100644 app/js/platform/nano.js create mode 100644 app/js/platform/screenshot.js create mode 100644 app/js/tl/tag.js create mode 100644 app/js/ui/pip.js create mode 100644 app/nano.html create mode 100644 app/screenshot.html diff --git a/LATEST.md b/LATEST.md index 4f45e909..634bf7d0 100644 --- a/LATEST.md +++ b/LATEST.md @@ -1,13 +1,12 @@ -## For Markdown-supporting Instances +## For Astarte(kirishima.cloud), My Primary Instance -[TheDesk](https://thedesk.top) :thedesk: Riina (ver.8) -・バグ修正 -・プラットフォーム追加(サポート外となります) +[TheDesk](https://thedesk.top) :thedesk: Airi (ver.1) +・史上最大の機能追加数です。ここに書ききれないので[HP](https://thedesk.top)へ。 :github: [cutls/TheDesk](https://github.com/cutls/TheDesk) #Desk #DeskUpdate ## For Vanilla Instances - WindowsクライアントTheDesk Riina (ver.8)リリース -・バグ修正 -・プラットフォーム追加(サポート外となります) + Windows/LinuxクライアントTheDesk Airi (ver.1)リリース +・史上最大の機能追加数です。 +TheDeskはマルチカラム,マルチアカウントはもちろんのこと,なにかとマストドンライフをシンプルに効率化するクライアントです。 https://thedesk.top \ No newline at end of file diff --git a/README.md b/README.md index bf794701..ce8af0be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TheDesk -Mastodon client for Windows -オープンソースSNSマストドンのWindowsクライアント +Mastodon client for Windows/Linux(Linux ver. is not supported by developer.) +オープンソースSNSマストドンのWindows/Linuxクライアント Download:[TheDesk](https://thedesk.top) Latest Info(Markdown Toot)/最新情報(マークダウン形式のトゥート): [LATEST.md](https://github.com/cutls/TheDesk/blob/master/LATEST.md) @@ -33,10 +33,11 @@ Japanese ## Requirement/環境 -- Windows 64bit(to launch TheDesk/実行に必要) +- Windows (to launch TheDesk/実行に必要) / Linux (x64/ia32/armv7l) - Electron beta.1.8.2-beta4 - electron-dl - electron-about-window +- Jimp(Node.js) - Ability to read unformated files! ### Why do we use Electron beta version?/Beta版の利用について diff --git a/app/acct.html b/app/acct.html index af640611..cbf4ee30 100644 --- a/app/acct.html +++ b/app/acct.html @@ -30,8 +30,9 @@

- -
+Linuxでご使用の方はチェックを入れて下さい。
+ +
Supports
diff --git a/app/css/master.css b/app/css/master.css index 19073c1e..52e4e939 100644 --- a/app/css/master.css +++ b/app/css/master.css @@ -37,6 +37,9 @@ option { position: fixed; z-index: 9; } +#imagemodal, #videomodal, #tootmodal { + background-color: white; +} #imagemodal .modal-content { overflow: hidden; } @@ -134,7 +137,28 @@ blockquote:before, .quote:before { font-size: 2rem; } .radio{ - font-family:'Yanone Kaffeesatz' + font-family:'Baloo Bhai' +} +#pip{ + z-index:1001; + width:418px; + background-color: white; + position:absolute; +} +.pip-bottom{ + bottom:10px; +} +.pip-left{ + left:10px; +} +.pip-top{ + top:10px; +} +.pip-right{ + right:10px; +} +#pip-content .material-icons{ + display:none; } @media only screen and (min-width: 993px){ @@ -173,7 +197,7 @@ blockquote:before, .quote:before { justify-content: center; align-items: center; } -.blacktheme #imagemodal, #videomodal, #tootmodal { +.blacktheme #imagemodal,.blacktheme #videomodal,.blacktheme #tootmodal { background-color: black; } .blacktheme .collapsible-header { @@ -182,6 +206,9 @@ blockquote:before, .quote:before { .blacktheme .tabs { background-color: #212121; } +.blacktheme #pip { + background-color: #212121; +} /*スクロールバー*/ ::-webkit-scrollbar { diff --git a/app/css/tl.css b/app/css/tl.css index 5f1f6f12..6b0ca08a 100644 --- a/app/css/tl.css +++ b/app/css/tl.css @@ -28,6 +28,9 @@ iframe { flex: 1; border: thin solid gray; } +.box .pin{ + display:none; +} .user{ cursor:text; font-size:1.2rem; @@ -118,7 +121,6 @@ font-size:1rem; .toot-img { object-fit: cover; width: 100%; - height: 200px; } .toot img:not(.emoji-img) { max-width: 100%; @@ -148,6 +150,9 @@ p { .shared { background-color: #cfd8dc; } +.emphasized { + background-color: #81c784; +} .udg { cursor: pointer; } @@ -165,6 +170,10 @@ p { grid-template-rows: 30px 30px; grid-template-areas: 'notice notice_name notice_name notice_name notice_name notice_name' 'notice a1 a2 a3 a4 a5' 'notf-box notf-box notf-box notf-box notf-box notf-box'; } +.emp{ + font-weight: bold; + text-decoration: underline; +} .area-notice { grid-area: notice; @@ -268,4 +277,7 @@ p { } .blacktheme .notice-box { background-color: #333333; +} +.blacktheme .emphasized { + background-color: #4e342e; } \ No newline at end of file diff --git a/app/img/osushi_qr.png b/app/img/osushi_qr.png new file mode 100644 index 0000000000000000000000000000000000000000..97ea525b0bdd3cf2e85cfbd16a101faf57d2a5ae GIT binary patch literal 434 zcmV;j0ZsmiP)K~zYI-PFB}gfI{V;6V&j+DQ(imRKIF>U--q(DLxP-M<3*8`@*Es)2@g@VDR==I5C!9wP&jtT_t~_egxm zpyRxp?R>p{tt`5)t9Gk9+WzG*T=RU$6HTx|osN;O#(el>xO;*n3aoPw8%>z|_O{58*9DP;l7BCGP!w}(4Qo!p)6761PfXQB3=`eHG zMBM_uA6r7NvCS+6G&JNV^Tzuq1}s}W+FAxN%mI_V_f$q9V5Sqb;YML)@SR7l8f{gF c3_j=j0u#bb{0M|0ng9R*07*qoM6N<$g5IaT`2YX_ literal 0 HcmV?d00001 diff --git a/app/index.html b/app/index.html index da3cc2dc..acb575fd 100644 --- a/app/index.html +++ b/app/index.html @@ -10,7 +10,7 @@ - + @@ -24,7 +24,7 @@ + + diff --git a/app/js/login/login.js b/app/js/login/login.js index 3006dee7..1dc4f862 100644 --- a/app/js/login/login.js +++ b/app/js/login/login.js @@ -32,7 +32,7 @@ function ck() { ck(); //ログインポップアップ function login(url) { - if($('#linux:checked').val()=="linux"){ + if($('#linux:checked').val()=="on"){ var red = "urn:ietf:wg:oauth:2.0:oob" }else{ var red = 'thedesk://login'; @@ -58,7 +58,7 @@ function login(url) { }).then(function(json) { var auth = "https://" + url + "/oauth/authorize?client_id=" + json[ "client_id"] + "&client_secret=" + json["client_secret"] + - "&response_type=code&redirect_uri=thedesk://login&scope=read+write+follow"; + "&response_type=code&redirect_uri="+red+"&scope=read+write+follow"; localStorage.setItem("domain_" + acct_id, url); localStorage.setItem("client_id", json["client_id"]); localStorage.setItem("client_secret", json["client_secret"]); @@ -69,9 +69,14 @@ function login(url) { } = require('electron'); shell.openExternal(auth); - var electron = require("electron"); - var ipc = electron.ipcRenderer; - ipc.send('quit', 'go'); + + if($('#linux:checked').val()=="on"){ + }else{ + var electron = require("electron"); + var ipc = electron.ipcRenderer; + ipc.send('quit', 'go'); + } + }); } diff --git a/app/js/login/manager.js b/app/js/login/manager.js index cf978630..2805188f 100644 --- a/app/js/login/manager.js +++ b/app/js/login/manager.js @@ -99,6 +99,9 @@ function multiDel(target) { obj.splice(target, 1); var json = JSON.stringify(obj); localStorage.setItem("multi", json); + Object.keys(obj).forEach(function(key) { + refresh(key); + }); load(); } } @@ -145,7 +148,7 @@ function login(url) { if (ng) { return; } - if($('#linux:checked').val()=="linux"){ + if($('#linux:checked').val()=="on"){ var red = "urn:ietf:wg:oauth:2.0:oob" }else{ var red = 'thedesk://manager'; @@ -172,7 +175,7 @@ function login(url) { console.log(json); var auth = "https://" + url + "/oauth/authorize?client_id=" + json[ "client_id"] + "&client_secret=" + json["client_secret"] + - "&response_type=code&scope=read+write+follow&redirect_uri="+json.redirect_uri; + "&response_type=code&scope=read+write+follow&redirect_uri="+red; localStorage.setItem("domain_tmp", url); localStorage.setItem("client_id", json["client_id"]); localStorage.setItem("client_secret", json["client_secret"]); @@ -185,7 +188,7 @@ function login(url) { shell.openExternal(auth); var electron = require("electron"); var ipc = electron.ipcRenderer; - if($('#linux:checked').val()=="linux"){ + if($('#linux:checked').val()=="on"){ }else{ ipc.send('quit', 'go'); } diff --git a/app/js/platform/end.js b/app/js/platform/end.js index 2127f171..276a9476 100644 --- a/app/js/platform/end.js +++ b/app/js/platform/end.js @@ -72,6 +72,13 @@ return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }); }; + function escapeHTML(str) { + return str.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } //コピー function execCopy(string){ var temp = $("#copy"); @@ -79,4 +86,11 @@ temp.select(); var result = document.execCommand('copy'); return result; - } \ No newline at end of file + } + //Nano + //Nano +function nano(){ + var electron = require("electron"); + var ipc = electron.ipcRenderer; + ipc.send('nano', ""); +} diff --git a/app/js/platform/nano.js b/app/js/platform/nano.js new file mode 100644 index 00000000..b67e5573 --- /dev/null +++ b/app/js/platform/nano.js @@ -0,0 +1,134 @@ + +//TL取得 +function tl(data) { + var tlid=0; + var acct_id = $("#post-acct-sel").val(); + var type = $("#type-sel").val(); + var domain = localStorage.getItem("domain_" + acct_id); + //タグの場合はカラム追加して描画 + if (!type) { + //デフォルト + var type = "local"; + } + var at = localStorage.getItem(domain + "_at"); + $("#notice_nano").text(cap(type, data) + " TL(" + localStorage.getItem( + "user_" + acct_id) + "@" + domain + ")"); + var start = "https://" + domain + "/api/v1/timelines/" + com(type, data); + console.log(start); + fetch(start, { + method: 'GET', + headers: { + 'content-type': 'application/json', + 'Authorization': 'Bearer ' + at + }, + }).then(function(response) { + return response.json(); + }).catch(function(error) { + console.error(error); + }).then(function(json) { + var templete = parse([json[0]], '', acct_id, tlid); + $("#timeline_nano").html(templete); + jQuery("time.timeago").timeago(); + reload(type, '', acct_id, data); + }); +} + +//Streaming接続 +var websocket=[]; +function reload(type, cc, acct_id, data) { + var tlid=0; + var domain = localStorage.getItem("domain_" + acct_id); + var at = localStorage.getItem(domain + "_at"); + if (type == "home") { + var start = "wss://" + domain + + "/api/v1/streaming/?stream=user&access_token=" + at; + } else if (type == "pub") { + var start = "wss://" + domain + + "/api/v1/streaming/?stream=public&access_token=" + at; + } else if (type == "local") { + var start = "wss://" + domain + + "/api/v1/streaming/?stream=public:local&access_token=" + at; + } else if (type == "tag") { + var start = "wss://" + domain + + "/api/v1/streaming/?stream=hashtag&tag=" + data +"&access_token=" + at; + } + console.log(start); + var wsid = websocket.length; + websocket[wsid] = new WebSocket(start); + websocket[wsid].onopen = function(mess) { + console.log(tlid + ":Connect Streaming API:" + type); + console.log(mess); + $("#notice_icon_" + tlid).removeClass("red-text"); + } + websocket[wsid].onmessage = function(mess) { + console.log(tlid + ":Receive Streaming API:"); + console.log(websocket[wsid]); + var typeA = JSON.parse(mess.data).event; + if (typeA == "delete") { + var obj = JSON.parse(mess.data).payload; + $("[toot-id=" + JSON.parse(mess.data).payload + "]").hide(); + $("[toot-id=" + JSON.parse(mess.data).payload + "]").remove(); + } else if (typeA == "update") { + var obj = JSON.parse(JSON.parse(mess.data).payload); + console.log(obj); + var templete = parse([obj], '', acct_id, tlid); + $("#timeline_nano").html(templete); + } + websocket[wsid].onclose = function(mess) { + console.log("Close Streaming API:" + type); + } + } + websocket[wsid].onerror = function(error) { + console.error('WebSocket Error ' + error); + }; +} +//TLのタイトル +function cap(type, data) { + if (type == "home") { + return "Home" + } else if (type == "local") { + return "Local" + } else if (type == "pub") { + return "Public" + } else if (type == "tag") { + return "#" + data + } else if (type == "list") { + return "List(id:" + data + ")" + } else if (type == "notf") { + return "Notification" + } +} + +//TLのURL +function com(type, data) { + if (type == "home") { + return "home?" + } else if (type == "local") { + return "public?local=true&" + } else if (type == "pub") { + return "public?" + } else if (type == "tag") { + return "tag/" + data + "?" + } + if (type == "list") { + return "list/" + data + "?" + } +} + +//TLのアイコン +function icon(type) { + if (type == "home") { + return "home" + } else if (type == "local") { + return "people_outline" + } else if (type == "pub") { + return "language" + } else if (type == "tag") { + return "search" + } + if (type == "list") { + return "subject" + } +} +function todo(){} +function todc(){} \ No newline at end of file diff --git a/app/js/platform/screenshot.js b/app/js/platform/screenshot.js new file mode 100644 index 00000000..b58e15a8 --- /dev/null +++ b/app/js/platform/screenshot.js @@ -0,0 +1,56 @@ +var electron = require("electron"); +const fs = require("fs"); +const os = require('os') +const shell = electron.shell; +const path = require('path') +function shot(){ + //screenshotMsg.textContent = 'Gathering screens...' + $(window).height + let options = { + types: ['screen'], + thumbnailSize: { + width: window.parent.screen.width, + height: window.parent.screen.height + } + } + const desktopCapturer = electron.desktopCapturer; + desktopCapturer.getSources(options, function(error, sources) { + if (error) return console.log(error) + + sources.forEach(function(source) { + if(location.search){ + var m = location.search.match(/\?id=([a-zA-Z-0-9]+)/); + var title=m[1]; + }else{ + var title="screenshot"; + } + if (source.name === 'Screen 1' || source.name === 'TheDesk') { + var durl=source.thumbnail.toDataURL(); + var b64 = durl.match( + /data:image\/png;base64,(.+)/ + ); + const screenshotPath = path.join(os.tmpdir(), 'screenshot.png'); + const savePath = path.join(os.tmpdir(), 'screenshot.png'); + var ipc = electron.ipcRenderer; + var h = $(window).height()-150; + var w = $(window).width(); + ipc.send('shot', ['file://' + screenshotPath,w,h,b64[1],title]); + if($(".img-parsed").length>0){ + for(i=0;i<$(".img-parsed").length;i++){ + var url=$(".img-parsed").eq(i).attr("data-url"); + ipc.send('shot-img-dl', [url,title+"_img"+i+".png"]); + } + } + window.close(); + return; + const message = `Saved screenshot to: ${screenshotPath}` + //screenshotMsg.textContent = message + } + }) + }) + } + $(window).load(function(){ + setTimeout(function(){ + shot(); + },2000); + }); \ No newline at end of file diff --git a/app/js/post/status.js b/app/js/post/status.js index a2dc9999..e876926a 100644 --- a/app/js/post/status.js +++ b/app/js/post/status.js @@ -235,6 +235,39 @@ function del(id, acct_id) { //$("#pub_"+id).hide(); }); } +//ピン留め +function pin(id, acct_id) { + if ($("#pub_" + id).hasClass("pined")) { + var flag = "unpin"; + } else { + var flag = "pin"; + } + var domain = localStorage.getItem("domain_" + acct_id); + var at = localStorage.getItem(domain + "_at"); + var start = "https://" + domain + "/api/v1/statuses/" + id + "/" + flag; + fetch(start, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': 'Bearer ' + at + }, + body: JSON.stringify({}) + }).then(function(response) { + return response.json(); + }).catch(function(error) { + todo(error); + console.error(error); + }).then(function(json) { + console.log(json); + if ($("[toot-id=" + id +"]").hasClass("pined")) { + $("[toot-id=" + id +"]").removeClass("pined"); + $(".pin_" + id).removeClass("blue-text"); + } else { + $("[toot-id=" + id +"]").addClass("pined"); + $(".pin_" + id).addClass("blue-text"); + } + }); +} //フォロリク function request(id, flag, acct_id) { @@ -289,6 +322,32 @@ function addDomainblock() { var domain = $("#domainblock").val(); domainblock(domain, 'POST'); } +//ユーザー強調 +function empUser(){ + var usr = localStorage.getItem("user_emp"); + var obj = JSON.parse(usr); + var id=$("#his-acct").attr("fullname"); + console.log(id); + if(!obj){ + var obj=[]; + obj.push(id); + Materialize.toast(id+"を強調します。設定を適用するにはF5を押して下さい。", 4000); + }else{ + var can; + Object.keys(obj).forEach(function(key) { + var usT = obj[key]; + if(usT!=id && !can){ + can=false; + }else{ + can=true; + obj.splice(key, 1); + Materialize.toast(id+"の強調を解除しました。設定を適用するにはF5を押して下さい。", 4000); + } + }); + } + var json = JSON.stringify(obj); + localStorage.setItem("user_emp", json); +} //URLコピー function tootUriCopy(url){ execCopy(url); diff --git a/app/js/post/suggest.js b/app/js/post/suggest.js index 9207efb4..4e1016bd 100644 --- a/app/js/post/suggest.js +++ b/app/js/post/suggest.js @@ -68,4 +68,5 @@ input.addEventListener("focus", function() { input.addEventListener("blur", function() { window.clearInterval(timer); + favTag(); }, false); diff --git a/app/js/tl/card.js b/app/js/tl/card.js index 831a9328..146425d9 100644 --- a/app/js/tl/card.js +++ b/app/js/tl/card.js @@ -39,8 +39,7 @@ function additional(acct_id, tlid) { json.description + ""); } if (json.html) { - $("[toot-id=" + id + "] .additional").html(json.html); - + $("[toot-id=" + id + "] .additional").html(json.html+'picture_in_picture_alt'); } if (json.title) { $("[toot-id=" + id + "] a:not(.parsed)").addClass("parsed"); @@ -100,7 +99,7 @@ function additionalIndv(tlid, acct_id, id) { json.description + ""); } if (json.html) { - $("[toot-id=" + id + "] .additional").html(json.html); + $("[toot-id=" + id + "] .additional").html(json.html+'picture_in_picture_alt'); } if (json.title) { diff --git a/app/js/tl/datails.js b/app/js/tl/datails.js index 8caf51ec..b0115f60 100644 --- a/app/js/tl/datails.js +++ b/app/js/tl/datails.js @@ -1,7 +1,7 @@ //トゥートの詳細 -function details(id, acct_id) { +function details(id, acct_id, tlid) { $(".toot-reset").html("データなし"); - var html = $("#pub_" + id).html(); + var html = $("#timeline_"+tlid+" #pub_" + id).html(); $("#toot-this").html(html); $('#tootmodal').modal('open'); var domain = localStorage.getItem("domain_" + acct_id); @@ -22,6 +22,7 @@ function details(id, acct_id) { $("#toot-this .fav_ct").text(json.favourites_count); $("#toot-this .rt_ct").text(json.reblogs_count); $("#tootmodal").attr("data-url",json.url); + $("#tootmodal").attr("data-id",json.id); if (json.in_reply_to_id) { replyTL(json.in_reply_to_id, acct_id); } @@ -167,4 +168,15 @@ function cbCopy(mode){ } } +} +//魚拓 +function shot(){ + var id=$("#tootmodal").attr("data-id"); + var w=$("#toot-this").width(); + var h=$("#toot-this").height()+150; + var text=$("#toot-this").html(); + localStorage.setItem("sc-text",text) + var electron = require("electron"); + var ipc = electron.ipcRenderer; + ipc.send('screen', [w,h,id]); } \ No newline at end of file diff --git a/app/js/tl/mix.js b/app/js/tl/mix.js index fb0b3e72..e4481544 100644 --- a/app/js/tl/mix.js +++ b/app/js/tl/mix.js @@ -27,6 +27,7 @@ function mixtl(acct_id, tlid) { jQuery("time.timeago").timeago(); $(window).scrollTop(0); var locals = templete[1]; + var times = templete[2]; todo("Integrated TL Loading...(Home)"); //Home var start = "https://" + domain + "/api/v1/timelines/home"; @@ -43,32 +44,46 @@ function mixtl(acct_id, tlid) { console.error(error); }).then(function(obj) { //ホームのオブジェクトをUnix時間で走査 + if (!$("[toot-id=" + obj[0].id + "]").length) { + $("#timeline_" + tlid + " .cvo").first().before(parse([obj[0]], 'home', + acct_id)); + //delete obj[0]; + } + //Localが遅すぎてHomeの全てより過去の場合 + var unixL=date(json[0].created_at,"unix"); + var unixH=date(obj[obj.length-1].created_at,"unix"); + //console.log(unixH+"vs"+unixL) + if(unixH < unixL){ Object.keys(obj).forEach(function(key) { var skey = obj.length - key - 1; var toot = obj[skey]; var id = toot.id; + if ($("#timeline_" + tlid + " [toot-id=" + toot.id + "]").length < 1) { + //console.log(toot.id); var tarunix = date(toot.created_at, 'unix'); var beforekey2; var key2; + //console.log(locals) //ホームのオブジェクトに対してLocalのオブジェクトを時間走査 - Object.keys(locals).forEach(function(key2) { - if (!$("#timeline_" + tlid + " [toot-id=" + obj[0].id + "]").length && - key2 < date(obj[0].created_at, 'unix')) { - $("#timeline_" + tlid + " .cvo").first().before(parse([obj[0]], - 'home', acct_id, tlid)+'
'); - } - if (!$("#timeline_" + tlid + " [toot-id=" + toot.id + "]").length) { - if (key2 > tarunix) { - var local = locals[key2]; - console.log("#timeline_" + tlid + " [toot-id=" + local + "]"); - $("#timeline_" + tlid + " [toot-id=" + local + "]").after('
'+parse( - [toot], 'home', acct_id, tlid)); - tarunix = 0; + Object.keys(times).forEach(function(key2) { + if (times[key2] < tarunix) { + var local = json[key2].id; + //console.log($.strip_tags(toot.content)); + html = parse( + [toot], 'home', acct_id, tlid); + $("#timeline_" + tlid + " [toot-id=" + local + "]").before(html); + //console.log("#timeline_" + tlid + " [toot-id=" + local + "]"); + tarunix = 0; } - } }); + } }); + }else{ + html = parse( + obj, 'home', acct_id, tlid); + $("#timeline_" + tlid).html(html); + } todc(); mixre(acct_id, tlid); additional(acct_id, tlid); @@ -94,9 +109,11 @@ function mixre(acct_id, tlid) { websocketLocal[wslid] = new WebSocket(startLocal); websocketHome[wshid].onopen = function(mess) { console.log("Connect Streaming API(Home)"); + $("#notice_icon_" + tlid).removeClass("red-text"); } websocketLocal[wslid].onopen = function(mess) { console.log("Connect Streaming API(Local)"); + $("#notice_icon_" + tlid).removeClass("red-text"); } websocketLocal[wslid].onmessage = function(mess) { console.log("Receive Streaming API:"); @@ -164,6 +181,7 @@ function mixmore(tlid) { var domain = localStorage.getItem("domain_" + acct_id); var at = localStorage.getItem(domain + "_at"); var sid = $("#timeline_" + tlid + " .cvo").last().attr("toot-id"); + var len = $("#timeline_" + tlid + " .cvo").length var start = "https://" + domain + "/api/v1/timelines/public?local=true&max_id=" + sid; console.log(start); @@ -197,9 +215,10 @@ function mixmore(tlid) { todo(error); console.error(error); }).then(function(obj) { - if (!$("[toot-id=" + obj[0].id + "]").length) { - $("#timeline_" + tlid + " .cvo").first().before(parse([obj[0]], 'home', - acct_id)); + if ($("[toot-id=" + obj[0].id + "]").length < 1) { + $("#timeline_" + tlid + " .cvo").eq(len).before(parse([obj[0]], 'home', + acct_id)+'
'); + //delete obj[0]; } Object.keys(obj).forEach(function(key) { var skey = obj.length - key - 1; @@ -209,12 +228,12 @@ function mixmore(tlid) { var beforekey2; var key2; Object.keys(locals).forEach(function(key2) { - if (!$("[toot-id=" + toot.id + "]").length) { + if ($("[toot-id=" + toot.id + "]").length <1) { if (key2 > tarunix) { var local = locals[key2]; - $("[toot-id=" + local + "]").after(parse([toot], 'home', + $("#timeline_" + tlid + " [toot-id=" + local + "]").after(parse([toot], 'home', acct_id, tlid)); - tarunix = 0; + tarunix = 2147483647; } } diff --git a/app/js/tl/notification.js b/app/js/tl/notification.js index cffcfe60..61acde76 100644 --- a/app/js/tl/notification.js +++ b/app/js/tl/notification.js @@ -60,17 +60,16 @@ function notf(acct_id, tlid, sys) { if (!popup) { popup = 0; } - if(json.type!="follow"){ - templete = templete+parse([json], '', acct_id, tlid, popup); + var templete=""; + if(obj.type!="follow"){ + templete = templete+parse([obj], '', acct_id, tlid, popup); }else{ - templete = templete+userparse([json], '', acct_id, tlid, popup); + templete = templete+userparse([obj], '', acct_id, tlid, popup); } - var notices = templete[1]; - console.log(templete); if (sys == "direct") { - $("#timeline_" + tlid).prepend(templete[0]); + $("#timeline_" + tlid).prepend(templete); } else { - $("#notifications_" + tlid).prepend(templete[0]); + $("#notifications_" + tlid).prepend(templete); } jQuery("time.timeago").timeago(); } else if (type == "delete") { diff --git a/app/js/tl/parse.js b/app/js/tl/parse.js index da6f0e1b..2b4b7c45 100644 --- a/app/js/tl/parse.js +++ b/app/js/tl/parse.js @@ -6,6 +6,32 @@ function parse(obj, mix, acct_id, tlid, popup) { var sent = localStorage.getItem("sentence"); var ltr = localStorage.getItem("letters"); var gif = localStorage.getItem("gif"); + var imh = localStorage.getItem("img-height"); + //クライアント強調 + var emp = localStorage.getItem("client_emp"); + if(emp){ + var emp = JSON.parse(emp); + } + //クライアントミュート + var mute = localStorage.getItem("client_mute"); + if(mute){ + var mute = JSON.parse(mute); + } + //ユーザー強調 + var useremp = localStorage.getItem("user_emp"); + if(useremp){ + var useremp = JSON.parse(useremp); + } + //ワード強調 + var wordemp = localStorage.getItem("word_emp"); + if(wordemp){ + var wordemp = JSON.parse(wordemp); + } + //ワードミュート + var wordmute = localStorage.getItem("word_mute"); + if(wordmute){ + var wordmute = JSON.parse(wordmute); + } if (!sent) { var sent = 500; } @@ -29,7 +55,27 @@ function parse(obj, mix, acct_id, tlid, popup) { if (!gif) { var gif = "yes"; } + if (!imh) { + var imh = "200"; + } + if(!emp){ + var emp=[]; + } + if(!mute){ + var mute=[]; + } + if(!useremp){ + var useremp=[]; + } + if(!wordemp){ + var wordemp=[]; + } + if(!wordmute){ + var wordmute=[]; + } + var local = []; + var times=[]; Object.keys(obj).forEach(function(key) { var toot = obj[key]; if(popup){ @@ -47,7 +93,6 @@ function parse(obj, mix, acct_id, tlid, popup) { '\',\'' + acct_id + '\')" class="pointer">' + toot.account.display_name + "(" + toot.account.acct + ")が" + what; - var toot = toot.status; var notice = noticetext; var memory = localStorage.getItem("notice-mem"); if (popup >= 0 && obj.length < 5 && noticetext != memory) { @@ -56,26 +101,44 @@ function parse(obj, mix, acct_id, tlid, popup) { localStorage.setItem("notice-mem", noticetext); noticetext = ""; } + var toot = toot.status; }else{ if (toot.reblog) { var notice = toot.account.display_name + "(" + toot.account.acct + ")がブースト
"; - var boostback = "shared"; + var boostback = "shared"; var toot = toot.reblog; } else { var notice = ""; var boostback = ""; + //ユーザー強調 + if(toot.account.username!=toot.account.acct){ + var fullname=toot.account.acct; + }else{ + var domain = localStorage.getItem("domain_" + acct_id); + var fullname=toot.account.acct+"@"+domain; + } + if(useremp){ + console.log(useremp); + Object.keys(useremp).forEach(function(key10) { + var user = useremp[key10]; + if(user==fullname){ + boostback = "emphasized"; + } + }); + } } } var id = toot.id; //Integratedである場合はUnix時間をキーに配列を生成しておく if (mix == "mix") { local[date(obj[key].created_at, 'unix')] = toot.id; + times.push(date(obj[key].created_at, 'unix')); var divider = '
'; } if (mix == "home") { var home = "Home TLより" - var divider = ""; + var divider = '
'; } else { var home = ""; var divider = '
'; @@ -89,6 +152,20 @@ function parse(obj, mix, acct_id, tlid, popup) { var via = 'Unknown'; } else { var via = toot.application.name; + //強調チェック + Object.keys(emp).forEach(function(key6) { + var cli = emp[key6]; + if(cli == via){ + boostback = "emphasized"; + } + }); + //ミュートチェック + Object.keys(mute).forEach(function(key7) { + var cli = mute[key7]; + if(cli == via){ + boostback = "hide"; + } + }); } if (toot.spoiler_text && cw) { var content = toot.content; @@ -157,7 +234,7 @@ function parse(obj, mix, acct_id, tlid, popup) { acct_id + ')" id="' + id + '-image-' + key2 + '" data-url="' + url + '" data-type="' + media.type + '" class="img-parsed">'; + ' toot-img pointer" style="width:' + cwdt + '%; height:'+imh+'px;">'; }); } else { viewer = ""; @@ -184,8 +261,9 @@ function parse(obj, mix, acct_id, tlid, popup) { } Object.keys(toot.tags).forEach(function(key4) { var tag = toot.tags[key4]; - tags = tags + '#' + tag.name + ' '; + tags = tags + '#' + tag.name + ' TL Toot '+ + 'Pin '; }); tags = '
' + tags + '
'; } @@ -228,14 +306,45 @@ function parse(obj, mix, acct_id, tlid, popup) { var if_rt = ""; var rt_app = ""; } + if (toot.pinned) { + var if_pin = "blue-text"; + var pin_app = "pinned"; + } else { + var if_pin = ""; + var pin_app = ""; + } //アニメ再生 if (gif == "yes") { var avatar = toot.account.avatar; } else { var avatar = toot.account.avatar_static; } + //ワードミュート + if(wordmute){ + Object.keys(wordmute).forEach(function(key8) { + var worde = wordmute[key8]; + if(worde){ + var word=worde.tag; + var regExp = new RegExp( word, "g" ) ; + if(content.match(regExp)){ + boostback = "hide"; + } + } + }); + } + //ワード強調 + if(wordemp){ + Object.keys(wordemp).forEach(function(key9) { + var word = wordemp[key9]; + if(word){ + var word=word.tag; + var regExp = new RegExp( word, "g" ) ; + content=content.replace(regExp,''+word+""); + } + }); + } templete = templete + '
' + '
' + notice + home + @@ -246,7 +355,7 @@ function parse(obj, mix, acct_id, tlid, popup) { '" width="40" class="prof-img" user="' + toot.account.acct + '">
' + '
' + - toot.account.display_name + + escapeHTML(toot.account.display_name) + ' @' + toot.account.acct + locked + '
' + '
' + + ')" class="waves-effect waves-dark btn-flat" style="padding:0" title="このトゥートに返信">
' + '' + '' + '
' + + ')" class="waves-effect waves-dark btn-flat" style="padding:0" title="このトゥートを削除">
' + + '
' + '' + + ','+tlid+')" class="waves-effect waves-dark btn-flat details" style="padding:0">more_vert' + '
' + - '
via ' + + '
via ' + via + '
' + '' + divider; }); if (mix == "mix") { - return [templete, local] + return [templete, local, times] } else { return templete; } @@ -351,3 +463,64 @@ function userparse(obj, auth, acct_id, tlid, popup) { }); return templete; } +//クライアントダイアログ +function client(name) { + if(name!="Unknown"){ + //聞く + localStorage.removeItem("client_mute"); + var electron = require("electron"); + var remote=electron.remote; + var dialog=remote.dialog; + const options = { + type: 'info', + title: 'クライアント処理', + message: name+"に対する処理を選択してください。", + buttons: ['何もしない','強調表示/解除', 'ミュート'] + } + dialog.showMessageBox(options, function(arg) { + if(arg==1){ + var cli = localStorage.getItem("client_emp"); + var obj = JSON.parse(cli); + if(!obj){ + var obj=[]; + obj.push(name); + Materialize.toast(name+"を強調表示します。", 2000); + }else{ + var can; + Object.keys(obj).forEach(function(key) { + var cliT = obj[key]; + if(cliT!=name && !can){ + can=false; + }else{ + can=true; + obj.splice(key, 1); + Materialize.toast(name+"の強調表示を解除しました。", 2000); + } + }); + if(!can){ + obj.push(name); + Materialize.toast(name+"を強調表示します。", 2000); + }else{ + + } + } + var json = JSON.stringify(obj); + localStorage.setItem("client_emp", json); + }else if(arg==2){ + var cli = localStorage.getItem("client_mute"); + var obj = JSON.parse(cli); + if(!obj){ + var obj=[]; + } + obj.push(name); + var json = JSON.stringify(obj); + localStorage.setItem("client_mute", json); + Materialize.toast(name+"をミュートします。設定から削除できます。", 2000); + }else{ + return; + } + parseColumn(); + }) + +} +} \ No newline at end of file diff --git a/app/js/tl/tag.js b/app/js/tl/tag.js new file mode 100644 index 00000000..add1c08b --- /dev/null +++ b/app/js/tl/tag.js @@ -0,0 +1,60 @@ +//よく使うタグ +function tagShow(tag){ + $("[data-tag="+tag+"]").toggleClass("hide"); +} +//タグ追加 +function tagPin(tag){ + var tags = localStorage.getItem("tag"); + if(!tags){ + var obj=[]; + }else{ + var obj = JSON.parse(tags); + } + var can; + Object.keys(obj).forEach(function(key) { + var tagT = obj[key]; + if(tagT==tag){ + can=true; + }else{ + can=false; + } + }); + if(!can){ + obj.push(tag); + } + var json = JSON.stringify(obj); + localStorage.setItem("tag", json); + favTag(); +} +//タグ削除 +function tagRemove(key) { + var tags = localStorage.getItem("tag"); + var obj = JSON.parse(tags); + obj.splice(key, 1); + var json = JSON.stringify(obj); + localStorage.setItem("tag", json); + favTag(); +} +function favTag(){ + var tagarr = localStorage.getItem("tag"); + if(!tagarr){ + var obj=[]; + }else{ + var obj = JSON.parse(tagarr); + } + var tags=""; + Object.keys(obj).forEach(function(key) { + var tag = obj[key]; + tags = tags + '#' + tag + ' TL Toot '+ + 'Unpin '; + }); + if(obj.length>0){ + $("#suggest").html("My Tags:" + tags); + }else{ + $("#suggest").html(""); + } +} +function tagTL(a,b,c,d){ + var acct_id = $("#post-acct-sel").val(); + tl(a,b,acct_id,d); +} \ No newline at end of file diff --git a/app/js/tl/tl.js b/app/js/tl/tl.js index 2c8704be..e16c6816 100644 --- a/app/js/tl/tl.js +++ b/app/js/tl/tl.js @@ -101,6 +101,7 @@ function reload(type, cc, acct_id, tlid, data) { websocket[wsid].onopen = function(mess) { console.log(tlid + ":Connect Streaming API:" + type); console.log(mess); + $("#notice_icon_" + tlid).removeClass("red-text"); } websocket[wsid].onmessage = function(mess) { console.log(tlid + ":Receive Streaming API:"); @@ -115,19 +116,24 @@ function reload(type, cc, acct_id, tlid, data) { } else if (typeA == "update") { var obj = JSON.parse(JSON.parse(mess.data).payload); console.log(obj); - var templete = parse([obj], '', acct_id, tlid); - var pool = localStorage.getItem("pool_" + tlid); - if (pool) { - pool = templete + pool; - } else { - pool = templete + if($("#timeline_" + tlid +" [toot-id=" + obj.id + "]").length < 1){ + var templete = parse([obj], '', acct_id, tlid); + var pool = localStorage.getItem("pool_" + tlid); + if (pool) { + pool = templete + pool; + } else { + pool = templete + } + localStorage.setItem("pool_" + tlid, pool); + + scrollck(); + + additional(acct_id, tlid); + jQuery("time.timeago").timeago(); + }else{ + todo("二重取得発生中"); } - localStorage.setItem("pool_" + tlid, pool); - - scrollck(); - - additional(acct_id, tlid); - jQuery("time.timeago").timeago(); + todc(); } websocket[wsid].onclose = function(mess) { diff --git a/app/js/ui/img.js b/app/js/ui/img.js index 8885bd81..280ed822 100644 --- a/app/js/ui/img.js +++ b/app/js/ui/img.js @@ -271,7 +271,7 @@ function dlImg(){ var url=$("#imgmodal").attr("src"); var electron = require("electron"); var ipc = electron.ipcRenderer; - ipc.send('general-dl', url); + ipc.send('general-dl', [url,false]); ipc.on('general-dl-prog', function (event, arg) { console.log(arg); }) diff --git a/app/js/ui/layout.js b/app/js/ui/layout.js index 02de8411..46852cdf 100644 --- a/app/js/ui/layout.js +++ b/app/js/ui/layout.js @@ -63,7 +63,7 @@ var acct = obj[key]; var html = '
'+ - '
'+ + '
'+ '
'+ ''; + $("#mute-cli").append(templete); + }); +} +} +function cliMuteDel(key){ + var cli = localStorage.getItem("client_mute"); + var obj = JSON.parse(cli); + obj.splice(key, 1); + var json = JSON.stringify(obj); + localStorage.setItem("client_mute", json); + mute(); +} + +function wordmute(){ + var word = localStorage.getItem("word_mute"); + var obj = JSON.parse(word); + $('#wordmute').material_chip({ + data: obj, + }); +} +function wordmuteSave(){ + var word=$('#wordmute').material_chip('data'); + var json = JSON.stringify(word); + localStorage.setItem("word_mute", json); +} + +function wordemp(){ + var word = localStorage.getItem("word_emp"); + var obj = JSON.parse(word); + $('#wordemp').material_chip({ + data: obj, + }); +} +function wordempSave(){ + var word=$('#wordemp').material_chip('data'); + var json = JSON.stringify(word); + localStorage.setItem("word_emp", json); +} \ No newline at end of file diff --git a/app/js/userdata/showOnTL.js b/app/js/userdata/showOnTL.js index 0e3ac7e5..3fb08ac5 100644 --- a/app/js/userdata/showOnTL.js +++ b/app/js/userdata/showOnTL.js @@ -51,14 +51,17 @@ function udg(user, acct_id) { if(json.username!=json.acct){ //Remote $('#his-data').attr("remote", "true"); + var fullname=json.acct; }else{ $('#his-data').attr("remote", "false"); + var fullname=json.acct+"@"+domain; } utl(json.id, '', acct_id); flw(json.id, '', acct_id); fer(json.id, '', acct_id); $("#his-name").text(json.display_name); $("#his-acct").text(json.acct); + $("#his-acct").attr("fullname",fullname); $("#his-prof").attr("src", json.avatar); $('#his-data').css('background-image', 'url(' + json.header + ')'); $("#his-sta").text(json.statuses_count); @@ -85,6 +88,7 @@ function udg(user, acct_id) { $("#his-mute-btn").hide(); $("#his-notf-btn").hide(); $("#his-domain-btn").hide(); + $("#his-emp-btn").hide(); $("#my-data-nav").show(); $("#his-data-nav").hide(); } else { @@ -195,6 +199,7 @@ function reset(){ $("#his-mute-btn").show(); $("#his-notf-btn").show(); $("#his-domain-btn").show(); + $("#his-emp-btn").show(); $("#his-follow-btn").text("フォロー"); $("#his-mute-btn").text("ミュート"); $("#his-block-btn").text("ブロック"); diff --git a/app/main.js b/app/main.js index 1185e719..99df0e48 100644 --- a/app/main.js +++ b/app/main.js @@ -5,6 +5,9 @@ const electron = require("electron"); const fs = require("fs"); const dialog = require('electron').dialog; var Jimp = require("jimp"); +const shell = electron.shell; +const os = require('os') +const path = require('path') // アプリケーションをコントロールするモジュール const app = electron.app; @@ -41,7 +44,7 @@ function createWindow() { electron.session.defaultSession.clearCache(() => {}) if(process.argv){ if(process.argv[1]){ - var m = process.argv[1].match(/([a-zA-Z0-9]+)\/\?[a-zA-Z-0-9]+=([a-zA-Z-0-9]+)/); + var m = process.argv[1].match(/([a-zA-Z0-9]+)\/\?[a-zA-Z-0-9]+=(.+)/); if(m){ var mode=m[1]; var code=m[2]; @@ -80,19 +83,36 @@ ipc.on('update', function(e, x, y) { return "true" }) -ipc.on('nano', function(e, x, y) { +ipc.on('screen', function(e, args) { var window = new BrowserWindow({ - width: 300, - height: 100, - "transparent": true, // ウィンドウの背景を透過 + width: args[0], + height: args[1], + "transparent": false, // ウィンドウの背景を透過 "frame": false, // 枠の無いウィンドウ - "resizable": false + "resizable": true }); - window.loadURL('file://' + __dirname + '/nano.html'); + window.loadURL('file://' + __dirname + '/screenshot.html?id='+args[2]); window.setAlwaysOnTop(true); window.setPosition(0, 0); return "true" }) +//Web魚拓 +ipc.on('shot', function(e, args) { + console.log(args[0]); + Jimp.read(Buffer.from( args[3],'base64'), function (err, lenna) { + if (err) throw err; + lenna.crop( 0, 0, args[1], args[2] ).write(app.getPath('home')+"\\Pictures\\TheDesk\\Screenshots\\"+args[4]+"-toot.png"); + }); + shell.showItemInFolder(app.getPath('home')+"\\Pictures\\TheDesk\\Screenshots\\"); +}) +ipc.on('shot-img-dl', (e, args) => { + Jimp.read(args[0], function (err, lenna) { + if (err) throw err; + lenna.write(app.getPath('home')+"\\Pictures\\TheDesk\\Screenshots\\"+args[1]); + }); + +}); +//アプデDL ipc.on('download-btn', (e, args) => { if(args=="true"){ dialog.showSaveDialog(null, { @@ -143,10 +163,12 @@ function dl(files,fullname){ .catch(console.error); } ipc.on('general-dl', (e, args) => { - console.log(args) + var name=""; + var dir=app.getPath('home')+"\\Pictures\\TheDesk"; mainWindow.webContents.send('general-dl-message', "ダウンロードを開始します。"); const opts = { - directory: app.getPath('home')+"\\Pictures\\TheDesk", + directory: dir, + filename:name, openFolderWhenDone: true, onProgress: function(e) { mainWindow.webContents.send('general-dl-prog', e); @@ -154,7 +176,7 @@ ipc.on('general-dl', (e, args) => { saveAs: false }; download(BrowserWindow.getFocusedWindow(), - args, opts) + args[0], opts) .then(dl => { mainWindow.webContents.send('general-dl-message', "ダウンロードが完了しました。"); }) @@ -195,4 +217,14 @@ ipc.on('bmp-image', (e, args) => { }); }); +ipc.on('nano', function (e, x, y) { + var window = new BrowserWindow({width: 300, height: 200, + "transparent": false, // ウィンドウの背景を透過 + "frame": false, // 枠の無いウィンドウ + "resizable": false }); + window.loadURL('file://' + __dirname + '/nano.html'); + window.setAlwaysOnTop(true); + window.setPosition(0, 0); + return "true" + }) app.setAsDefaultProtocolClient('thedesk') diff --git a/app/nano.html b/app/nano.html new file mode 100644 index 00000000..3775b02f --- /dev/null +++ b/app/nano.html @@ -0,0 +1,98 @@ + + + + + + +TheDesk Nano + + + + + +
+
+ + + +

+ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/package.json b/app/package.json index 3b25c8d7..cbf58820 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "TheDesk", - "version": "12.8.0", + "version": "13.1.0", "description": "TheDesk on Mastodonはシンプルと多機能を両立したデスクトップ向けクライアントです", "main": "main.js", "scripts": { diff --git a/app/screenshot.html b/app/screenshot.html new file mode 100644 index 00000000..530cca88 --- /dev/null +++ b/app/screenshot.html @@ -0,0 +1,29 @@ + + + +TheDesk Screenshot + + + + + + + + + + + + + + + +
+ + +
+数秒お待ち下さい。完了後エクスプローラーが表示されます。 \ No newline at end of file diff --git a/app/setting.html b/app/setting.html index 81aa6dc3..e98b0a89 100644 --- a/app/setting.html +++ b/app/setting.html @@ -61,7 +61,6 @@
px -
  • @@ -106,6 +105,11 @@ 文字以上
    +
    画像の高さ
    +
    + px + +
  • @@ -150,6 +154,28 @@
  • +
  • +
    + bookmarkミュート・強調の設定 +
    +
    +
    クライアントミュート
    +
    +
    クライアント強調
    + 各トゥートのクライアントをクリックすると設定できます。 +
    ワードミュート
    + Enterで確定
    +
    + +
    ワード強調
    + Enterで確定
    +
    + +
    ユーザー強調
    + 各ユーザーのデータ表示画面で設定できます。 +  強調色(テーマによって異なります。)  +
    +

  • undo戻る @@ -194,6 +220,9 @@
    trending_up寄付(Enty)
    +寿司を投げる
    +Osushi.love(スマートフォンから)
    +
    GitHub
    Developer: Cutls@kirishima.cloud diff --git a/ver.json b/ver.json index f9d532c9..57cb0aba 100644 --- a/ver.json +++ b/ver.json @@ -1 +1 @@ -{"desk":"Riina (ver.8)","date":"2018-02-25","detail":"内部V:12.8.0|バグ修正。プラットフォーム追加。詳しくはGitHub参照"} \ No newline at end of file +{"desk":"Airi (ver.1)","date":"2018-03-10","detail":"内部V:13.1.0|TheDesk史上最大の機能追加数。詳しくはGitHubやHP参照"} \ No newline at end of file