mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Add batch operations
This commit is contained in:
parent
0bac3b2d78
commit
b9fe65b455
263
@types/jszip/index.d.ts
vendored
Normal file
263
@types/jszip/index.d.ts
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
// Type definitions for JSZip 3.1
|
||||
// Project: http://stuk.github.com/jszip/, https://github.com/stuk/jszip
|
||||
// Definitions by: mzeiher <https://github.com/mzeiher>, forabi <https://github.com/forabi>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
|
||||
interface JSZipSupport
|
||||
{
|
||||
arraybuffer: boolean;
|
||||
uint8array: boolean;
|
||||
blob: boolean;
|
||||
nodebuffer: boolean;
|
||||
}
|
||||
|
||||
type Compression = 'STORE' | 'DEFLATE';
|
||||
|
||||
interface Metadata
|
||||
{
|
||||
percent: number;
|
||||
currentFile: string;
|
||||
}
|
||||
|
||||
type OnUpdateCallback = (metadata: Metadata) => void;
|
||||
|
||||
interface InputByType
|
||||
{
|
||||
base64: string;
|
||||
string: string;
|
||||
text: string;
|
||||
binarystring: string;
|
||||
array: number[];
|
||||
uint8array: Uint8Array;
|
||||
arraybuffer: ArrayBuffer;
|
||||
blob: Blob;
|
||||
}
|
||||
|
||||
interface OutputByType
|
||||
{
|
||||
base64: string;
|
||||
text: string;
|
||||
binarystring: string;
|
||||
array: number[];
|
||||
uint8array: Uint8Array;
|
||||
arraybuffer: ArrayBuffer;
|
||||
blob: Blob;
|
||||
}
|
||||
|
||||
type InputFileFormat = InputByType[keyof InputByType];
|
||||
|
||||
declare namespace JSZip
|
||||
{
|
||||
type InputType = keyof InputByType;
|
||||
|
||||
type OutputType = keyof OutputByType;
|
||||
|
||||
interface JSZipObject
|
||||
{
|
||||
name: string;
|
||||
dir: boolean;
|
||||
date: Date;
|
||||
comment: string;
|
||||
/** The UNIX permissions of the file, if any. */
|
||||
unixPermissions: number | string | null;
|
||||
/** The UNIX permissions of the file, if any. */
|
||||
dosPermissions: number | null;
|
||||
options: JSZipObjectOptions;
|
||||
|
||||
/**
|
||||
* Prepare the content in the asked type.
|
||||
* @param type the type of the result.
|
||||
* @param onUpdate a function to call on each internal update.
|
||||
* @return Promise the promise of the result.
|
||||
*/
|
||||
async<T extends OutputType>(type: T, onUpdate?: OnUpdateCallback): Promise<OutputByType[T]>;
|
||||
}
|
||||
|
||||
interface JSZipFileOptions
|
||||
{
|
||||
/** Set to `true` if the data is `base64` encoded. For example image data from a `<canvas>` element. Plain text and HTML do not need this option. */
|
||||
base64?: boolean;
|
||||
/**
|
||||
* Set to `true` if the data should be treated as raw content, `false` if this is a text. If `base64` is used,
|
||||
* this defaults to `true`, if the data is not a `string`, this will be set to `true`.
|
||||
*/
|
||||
binary?: boolean;
|
||||
/**
|
||||
* The last modification date, defaults to the current date.
|
||||
*/
|
||||
date?: Date;
|
||||
compression?: string;
|
||||
comment?: string;
|
||||
/** Set to `true` if (and only if) the input is a "binary string" and has already been prepared with a `0xFF` mask. */
|
||||
optimizedBinaryString?: boolean;
|
||||
/** Set to `true` if folders in the file path should be automatically created, otherwise there will only be virtual folders that represent the path to the file. */
|
||||
createFolders?: boolean;
|
||||
/** Set to `true` if this is a directory and content should be ignored. */
|
||||
dir?: boolean;
|
||||
|
||||
/** 6 bits number. The DOS permissions of the file, if any. */
|
||||
dosPermissions?: number | null;
|
||||
/**
|
||||
* 16 bits number. The UNIX permissions of the file, if any.
|
||||
* Also accepts a `string` representing the octal value: `"644"`, `"755"`, etc.
|
||||
*/
|
||||
unixPermissions?: number | string | null;
|
||||
}
|
||||
|
||||
interface JSZipObjectOptions
|
||||
{
|
||||
compression: Compression;
|
||||
}
|
||||
|
||||
interface JSZipGeneratorOptions<T extends OutputType = OutputType>
|
||||
{
|
||||
compression?: Compression;
|
||||
compressionOptions?: null | {
|
||||
level: number;
|
||||
};
|
||||
type?: T;
|
||||
comment?: string;
|
||||
/**
|
||||
* mime-type for the generated file.
|
||||
* Useful when you need to generate a file with a different extension, ie: “.ods”.
|
||||
* @default 'application/zip'
|
||||
*/
|
||||
mimeType?: string;
|
||||
encodeFileName?(filename: string): string;
|
||||
/** Stream the files and create file descriptors */
|
||||
streamFiles?: boolean;
|
||||
/** DOS (default) or UNIX */
|
||||
platform?: 'DOS' | 'UNIX';
|
||||
}
|
||||
|
||||
interface JSZipLoadOptions
|
||||
{
|
||||
base64?: boolean;
|
||||
checkCRC32?: boolean;
|
||||
optimizedBinaryString?: boolean;
|
||||
createFolders?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
interface JSZip
|
||||
{
|
||||
files: { [key: string]: JSZip.JSZipObject };
|
||||
|
||||
/**
|
||||
* Get a file from the archive
|
||||
*
|
||||
* @param Path relative path to file
|
||||
* @return File matching path, null if no file found
|
||||
*/
|
||||
file(path: string): JSZip.JSZipObject;
|
||||
|
||||
/**
|
||||
* Get files matching a RegExp from archive
|
||||
*
|
||||
* @param path RegExp to match
|
||||
* @return Return all matching files or an empty array
|
||||
*/
|
||||
file(path: RegExp): JSZip.JSZipObject[];
|
||||
|
||||
/**
|
||||
* Add a file to the archive
|
||||
*
|
||||
* @param path Relative path to file
|
||||
* @param data Content of the file
|
||||
* @param options Optional information about the file
|
||||
* @return JSZip object
|
||||
*/
|
||||
file<T extends JSZip.InputType>(path: string, data: InputByType[T] | Promise<InputByType[T]>, options?: JSZip.JSZipFileOptions): this;
|
||||
file<T extends JSZip.InputType>(path: string, data: null, options?: JSZip.JSZipFileOptions & { dir: true }): this;
|
||||
|
||||
/**
|
||||
* Returns an new JSZip instance with the given folder as root
|
||||
*
|
||||
* @param name Name of the folder
|
||||
* @return New JSZip object with the given folder as root or null
|
||||
*/
|
||||
folder(name: string): JSZip;
|
||||
|
||||
/**
|
||||
* Returns new JSZip instances with the matching folders as root
|
||||
*
|
||||
* @param name RegExp to match
|
||||
* @return New array of JSZipFile objects which match the RegExp
|
||||
*/
|
||||
folder(name: RegExp): JSZip.JSZipObject[];
|
||||
|
||||
/**
|
||||
* Call a callback function for each entry at this folder level.
|
||||
*
|
||||
* @param callback function
|
||||
*/
|
||||
forEach(callback: (relativePath: string, file: JSZip.JSZipObject) => void): void;
|
||||
|
||||
/**
|
||||
* Get all files which match the given filter function
|
||||
*
|
||||
* @param predicate Filter function
|
||||
* @return Array of matched elements
|
||||
*/
|
||||
filter(predicate: (relativePath: string, file: JSZip.JSZipObject) => boolean): JSZip.JSZipObject[];
|
||||
|
||||
/**
|
||||
* Removes the file or folder from the archive
|
||||
*
|
||||
* @param path Relative path of file or folder
|
||||
* @return Returns the JSZip instance
|
||||
*/
|
||||
remove(path: string): JSZip;
|
||||
|
||||
/**
|
||||
* Generates a new archive asynchronously
|
||||
*
|
||||
* @param options Optional options for the generator
|
||||
* @param onUpdate The optional function called on each internal update with the metadata.
|
||||
* @return The serialized archive
|
||||
*/
|
||||
generateAsync<T extends JSZip.OutputType>(options?: JSZip.JSZipGeneratorOptions<T>, onUpdate?: OnUpdateCallback): Promise<OutputByType[T]>;
|
||||
|
||||
|
||||
/**
|
||||
* Deserialize zip file asynchronously
|
||||
*
|
||||
* @param data Serialized zip file
|
||||
* @param options Options for deserializing
|
||||
* @return Returns promise
|
||||
*/
|
||||
loadAsync(data: InputFileFormat, options?: JSZip.JSZipLoadOptions): Promise<JSZip>;
|
||||
|
||||
/**
|
||||
* Create JSZip instance
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create JSZip instance
|
||||
* If no parameters given an empty zip archive will be created
|
||||
*
|
||||
* @param data Serialized zip archive
|
||||
* @param options Description of the serialized zip archive
|
||||
*/
|
||||
new(data?: InputFileFormat, options?: JSZip.JSZipLoadOptions): this;
|
||||
|
||||
(): JSZip;
|
||||
|
||||
prototype: JSZip;
|
||||
support: JSZipSupport;
|
||||
external: {
|
||||
Promise: PromiseConstructorLike;
|
||||
};
|
||||
version: string;
|
||||
}
|
||||
|
||||
|
||||
export = JSZip;
|
||||
|
||||
|
||||
declare global
|
||||
{
|
||||
const JSZip: JSZip;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name Bilibili Evolved (Offline)
|
||||
// @version 306.90
|
||||
// @version 307.04
|
||||
// @description Bilibili Evolved 的离线版, 所有功能都已内置于脚本中.
|
||||
// @author Grant Howard, Coulomb-G
|
||||
// @copyright 2019, Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)
|
||||
@ -1099,8 +1099,8 @@ offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/m
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-promotions.min.js"] = (()=>{return(e,n)=>{SpinQuery.any(()=>document.querySelectorAll(".gg-pic"),e=>{e.forEach(e=>{const n=e.parentElement;n.style.display="none";const t=[...n.parentElement.childNodes].indexOf(n)+1;const l=n.parentElement.parentElement.querySelector(`.pic li:nth-child(${t})`);if(l){l.style.visibility="hidden"}})})}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-top-mask.min.js"] = (()=>{return(e,t)=>{const o=`.bilibili-player-video-top { display: none !important; }`;const n="remove-top-mask-style";const l=()=>t.applyStyleFromText(`<style id="${n}">${o}</style>`);const r=()=>{const e=document.getElementById(n);if(e){e.remove()}};l();return{reload:l,unload:r}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-watermark.min.js"] = (()=>{return(i,l)=>{const n="bilibili-live-watermark";if($(`#${n}`).length===0){l.applyStyleFromText(`\n <style id='${n}'>\n .bilibili-live-player-video-logo\n {\n display: none !important;\n }\n </style>\n `)}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.css"] = `.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-list,.video-screenshot-list *{transition:.2s ease-out}.video-screenshot-list{position:absolute;top:0;right:0;z-index:20000;padding:12px 0;pointer-events:none}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(-240px)}.video-screenshot-list-leave-to{opacity:0;transform:translateX(240px)}.video-screenshot-thumbnail img{max-width:240px;display:block;background-color:#000}.video-screenshot-thumbnail{margin:12px 24px;position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:240px;height:135px;background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.js"] = (()=>{return(e,t)=>{const{getFriendlyTitle:i}=t.import("title");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){const e=document.createElement("canvas");e.width=this.video.videoWidth;e.height=this.video.videoHeight;const t=e.getContext("2d");if(t===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}t.drawImage(this.video,0,0);e.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.url=URL.createObjectURL(e)},"image/png")}get filename(){return i()+" @"+this.time.toString()+".png"}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const s=e=>{const t=document.createElement("canvas");t.width=e.videoWidth;t.height=e.videoHeight;const i=e.currentTime;return new n(e,i)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <transition-group class="video-screenshot-list" name="video-screenshot-list">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a v-bind:href="objectUrl" v-bind:download="filename" title="保存">\n <button class="save"><i class="mdi mdi-content-save-outline"></i></button>\n </a>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")}}});const o=new Vue({el:".video-screenshot-list",data:{screenshots:[]},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const i=document.querySelector(".video-take-screenshot");if(i===null){return}i.addEventListener("click",()=>{const t=s(e);o.screenshots.push(t)})});return{export:{takeScreenshot:s,screenShotsList:o}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.css"] = `.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-container{position:relative;--screenshot-width:240px;--screenshot-width-negative:calc(0px - var(--screenshot-width));--screenshot-height:135px;--thumbnail-margin-vertical:12px;--thumbnail-margin-horizontal:24px;--screenshot-list-width:calc(2 * var(--thumbnail-margin-horizontal) + var(--screenshot-width))}.video-screenshot-batch{position:fixed;bottom:0;right:0;z-index:20000;display:flex;width:var(--screenshot-list-width);align-items:center;justify-content:space-evenly}.video-screenshot-batch button{background:#000c;color:#fff;border:none;border-radius:10px 10px 0 0;font-size:12pt;cursor:pointer;outline:0!important;padding:8px 12px;display:flex;justify-content:center;align-items:center}.video-screenshot-batch button i{font-size:14pt;margin-right:4px}.video-screenshot-container,.video-screenshot-container *{transition:.2s ease-out}.video-screenshot-list{position:fixed;top:0;right:0;z-index:20000;padding:var(--thumbnail-margin-vertical) 0;pointer-events:none;height:calc(100% - 2 * var(--thumbnail-margin-vertical) - 48px);width:var(--screenshot-list-width);overflow:auto}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(var(--screenshot-width-negative))}.video-screenshot-list-leave-to{opacity:0;transform:translateX(var(--screenshot-width))}.video-screenshot-thumbnail img{max-width:var(--screenshot-width);display:block;background-color:#000}.video-screenshot-thumbnail{margin:var(--thumbnail-margin-vertical) var(--thumbnail-margin-horizontal);position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:var(--screenshot-width);height:var(--screenshot-height);background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center;transition:none}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.js"] = (()=>{return(e,t)=>{const{getFriendlyTitle:s}=t.import("title");const i=document.createElement("canvas");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){i.width=this.video.videoWidth;i.height=this.video.videoHeight;const e=i.getContext("2d");if(e===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}e.drawImage(this.video,0,0);i.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.blob=e;this.url=URL.createObjectURL(e)},"image/png")}get filename(){return`${s()} @${this.time.toString()}:${this.timeStamp.toString()}.png`}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const o=e=>{const t=e.currentTime;return new n(e,t)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <div class="video-screenshot-container">\n <transition-group class="video-screenshot-list" name="video-screenshot-list" tag="div">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n <div v-show="showBatch" class="video-screenshot-batch">\n <a class="batch-link" style="display:none" v-bind:download="batchFilename"></a>\n <button v-on:click="saveAll">\n <i class="mdi mdi-content-save"></i>全部保存\n </button>\n <button v-on:click="discardAll">\n <i class="mdi mdi-delete-forever"></i>全部丢弃\n </button>\n </div>\n </div>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a class="link" style="display:none" v-bind:href="objectUrl" v-bind:download="filename"></a>\n <button v-on:click="save" class="save" title="保存"><i class="mdi mdi-content-save-outline"></i></button>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")},save(){this.$el.querySelector(".link").click();this.discard()}}});const r=new Vue({el:".video-screenshot-container",data:{screenshots:[],batchFilename:s()+".zip"},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()},async saveAll(){const e=new JSZip;this.screenshots.forEach(t=>{e.file(t.filename,t.blob)});const t=await e.generateAsync({type:"blob"});const s=this.$el.querySelector(".batch-link");s.href=URL.createObjectURL(t);s.click();URL.revokeObjectURL(s.href);s.href=""},discardAll(){this.screenshots.forEach(e=>e.revoke());this.screenshots=[]}},computed:{showBatch(){return this.screenshots.length>=2}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const s=document.querySelector(".video-take-screenshot");if(s===null){return}s.addEventListener("click",()=>{const t=o(e);r.screenshots.unshift(t)})});return{export:{takeScreenshot:o,screenShotsList:r}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/scrollbar.min.css"] = `::-webkit-scrollbar{width:5px!important;height:5px!important}::-webkit-scrollbar-corner,::-webkit-scrollbar-track{background:0 0!important}::-webkit-resizer,::-webkit-scrollbar-thumb{background:#aaa}::-webkit-scrollbar-thumb:hover{background:#888}*{scrollbar-color:#aaa transparent;scrollbar-width:thin!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/settings-search.min.js"] = (()=>{return(t,e)=>{class s{constructor(){this.input=document.querySelector(".gui-settings-search");const t=[...document.querySelectorAll(".gui-settings-content>ul>li")];const e=t=>e=>e.classList.contains("category")===t;this.categories=t.filter(e(true));this.items=t.filter(e(false));this.importToolTips().then(()=>this.input.addEventListener("input",()=>this.keywordChange()))}async importToolTips(){if(typeof getI18nKey==="undefined"){console.error("请更新脚本后再使用设置搜索功能.");return}const{toolTips:t}=await e.importAsync(`settings-tooltip.${getI18nKey()}`);this.toolTips=t}keywordChange(){const t=this.input.value.trim();if(!t){this.categories.concat(this.items).forEach(t=>t.classList.add("folded"));return}this.items.forEach(e=>{const s=e.querySelector("input").getAttribute("key");const i=Resource.displayNames[s]+this.toolTips.get(s).replace(/<.*>|<\/.*>/g,"");if(i.includes(t)){e.classList.remove("folded")}else{e.classList.add("folded")}});this.foldCategories()}foldCategories(){for(const e of this.categories){function t(){let t=e.nextElementSibling;while(t!==null&&!t.classList.contains("category")){if(!t.classList.contains("folded")){return"remove"}t=t.nextElementSibling}return"add"}e.classList[t()]("folded")}}}return{export:{SettingsSearch:s}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/settings-side-bar.min.js"] = (()=>{return(e,i)=>{if(document.querySelector(".gui-settings-icon-panel")===null){document.body.insertAdjacentHTML("beforeend",`\n <div class='gui-settings-icon-panel icons-enabled'>\n <div class='gui-settings-widgets' title='附加功能'>\n <i class="icon-widgets"></i>\n </div>\n <div class='gui-settings' title='设置'>\n <i class="icon-settings"></i>\n </div>\n </div>`);document.querySelector(".gui-settings").addEventListener("click",e=>{if(e.shiftKey===false){document.querySelectorAll(".gui-settings-box,.gui-settings-mask").forEach(e=>e.classList.add("opened"))}else{document.querySelectorAll(".bilibili-evolved-about,.gui-settings-mask").forEach(e=>e.classList.add("opened"))}});document.querySelector(".gui-settings-widgets").addEventListener("click",()=>{document.querySelectorAll(".gui-settings-widgets-box,.gui-settings-mask").forEach(e=>e.classList.add("opened"))})}}})();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name Bilibili Evolved (Preview Offline)
|
||||
// @version 306.90
|
||||
// @version 307.04
|
||||
// @description Bilibili Evolved 的预览离线版, 可以抢先体验新功能, 并且所有功能都已内置于脚本中.
|
||||
// @author Grant Howard, Coulomb-G
|
||||
// @copyright 2019, Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)
|
||||
@ -1099,8 +1099,8 @@ offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/m
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-promotions.min.js"] = (()=>{return(e,n)=>{SpinQuery.any(()=>document.querySelectorAll(".gg-pic"),e=>{e.forEach(e=>{const n=e.parentElement;n.style.display="none";const t=[...n.parentElement.childNodes].indexOf(n)+1;const l=n.parentElement.parentElement.querySelector(`.pic li:nth-child(${t})`);if(l){l.style.visibility="hidden"}})})}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-top-mask.min.js"] = (()=>{return(e,t)=>{const o=`.bilibili-player-video-top { display: none !important; }`;const n="remove-top-mask-style";const l=()=>t.applyStyleFromText(`<style id="${n}">${o}</style>`);const r=()=>{const e=document.getElementById(n);if(e){e.remove()}};l();return{reload:l,unload:r}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-watermark.min.js"] = (()=>{return(i,l)=>{const n="bilibili-live-watermark";if($(`#${n}`).length===0){l.applyStyleFromText(`\n <style id='${n}'>\n .bilibili-live-player-video-logo\n {\n display: none !important;\n }\n </style>\n `)}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.css"] = `.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-list,.video-screenshot-list *{transition:.2s ease-out}.video-screenshot-list{position:absolute;top:0;right:0;z-index:20000;padding:12px 0;pointer-events:none}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(-240px)}.video-screenshot-list-leave-to{opacity:0;transform:translateX(240px)}.video-screenshot-thumbnail img{max-width:240px;display:block;background-color:#000}.video-screenshot-thumbnail{margin:12px 24px;position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:240px;height:135px;background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.js"] = (()=>{return(e,t)=>{const{getFriendlyTitle:i}=t.import("title");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){const e=document.createElement("canvas");e.width=this.video.videoWidth;e.height=this.video.videoHeight;const t=e.getContext("2d");if(t===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}t.drawImage(this.video,0,0);e.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.url=URL.createObjectURL(e)},"image/png")}get filename(){return i()+" @"+this.time.toString()+".png"}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const s=e=>{const t=document.createElement("canvas");t.width=e.videoWidth;t.height=e.videoHeight;const i=e.currentTime;return new n(e,i)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <transition-group class="video-screenshot-list" name="video-screenshot-list">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a v-bind:href="objectUrl" v-bind:download="filename" title="保存">\n <button class="save"><i class="mdi mdi-content-save-outline"></i></button>\n </a>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")}}});const o=new Vue({el:".video-screenshot-list",data:{screenshots:[]},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const i=document.querySelector(".video-take-screenshot");if(i===null){return}i.addEventListener("click",()=>{const t=s(e);o.screenshots.push(t)})});return{export:{takeScreenshot:s,screenShotsList:o}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.css"] = `.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-container{position:relative;--screenshot-width:240px;--screenshot-width-negative:calc(0px - var(--screenshot-width));--screenshot-height:135px;--thumbnail-margin-vertical:12px;--thumbnail-margin-horizontal:24px;--screenshot-list-width:calc(2 * var(--thumbnail-margin-horizontal) + var(--screenshot-width))}.video-screenshot-batch{position:fixed;bottom:0;right:0;z-index:20000;display:flex;width:var(--screenshot-list-width);align-items:center;justify-content:space-evenly}.video-screenshot-batch button{background:#000c;color:#fff;border:none;border-radius:10px 10px 0 0;font-size:12pt;cursor:pointer;outline:0!important;padding:8px 12px;display:flex;justify-content:center;align-items:center}.video-screenshot-batch button i{font-size:14pt;margin-right:4px}.video-screenshot-container,.video-screenshot-container *{transition:.2s ease-out}.video-screenshot-list{position:fixed;top:0;right:0;z-index:20000;padding:var(--thumbnail-margin-vertical) 0;pointer-events:none;height:calc(100% - 2 * var(--thumbnail-margin-vertical) - 48px);width:var(--screenshot-list-width);overflow:auto}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(var(--screenshot-width-negative))}.video-screenshot-list-leave-to{opacity:0;transform:translateX(var(--screenshot-width))}.video-screenshot-thumbnail img{max-width:var(--screenshot-width);display:block;background-color:#000}.video-screenshot-thumbnail{margin:var(--thumbnail-margin-vertical) var(--thumbnail-margin-horizontal);position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:var(--screenshot-width);height:var(--screenshot-height);background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center;transition:none}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/screenshot.min.js"] = (()=>{return(e,t)=>{const{getFriendlyTitle:s}=t.import("title");const i=document.createElement("canvas");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){i.width=this.video.videoWidth;i.height=this.video.videoHeight;const e=i.getContext("2d");if(e===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}e.drawImage(this.video,0,0);i.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.blob=e;this.url=URL.createObjectURL(e)},"image/png")}get filename(){return`${s()} @${this.time.toString()}:${this.timeStamp.toString()}.png`}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const o=e=>{const t=e.currentTime;return new n(e,t)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <div class="video-screenshot-container">\n <transition-group class="video-screenshot-list" name="video-screenshot-list" tag="div">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n <div v-show="showBatch" class="video-screenshot-batch">\n <a class="batch-link" style="display:none" v-bind:download="batchFilename"></a>\n <button v-on:click="saveAll">\n <i class="mdi mdi-content-save"></i>全部保存\n </button>\n <button v-on:click="discardAll">\n <i class="mdi mdi-delete-forever"></i>全部丢弃\n </button>\n </div>\n </div>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a class="link" style="display:none" v-bind:href="objectUrl" v-bind:download="filename"></a>\n <button v-on:click="save" class="save" title="保存"><i class="mdi mdi-content-save-outline"></i></button>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")},save(){this.$el.querySelector(".link").click();this.discard()}}});const r=new Vue({el:".video-screenshot-container",data:{screenshots:[],batchFilename:s()+".zip"},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()},async saveAll(){const e=new JSZip;this.screenshots.forEach(t=>{e.file(t.filename,t.blob)});const t=await e.generateAsync({type:"blob"});const s=this.$el.querySelector(".batch-link");s.href=URL.createObjectURL(t);s.click();URL.revokeObjectURL(s.href);s.href=""},discardAll(){this.screenshots.forEach(e=>e.revoke());this.screenshots=[]}},computed:{showBatch(){return this.screenshots.length>=2}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const s=document.querySelector(".video-take-screenshot");if(s===null){return}s.addEventListener("click",()=>{const t=o(e);r.screenshots.unshift(t)})});return{export:{takeScreenshot:o,screenShotsList:r}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/scrollbar.min.css"] = `::-webkit-scrollbar{width:5px!important;height:5px!important}::-webkit-scrollbar-corner,::-webkit-scrollbar-track{background:0 0!important}::-webkit-resizer,::-webkit-scrollbar-thumb{background:#aaa}::-webkit-scrollbar-thumb:hover{background:#888}*{scrollbar-color:#aaa transparent;scrollbar-width:thin!important}`;
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/settings-search.min.js"] = (()=>{return(t,e)=>{class s{constructor(){this.input=document.querySelector(".gui-settings-search");const t=[...document.querySelectorAll(".gui-settings-content>ul>li")];const e=t=>e=>e.classList.contains("category")===t;this.categories=t.filter(e(true));this.items=t.filter(e(false));this.importToolTips().then(()=>this.input.addEventListener("input",()=>this.keywordChange()))}async importToolTips(){if(typeof getI18nKey==="undefined"){console.error("请更新脚本后再使用设置搜索功能.");return}const{toolTips:t}=await e.importAsync(`settings-tooltip.${getI18nKey()}`);this.toolTips=t}keywordChange(){const t=this.input.value.trim();if(!t){this.categories.concat(this.items).forEach(t=>t.classList.add("folded"));return}this.items.forEach(e=>{const s=e.querySelector("input").getAttribute("key");const i=Resource.displayNames[s]+this.toolTips.get(s).replace(/<.*>|<\/.*>/g,"");if(i.includes(t)){e.classList.remove("folded")}else{e.classList.add("folded")}});this.foldCategories()}foldCategories(){for(const e of this.categories){function t(){let t=e.nextElementSibling;while(t!==null&&!t.classList.contains("category")){if(!t.classList.contains("folded")){return"remove"}t=t.nextElementSibling}return"add"}e.classList[t()]("folded")}}}return{export:{SettingsSearch:s}}}})();
|
||||
offlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/settings-side-bar.min.js"] = (()=>{return(e,i)=>{if(document.querySelector(".gui-settings-icon-panel")===null){document.body.insertAdjacentHTML("beforeend",`\n <div class='gui-settings-icon-panel icons-enabled'>\n <div class='gui-settings-widgets' title='附加功能'>\n <i class="icon-widgets"></i>\n </div>\n <div class='gui-settings' title='设置'>\n <i class="icon-settings"></i>\n </div>\n </div>`);document.querySelector(".gui-settings").addEventListener("click",e=>{if(e.shiftKey===false){document.querySelectorAll(".gui-settings-box,.gui-settings-mask").forEach(e=>e.classList.add("opened"))}else{document.querySelectorAll(".bilibili-evolved-about,.gui-settings-mask").forEach(e=>e.classList.add("opened"))}});document.querySelector(".gui-settings-widgets").addEventListener("click",()=>{document.querySelectorAll(".gui-settings-widgets-box,.gui-settings-mask").forEach(e=>e.classList.add("opened"))})}}})();
|
||||
|
||||
2
min/screenshot.min.css
vendored
2
min/screenshot.min.css
vendored
@ -1 +1 @@
|
||||
.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-list,.video-screenshot-list *{transition:.2s ease-out}.video-screenshot-list{position:absolute;top:0;right:0;z-index:20000;padding:12px 0;pointer-events:none}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(-240px)}.video-screenshot-list-leave-to{opacity:0;transform:translateX(240px)}.video-screenshot-thumbnail img{max-width:240px;display:block;background-color:#000}.video-screenshot-thumbnail{margin:12px 24px;position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:240px;height:135px;background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}
|
||||
.video-take-screenshot{padding:0 6px 0 18px;height:100%;cursor:pointer}.video-take-screenshot span{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.video-take-screenshot i{font-size:20px;color:#fff;transform:scale(1);opacity:.9;transition:.4s cubic-bezier(.18,.89,.32,1.28)}.video-take-screenshot:hover i{transform:scale(1.05);opacity:1}.video-take-screenshot:active i{transform:scale(.95);opacity:1}.video-screenshot-container{position:relative;--screenshot-width:240px;--screenshot-width-negative:calc(0px - var(--screenshot-width));--screenshot-height:135px;--thumbnail-margin-vertical:12px;--thumbnail-margin-horizontal:24px;--screenshot-list-width:calc(2 * var(--thumbnail-margin-horizontal) + var(--screenshot-width))}.video-screenshot-batch{position:fixed;bottom:0;right:0;z-index:20000;display:flex;width:var(--screenshot-list-width);align-items:center;justify-content:space-evenly}.video-screenshot-batch button{background:#000c;color:#fff;border:none;border-radius:10px 10px 0 0;font-size:12pt;cursor:pointer;outline:0!important;padding:8px 12px;display:flex;justify-content:center;align-items:center}.video-screenshot-batch button i{font-size:14pt;margin-right:4px}.video-screenshot-container,.video-screenshot-container *{transition:.2s ease-out}.video-screenshot-list{position:fixed;top:0;right:0;z-index:20000;padding:var(--thumbnail-margin-vertical) 0;pointer-events:none;height:calc(100% - 2 * var(--thumbnail-margin-vertical) - 48px);width:var(--screenshot-list-width);overflow:auto}.video-screenshot-list *{pointer-events:initial}.video-screenshot-list-enter{opacity:0;transform:translateX(var(--screenshot-width-negative))}.video-screenshot-list-leave-to{opacity:0;transform:translateX(var(--screenshot-width))}.video-screenshot-thumbnail img{max-width:var(--screenshot-width);display:block;background-color:#000}.video-screenshot-thumbnail{margin:var(--thumbnail-margin-vertical) var(--thumbnail-margin-horizontal);position:relative;transition:.35s cubic-bezier(.18,.89,.32,1.28);width:var(--screenshot-width);height:var(--screenshot-height);background-color:#000}@keyframes spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.video-screenshot-thumbnail .loading::before{content:"";box-sizing:border-box;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0);width:24px;height:24px;border-radius:50%;border:3px solid #8888;border-top-color:var(--theme-color);animation:.6s linear infinite spinner}.video-screenshot-thumbnail.video-screenshot-list-leave-active{position:absolute;transition:.35s cubic-bezier(.6,-.28,.74,.05)}.video-screenshot-thumbnail .mask{position:absolute;opacity:0;top:0;left:0;width:100%;height:100%;background:#0008;display:flex;justify-content:space-around;align-items:center;transition:none}.video-screenshot-thumbnail:hover .mask{opacity:1}.video-screenshot-thumbnail .mask button{background:#000a;color:#fff;border:none;border-radius:50%;font-size:24pt;cursor:pointer;width:48px;height:48px;outline:0!important}
|
||||
2
min/screenshot.min.js
vendored
2
min/screenshot.min.js
vendored
@ -1 +1 @@
|
||||
(()=>{return(e,t)=>{const{getFriendlyTitle:i}=t.import("title");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){const e=document.createElement("canvas");e.width=this.video.videoWidth;e.height=this.video.videoHeight;const t=e.getContext("2d");if(t===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}t.drawImage(this.video,0,0);e.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.url=URL.createObjectURL(e)},"image/png")}get filename(){return i()+" @"+this.time.toString()+".png"}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const s=e=>{const t=document.createElement("canvas");t.width=e.videoWidth;t.height=e.videoHeight;const i=e.currentTime;return new n(e,i)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <transition-group class="video-screenshot-list" name="video-screenshot-list">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a v-bind:href="objectUrl" v-bind:download="filename" title="保存">\n <button class="save"><i class="mdi mdi-content-save-outline"></i></button>\n </a>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")}}});const o=new Vue({el:".video-screenshot-list",data:{screenshots:[]},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const i=document.querySelector(".video-take-screenshot");if(i===null){return}i.addEventListener("click",()=>{const t=s(e);o.screenshots.push(t)})});return{export:{takeScreenshot:s,screenShotsList:o}}}})();
|
||||
(()=>{return(e,t)=>{const{getFriendlyTitle:s}=t.import("title");const i=document.createElement("canvas");class n{constructor(e,t){this.url="";this.timeStamp=(new Date).getTime();this.video=e;this.time=t;this.createUrl()}async createUrl(){i.width=this.video.videoWidth;i.height=this.video.videoHeight;const e=i.getContext("2d");if(e===null){throw new Error("视频截图失败: canvas 未创建或创建失败.")}e.drawImage(this.video,0,0);i.toBlob(e=>{if(e===null){throw new Error("视频截图失败: 创建 blob 失败.")}this.blob=e;this.url=URL.createObjectURL(e)},"image/png")}get filename(){return`${s()} @${this.time.toString()}:${this.timeStamp.toString()}.png`}get id(){return this.time.toString()+this.timeStamp.toString()}revoke(){URL.revokeObjectURL(this.url)}}const o=e=>{const t=e.currentTime;return new n(e,t)};t.applyStyle("videoScreenshotStyle");document.body.insertAdjacentHTML("beforeend",`\n <div class="video-screenshot-container">\n <transition-group class="video-screenshot-list" name="video-screenshot-list" tag="div">\n <video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>\n </transition-group>\n <div v-show="showBatch" class="video-screenshot-batch">\n <a class="batch-link" style="display:none" v-bind:download="batchFilename"></a>\n <button v-on:click="saveAll">\n <i class="mdi mdi-content-save"></i>全部保存\n </button>\n <button v-on:click="discardAll">\n <i class="mdi mdi-delete-forever"></i>全部丢弃\n </button>\n </div>\n </div>\n`);Vue.component("video-screenshot",{props:{objectUrl:String,filename:String},template:`\n <div class="video-screenshot-thumbnail">\n <img v-if="objectUrl" v-bind:src="objectUrl">\n <div class="mask" v-if="objectUrl">\n <a class="link" style="display:none" v-bind:href="objectUrl" v-bind:download="filename"></a>\n <button v-on:click="save" class="save" title="保存"><i class="mdi mdi-content-save-outline"></i></button>\n <button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>\n </div>\n <div class="loading" v-else>\n </div>\n </div>`,methods:{discard(){this.$emit("discard")},save(){this.$el.querySelector(".link").click();this.discard()}}});const r=new Vue({el:".video-screenshot-container",data:{screenshots:[],batchFilename:s()+".zip"},methods:{discard(e){this.screenshots.splice(this.screenshots.indexOf(e),1);e.revoke()},async saveAll(){const e=new JSZip;this.screenshots.forEach(t=>{e.file(t.filename,t.blob)});const t=await e.generateAsync({type:"blob"});const s=this.$el.querySelector(".batch-link");s.href=URL.createObjectURL(t);s.click();URL.revokeObjectURL(s.href);s.href=""},discardAll(){this.screenshots.forEach(e=>e.revoke());this.screenshots=[]}},computed:{showBatch(){return this.screenshots.length>=2}}});Observer.videoChange(async()=>{const e=await SpinQuery.select("#bofqi video");const t=await SpinQuery.select(".bilibili-player-video-time");if(e===null||t===null||document.querySelector(".video-take-screenshot")){return}t.insertAdjacentHTML("afterend",`\n <div class="video-take-screenshot" title="截图">\n <span><i class="mdi mdi-camera"></i></span>\n </div>`);const s=document.querySelector(".video-take-screenshot");if(s===null){return}s.addEventListener("click",()=>{const t=o(e);r.screenshots.unshift(t)})});return{export:{takeScreenshot:o,screenShotsList:r}}}})();
|
||||
@ -31,19 +31,63 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.video-screenshot-list,
|
||||
.video-screenshot-list *
|
||||
|
||||
.video-screenshot-container
|
||||
{
|
||||
position: relative;
|
||||
--screenshot-width: 240px;
|
||||
--screenshot-width-negative: calc(0px - var(--screenshot-width));
|
||||
--screenshot-height: 135px;
|
||||
--thumbnail-margin-vertical: 12px;
|
||||
--thumbnail-margin-horizontal: 24px;
|
||||
--screenshot-list-width: calc(2 * var(--thumbnail-margin-horizontal) + var(--screenshot-width));
|
||||
}
|
||||
.video-screenshot-batch
|
||||
{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 20000;
|
||||
display: flex;
|
||||
width: var(--screenshot-list-width);
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.video-screenshot-batch button
|
||||
{
|
||||
background: #000c;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px 10px 0 0;
|
||||
font-size: 12pt;
|
||||
cursor: pointer;
|
||||
outline: 0!important;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.video-screenshot-batch button i
|
||||
{
|
||||
font-size: 14pt;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.video-screenshot-container,
|
||||
.video-screenshot-container *
|
||||
{
|
||||
transition: all .2s ease-out;
|
||||
}
|
||||
.video-screenshot-list
|
||||
{
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 20000;
|
||||
padding: 12px 0;
|
||||
padding: var(--thumbnail-margin-vertical) 0;
|
||||
pointer-events: none;
|
||||
height: calc(100% - 2 * var(--thumbnail-margin-vertical) - 48px);
|
||||
width: var(--screenshot-list-width);
|
||||
overflow: auto;
|
||||
}
|
||||
.video-screenshot-list *
|
||||
{
|
||||
@ -52,26 +96,26 @@
|
||||
.video-screenshot-list-enter
|
||||
{
|
||||
opacity: 0;
|
||||
transform: translateX(-240px);
|
||||
transform: translateX(var(--screenshot-width-negative));
|
||||
}
|
||||
.video-screenshot-list-leave-to
|
||||
{
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
transform: translateX(var(--screenshot-width));
|
||||
}
|
||||
.video-screenshot-thumbnail img
|
||||
{
|
||||
max-width: 240px;
|
||||
max-width: var(--screenshot-width);
|
||||
display: block;
|
||||
background-color: black;
|
||||
}
|
||||
.video-screenshot-thumbnail
|
||||
{
|
||||
margin: 12px 24px;
|
||||
margin: var(--thumbnail-margin-vertical) var(--thumbnail-margin-horizontal);
|
||||
position: relative;
|
||||
transition: .35s cubic-bezier(0.18, 0.89, 0.32, 1.28);
|
||||
width: 240px;
|
||||
height: 135px;
|
||||
width: var(--screenshot-width);
|
||||
height: var(--screenshot-height);
|
||||
background-color: black;
|
||||
}
|
||||
@keyframes spinner
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { getFriendlyTitle } from '../title';
|
||||
// let canvas: HTMLCanvasElement | null = null;
|
||||
// let context: CanvasRenderingContext2D | null = null;
|
||||
const canvas = document.createElement("canvas");
|
||||
class Screenshot {
|
||||
constructor(video, time) {
|
||||
this.url = "";
|
||||
@ -11,7 +10,6 @@ class Screenshot {
|
||||
this.createUrl();
|
||||
}
|
||||
async createUrl() {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = this.video.videoWidth;
|
||||
canvas.height = this.video.videoHeight;
|
||||
const context = canvas.getContext("2d");
|
||||
@ -23,11 +21,12 @@ class Screenshot {
|
||||
if (blob === null) {
|
||||
throw new Error("视频截图失败: 创建 blob 失败.");
|
||||
}
|
||||
this.blob = blob;
|
||||
this.url = URL.createObjectURL(blob);
|
||||
}, "image/png");
|
||||
}
|
||||
get filename() {
|
||||
return getFriendlyTitle() + " @" + this.time.toString() + ".png";
|
||||
return `${getFriendlyTitle()} @${this.time.toString()}:${this.timeStamp.toString()}.png`;
|
||||
}
|
||||
get id() {
|
||||
return this.time.toString() + this.timeStamp.toString();
|
||||
@ -37,16 +36,6 @@ class Screenshot {
|
||||
}
|
||||
}
|
||||
export const takeScreenshot = (video) => {
|
||||
// if (canvas === null || context === null)
|
||||
// {
|
||||
// canvas = document.createElement("canvas");
|
||||
// canvas.width = video.videoWidth;
|
||||
// canvas.height = video.videoHeight;
|
||||
// context = canvas.getContext("2d");
|
||||
// }
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
const time = video.currentTime;
|
||||
return new Screenshot(video, time);
|
||||
// return new Promise<Screenshot>((resolve, reject) =>
|
||||
@ -71,9 +60,20 @@ export const takeScreenshot = (video) => {
|
||||
};
|
||||
resources.applyStyle("videoScreenshotStyle");
|
||||
document.body.insertAdjacentHTML("beforeend", /*html*/ `
|
||||
<transition-group class="video-screenshot-list" name="video-screenshot-list">
|
||||
<video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>
|
||||
</transition-group>
|
||||
<div class="video-screenshot-container">
|
||||
<transition-group class="video-screenshot-list" name="video-screenshot-list" tag="div">
|
||||
<video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>
|
||||
</transition-group>
|
||||
<div v-show="showBatch" class="video-screenshot-batch">
|
||||
<a class="batch-link" style="display:none" v-bind:download="batchFilename"></a>
|
||||
<button v-on:click="saveAll">
|
||||
<i class="mdi mdi-content-save"></i>全部保存
|
||||
</button>
|
||||
<button v-on:click="discardAll">
|
||||
<i class="mdi mdi-delete-forever"></i>全部丢弃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
Vue.component("video-screenshot", {
|
||||
props: {
|
||||
@ -84,9 +84,8 @@ Vue.component("video-screenshot", {
|
||||
<div class="video-screenshot-thumbnail">
|
||||
<img v-if="objectUrl" v-bind:src="objectUrl">
|
||||
<div class="mask" v-if="objectUrl">
|
||||
<a v-bind:href="objectUrl" v-bind:download="filename" title="保存">
|
||||
<button class="save"><i class="mdi mdi-content-save-outline"></i></button>
|
||||
</a>
|
||||
<a class="link" style="display:none" v-bind:href="objectUrl" v-bind:download="filename"></a>
|
||||
<button v-on:click="save" class="save" title="保存"><i class="mdi mdi-content-save-outline"></i></button>
|
||||
<button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>
|
||||
</div>
|
||||
<div class="loading" v-else>
|
||||
@ -96,19 +95,45 @@ Vue.component("video-screenshot", {
|
||||
discard() {
|
||||
this.$emit("discard");
|
||||
},
|
||||
save() {
|
||||
this.$el.querySelector(".link").click();
|
||||
this.discard();
|
||||
},
|
||||
},
|
||||
});
|
||||
const screenShotsList = new Vue({
|
||||
el: ".video-screenshot-list",
|
||||
el: ".video-screenshot-container",
|
||||
data: {
|
||||
screenshots: [],
|
||||
batchFilename: getFriendlyTitle() + ".zip",
|
||||
},
|
||||
methods: {
|
||||
discard(screenshot) {
|
||||
this.screenshots.splice(this.screenshots.indexOf(screenshot), 1);
|
||||
screenshot.revoke();
|
||||
}
|
||||
}
|
||||
},
|
||||
async saveAll() {
|
||||
const zip = new JSZip();
|
||||
this.screenshots.forEach((it) => {
|
||||
zip.file(it.filename, it.blob);
|
||||
});
|
||||
const blob = await zip.generateAsync({ type: "blob" });
|
||||
const link = this.$el.querySelector(".batch-link");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
link.href = "";
|
||||
},
|
||||
discardAll() {
|
||||
this.screenshots.forEach((it) => it.revoke());
|
||||
this.screenshots = [];
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showBatch() {
|
||||
return this.screenshots.length >= 2;
|
||||
},
|
||||
},
|
||||
});
|
||||
Observer.videoChange(async () => {
|
||||
const video = await SpinQuery.select("#bofqi video");
|
||||
@ -126,7 +151,7 @@ Observer.videoChange(async () => {
|
||||
}
|
||||
screenshotButton.addEventListener("click", () => {
|
||||
const screenshot = takeScreenshot(video);
|
||||
screenShotsList.screenshots.push(screenshot);
|
||||
screenShotsList.screenshots.unshift(screenshot);
|
||||
});
|
||||
});
|
||||
export default {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { getFriendlyTitle } from '../title';
|
||||
|
||||
// let canvas: HTMLCanvasElement | null = null;
|
||||
// let context: CanvasRenderingContext2D | null = null;
|
||||
const canvas = document.createElement("canvas");
|
||||
class Screenshot
|
||||
{
|
||||
video: HTMLVideoElement;
|
||||
url = "";
|
||||
time: number;
|
||||
blob: Blob;
|
||||
private timeStamp = new Date().getTime();
|
||||
constructor(video: HTMLVideoElement, time: number)
|
||||
{
|
||||
@ -17,7 +17,6 @@ class Screenshot
|
||||
}
|
||||
async createUrl()
|
||||
{
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = this.video.videoWidth;
|
||||
canvas.height = this.video.videoHeight;
|
||||
const context = canvas.getContext("2d");
|
||||
@ -32,12 +31,13 @@ class Screenshot
|
||||
{
|
||||
throw new Error("视频截图失败: 创建 blob 失败.");
|
||||
}
|
||||
this.blob = blob;
|
||||
this.url = URL.createObjectURL(blob);
|
||||
}, "image/png");
|
||||
}
|
||||
get filename()
|
||||
{
|
||||
return getFriendlyTitle() + " @" + this.time.toString() + ".png";
|
||||
return `${getFriendlyTitle()} @${this.time.toString()}:${this.timeStamp.toString()}.png`;
|
||||
}
|
||||
get id()
|
||||
{
|
||||
@ -50,16 +50,6 @@ class Screenshot
|
||||
}
|
||||
export const takeScreenshot = (video: HTMLVideoElement) =>
|
||||
{
|
||||
// if (canvas === null || context === null)
|
||||
// {
|
||||
// canvas = document.createElement("canvas");
|
||||
// canvas.width = video.videoWidth;
|
||||
// canvas.height = video.videoHeight;
|
||||
// context = canvas.getContext("2d");
|
||||
// }
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
const time = video.currentTime;
|
||||
return new Screenshot(video, time);
|
||||
// return new Promise<Screenshot>((resolve, reject) =>
|
||||
@ -84,9 +74,20 @@ export const takeScreenshot = (video: HTMLVideoElement) =>
|
||||
}
|
||||
resources.applyStyle("videoScreenshotStyle");
|
||||
document.body.insertAdjacentHTML("beforeend", /*html*/`
|
||||
<transition-group class="video-screenshot-list" name="video-screenshot-list">
|
||||
<video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>
|
||||
</transition-group>
|
||||
<div class="video-screenshot-container">
|
||||
<transition-group class="video-screenshot-list" name="video-screenshot-list" tag="div">
|
||||
<video-screenshot v-for="screenshot of screenshots" v-bind:filename="screenshot.filename" v-bind:object-url="screenshot.url" v-on:discard="discard(screenshot)" v-bind:key="screenshot.id"></video-screenshot>
|
||||
</transition-group>
|
||||
<div v-show="showBatch" class="video-screenshot-batch">
|
||||
<a class="batch-link" style="display:none" v-bind:download="batchFilename"></a>
|
||||
<button v-on:click="saveAll">
|
||||
<i class="mdi mdi-content-save"></i>全部保存
|
||||
</button>
|
||||
<button v-on:click="discardAll">
|
||||
<i class="mdi mdi-delete-forever"></i>全部丢弃
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
Vue.component("video-screenshot", {
|
||||
props: {
|
||||
@ -97,9 +98,8 @@ Vue.component("video-screenshot", {
|
||||
<div class="video-screenshot-thumbnail">
|
||||
<img v-if="objectUrl" v-bind:src="objectUrl">
|
||||
<div class="mask" v-if="objectUrl">
|
||||
<a v-bind:href="objectUrl" v-bind:download="filename" title="保存">
|
||||
<button class="save"><i class="mdi mdi-content-save-outline"></i></button>
|
||||
</a>
|
||||
<a class="link" style="display:none" v-bind:href="objectUrl" v-bind:download="filename"></a>
|
||||
<button v-on:click="save" class="save" title="保存"><i class="mdi mdi-content-save-outline"></i></button>
|
||||
<button v-on:click="discard" title="丢弃" class="discard"><i class="mdi mdi-delete-forever-outline"></i></button>
|
||||
</div>
|
||||
<div class="loading" v-else>
|
||||
@ -110,20 +110,51 @@ Vue.component("video-screenshot", {
|
||||
{
|
||||
this.$emit("discard");
|
||||
},
|
||||
save()
|
||||
{
|
||||
this.$el.querySelector(".link").click();
|
||||
this.discard();
|
||||
},
|
||||
},
|
||||
});
|
||||
const screenShotsList = new Vue({
|
||||
el: ".video-screenshot-list",
|
||||
el: ".video-screenshot-container",
|
||||
data: {
|
||||
screenshots: [] as Screenshot[],
|
||||
batchFilename: getFriendlyTitle() + ".zip",
|
||||
},
|
||||
methods: {
|
||||
discard(screenshot: Screenshot)
|
||||
{
|
||||
this.screenshots.splice(this.screenshots.indexOf(screenshot), 1);
|
||||
screenshot.revoke();
|
||||
}
|
||||
}
|
||||
},
|
||||
async saveAll()
|
||||
{
|
||||
const zip = new JSZip();
|
||||
this.screenshots.forEach((it: Screenshot) =>
|
||||
{
|
||||
zip.file(it.filename, it.blob);
|
||||
});
|
||||
const blob = await zip.generateAsync({ type: "blob" });
|
||||
const link = this.$el.querySelector(".batch-link");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
link.href = "";
|
||||
},
|
||||
discardAll()
|
||||
{
|
||||
this.screenshots.forEach((it: Screenshot) => it.revoke());
|
||||
this.screenshots = [];
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showBatch()
|
||||
{
|
||||
return this.screenshots.length >= 2;
|
||||
},
|
||||
},
|
||||
});
|
||||
Observer.videoChange(async () =>
|
||||
{
|
||||
@ -145,7 +176,7 @@ Observer.videoChange(async () =>
|
||||
screenshotButton.addEventListener("click", () =>
|
||||
{
|
||||
const screenshot = takeScreenshot(video);
|
||||
screenShotsList.screenshots.push(screenshot);
|
||||
screenShotsList.screenshots.unshift(screenshot);
|
||||
});
|
||||
});
|
||||
export default {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user