您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Xueersi enhancement | For the old days of Xueersi.
// ==UserScript== // @name Xueersi Aurora // @name:zh-CN 学而思极光 // @namespace XueersiAurora // @version 1.0.3 // @description Xueersi enhancement | For the old days of Xueersi. // @description:zh-CN 学而思增强工具 | 谨以此献给学而思最好的那段时光, 以及我们失去的一切。 // @license AGPL-3.0-only // @author FurryR // @match https://code.xueersi.com/* // @icon https://static0.xesimg.com/platform-fe/website/home/favicon.ico // @grant none // @run-at document-start // ==/UserScript== ;(async () => { 'use strict' /** * @typedef {{ new(tag?: string, data?: object, children?: object[], text?: string, elm?: unknown, context?: unknown, componentOptions?: object, asyncFactory?: Function): object }} VElement */ const logger = Object.assign({}, console) if (!window.location.hostname.includes('xueersi.com')) { logger.log(`Aurora 将不在 ${window.location.href} 内运行。`) return } if (!console.log.toString().includes('[native code]')) { alert( '看起来 Xueersi Aurora 加载得太慢了... 别担心,我们可以轻松解决这个问题。\n1. 转到 Tampermonkey 设置。\n2. 将“配置模式”改为“高级”。\n3. 转到“实验”,将“注入模式”改为“即时”。\n4. 刷新页面。\n\n在您进行这些操作前,Xueersi Aurora 的部分或全部功能不会生效。' ) } // [审查用]取得直链 function getScratchlink(id, version, type) { let ret = '' if (type == 'scratch') { ret = `https://code.xueersi.com/scratch/index.html?pid=${id}&version=${version}&env=community` if (version == '2.0') { if (id.includes('8080')) { ret = `http://dev-code.xueersi.com/scratch/index.html?pid=${id}&version=${version}&env=community` } } else { if (id.includes('8080')) { ret = `http://dev-code.xueersi.com/scratch3/index.html?pid=${id}&version=${version}&env=community` } else { ret = `https://code.xueersi.com/scratch3/index.html?pid=${id}&version=${version}&env=community` } } } else if (type == 'cpp' || type == 'webpy' || type == 'python') { ret = `https://code.xueersi.com/ide/code/${id}` } return ret } // xterm-original from xtermjs.org const xterm_theme = { foreground: '#F8F8F8', background: '#2D2E2C', selection: '#5DA5D533', black: '#1E1E1D', brightBlack: '#262625', red: '#CE5C5C', brightRed: '#FF7272', green: '#5BCC5B', brightGreen: '#72FF72', yellow: '#CCCC5B', brightYellow: '#FFFF72', blue: '#5D5DD3', brightBlue: '#7279FF', magenta: '#BC5ED1', brightMagenta: '#E572FF', cyan: '#5DA5D5', brightCyan: '#72F0FF', white: '#F8F8F8', brightWhite: '#FFFFFF' } const CODEFONT_CSS = 'color: white; font-family: "Jetbrains Mono", "Fira Code", Consolas, "Courier New", monospace' /** * @template T * @template {keyof T} Key * @param {T} obj * @param {Key} p * @param {(fn: T[Key]) => T[Key]} fn */ function patch(obj, p, fn) { if (obj[p]) obj[p] = fn(obj[p]) } /** * 获得默认配置。 * @returns 默认配置。 */ function default_config() { return { disabled: [], option: {} } } let Aurora_config = default_config() class PluginManager { /** * 获得名为 id 的设置,若找不到则以 value 顶替。 * @param {id} id 设置 id。 * @param {any} value 顶替的值。 * @returns {any} 设置的值。 */ get_option_or(id, value) { if (Aurora_config.option[id] !== undefined) { return Aurora_config.option[id] } Aurora_config.option[id] = value window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) return value } /** * 重设 ID 为 id 的设置为指定值。 * @param {string} id 设定 id。 * @param {any} value 设定的值。 */ set_option(id, value) { Aurora_config.option[id] = value window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) } /** * 注册(不可用)功能。 * @param {string} id 功能的 ID。 * @param {string} description 功能的人类可读描述。 * @param {Function} fn 功能本体函数。 */ plug(id, description, fn) { this._events[id] = [description, fn] } /** * 完成注册(不可用),开始加载 Aurora。 */ done() { const v = Aurora_config.disabled if (!v instanceof Array) { logger.error('Aurora 加载失败。请检查配置文件是否设置正确。') return } for (const [key, value] of Object.entries(this._events)) { if (!v.includes(key)) { if (window === window.top) logger.log(`Aurora 正在加载功能 %c${key}%c。`, CODEFONT_CSS, '') value[1]() } } } constructor() { this._events = {} } } class VueElementMixin { constructor() { this._events = new Map() } on(tagName, fn) { const v = this._events.get(tagName) if (v) v.push(fn) else this._events.set(tagName, [fn]) } emit(instance) { const tag = instance.$vnode?.componentOptions?.tag ?? instance._vnode?.componentOptions?.tag ?? instance.$vnode?.tag?.split('-')?.at(-1) if (this._events.has(tag)) { for (const v of this._events.get(tag)) { try { v(instance) } catch (e) { logger.error( `在为 %c${tag}%c 组件进行补丁时发生了错误。这看起来是一个漏洞,请报告给 Aurora 开发者。`, CODEFONT_CSS, '' ) logger.error(e) } } } } } const webpackListener = [] const plug = new PluginManager() window.Aurora = { /** * 获得帮助。 * @param {string | undefined} 帮助 ID。不填则查看全部。 */ help(obj = undefined) { if (obj == undefined) { logger.log('Aurora 帮助') logger.log('所有条目:') logger.table({ enable_plugin: '关于启用/关闭功能的方法', option: '关于设置', development: '关于如何开发功能' }) logger.log( '请输入 %clogger.log("条目 ID")%c 来查看相应条目的帮助。', CODEFONT_CSS, '' ) } else if (obj == 'enable_plugin') { logger.log( '以下是 Aurora 对象中 %cenable/disable%c 方法参数的说明。', CODEFONT_CSS, '' ) logger.table({ id: '可选,需启用/禁用功能的 ID。若不指定,则会打印目前启用/禁用的全部功能。' }) } else if (obj == 'option') { logger.log( '%cAurora.option%c 可以获取/更改设置内容。', CODEFONT_CSS, '' ) logger.log('以下是 Aurora 对象中 option 方法参数的说明。') logger.table({ id: '可选,需设定的设置 id。不指定时,打印全部设置。', value: '可选,需设定的设置值。不指定时,返回这个 id 的设置。' }) } else if (obj == 'development') { logger.log('%cplug.plug%c 可以注册(不可用)一个功能。', CODEFONT_CSS, '') logger.log('以下是 plug 对象中 plug 方法参数的说明。') logger.table({ id: '功能的 ID。', description: '功能的人类可读描述。', fn: '功能本体函数。' }) } else { logger.log('无法找到条目。请确认您的拼写。') } }, /** * 禁用功能。当没有参数时,查看已经禁用的功能列表。 * @param {string[]} args 禁用的功能 ID。 */ disable(...args) { if (args.length == 0) { { const v = Aurora_config.disabled, v2 = {} if (!v instanceof Array) { logger.error('Aurora 加载失败。请检查配置文件是否设置正确。') return } if (v.length != 0) { logger.log('已经禁用的功能:') for (const key of v) { v2[key] = plug._events[key][0] } logger.table(v2) } else { logger.log('尚未禁用任何功能。') } } return } for (const id of args) { if (!Aurora_config.disabled.includes(id)) { Aurora_config.disabled.push(id) } window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) logger.log(`Aurora 已经禁用了 %c${id}%c。`, CODEFONT_CSS, '') } logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '') setTimeout(() => window.location.reload(), 3000) }, /** * 启用功能。 * @param {string[]} args 启用的功能 ID。 */ enable(...args) { if (args.length == 0) { { const v = Aurora_config.disabled, v2 = {} if (!v instanceof Array) { logger.error('Aurora 加载失败。请检查配置文件是否设置正确。') return } if (v.length != Object.keys(plug._events).length) { logger.log('已经启用的功能:') for (const [key, value] of Object.entries(plug._events)) { if (!v.includes(key)) { v2[key] = value[0] } } logger.table(v2) } else { logger.log('尚未启用任何功能。') } } return } for (const id of args) { Aurora_config.disabled = Aurora_config.disabled.filter(val => val != id) window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) logger.log(`Aurora 已经启用了 %c${id}%c。`, CODEFONT_CSS, '') } logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '') setTimeout(() => window.location.reload(), 3000) }, /** * 获得 Aurora 设置。 * @param {string | undefined} id 设置的 ID。 * @param {string | undefined} value 设置的值。 */ option(...args) { if (args.length == 0) { logger.log('Aurora 设置:') logger.table(Aurora_config.option) return } if (args.length == 1) { return Aurora_config.option[args[0]] } Aurora_config.option[args[0]] = args[1] window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) logger.log( `Aurora 已经设置了 %c${args[0]}%c 为 %c${args[1]}%c。`, CODEFONT_CSS, '', CODEFONT_CSS, '' ) logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '') setTimeout(() => window.location.reload(), 3000) } } function requireVue(callback) { let captured = false patch(Function.prototype, 'call', call => { return function (self, ...args) { if ( args.length === 3 && typeof args[0] === 'object' && args[0] !== null && typeof args[1] === 'object' && args[1] !== null && typeof args[2] === 'function' && args[0].exports ) { const fn = this // const require = args[2] const str = fn.toString() if (str.includes('ENABLE_XES_CONSOLE')) { return } const res = call.apply(this, [self, ...args]) const exports = args[0].exports if (!exports) return res webpackListener.forEach(v => v(exports)) if ( typeof exports.default === 'function' && typeof exports.default.version === 'string' && !captured ) { // This is vue. captured = true callback(self.default) } return res } else return call.apply(this, [self, ...args]) } }) } function addStyle(css) { if (css instanceof URL) { const style = document.createElement('link') style.rel = 'stylesheet' style.href = css.toString() document.documentElement.appendChild(style) } else { const style = document.createElement('style') style.textContent = css document.documentElement.appendChild(style) } } { if (window === window.top) logger.log('Aurora 正在加载配置。') let v = window.localStorage.getItem('Aurora') if (v == null) { window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) } else { try { Aurora_config = JSON.parse(v) } catch (_) { window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config)) } } } const vueMixinManager = new VueElementMixin() requireVue(Vue => { patch(Vue.prototype, '_init', _init => { return function (args) { _init.call(this, args) vueMixinManager.emit(this) } }) }) plug.plug('privacy', '去除一些让开发者伤心的东西。', () => { let loaded = false // Universal (Vue.js / React) patch webpackListener.push(exports => { if ( typeof exports === 'function' && exports.default === exports && typeof exports.Axios === 'function' && !loaded ) { loaded = true exports.interceptors.request.use(function (config) { if (config.url.startsWith('/log')) { config.baseURL = '' config.url = 'data:application/json,{}' } return config }) } }) sessionStorage.setItem('debugger', 'true') Object.defineProperty(window, 'logger', { get() { return new Proxy( {}, { get() { return () => {} }, set() { return true } } ) }, set() {} }) Object.defineProperty(window, 'xesWeb_eventLog', { get() { return new Proxy( {}, { get() { return () => {} }, set() { return true } } ) }, set() {} }) Object.defineProperty(window, 'XesLoggerSDK', { value: function () {}, writable: false }) }) plug.plug('sudo', '非唯C者也是人。', () => { vueMixinManager.on('109', instance => { instance.fnHandlePermission = () => true }) }) plug.plug('operate', '作品操作优化。', () => { vueMixinManager.on('109', instance => { if (instance.projectData) { instance.onAdaptClick = () => { instance.openNewTab( getScratchlink( instance.projectId, instance.version ?? instance.projectData.version, instance.$route.query.langType ) ) } patch(instance.$options, 'render', render => { return function (createElement) { this.projectData.hidden_code = 2 const ret = render.call(this, createElement) const operateLeft = ret.componentOptions.children[1].children[1].children[1] .children[0].children[1].children[0] const adapt = operateLeft.children[5] adapt.children[1].text = ' 审查 ' return ret } }) } }) }) plug.plug('unlimited', '解除一些长度限制。', () => { if (window.location.pathname.startsWith('/space/')) { vueMixinManager.on('homepages', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) if (this.contenteditable) { const signature = res.children[0].componentOptions.children[1].children[2] .children[0] delete signature.data.attrs.maxlength } return res } }) }) } else if (window.location.pathname.startsWith('/ide/code')) { vueMixinManager.on('HeaderBase', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) if (this.canEditName) { const input = res.children[0].children[1].children[0] delete input.data.attrs.maxlength } return res } }) }) } else if (window.location.pathname === '/project/publish/modal') { addStyle(` .publish .publish_main_warp .publish_main .publish_detail .icon-edit { font-size: 14px; color: rgba(53,53,53,.3); cursor: pointer } `) vueMixinManager.on('Publish', instance => { // 允许上传非指定尺寸/格式及大小超过 5 MiB 的图像 instance.handleFileChange = async () => { instance.handleUpload(instance.$refs.input.files[0]) } instance.inputHandle = () => { instance.descriptionLength = instance.description.length } instance.$data.textareaPlaceholder = '在这里介绍你的作品...' instance.$data.tagPlacerholder = '勾选作品的标签,你也可以点击右侧按钮来添加标签。' instance.toSelectedTag = tag => { if (instance.selectedTagList.includes(tag)) { const tagIndex = instance.selectedTagList.findIndex( selectedTag => selectedTag === tag ) instance.selectedTagList.splice(tagIndex, 1) } else instance.selectedTagList.push(tag) } instance.fnPublish = async () => { const t = instance // From Xueersi obfuscated code function getMd5PythonLibs(e) { return instance.axios.get(`${e}?_v=${Date.now()}`).catch(() => { logger.log('获取文件失败。') }) } function processAsset(t) { const e = {} function n(t, r) { t.forEach(function (t) { t.isLeaf && 'html' === t.lang && (e['' + r + t.name] = t), !t.isLeaf && t.children && t.children.length > 0 && n(t.children, '' + r + t.name + '/') }) } return n(t, ''), e } t.sourceType = t.isHomework ? 'homework' : 'normal' if ('offline' !== t.version || !t.assets.assets_url) { t.publishAjax() } else { const r = await getMd5PythonLibs(t.assets.assets_url) if (r.data.treeAssets) { t.assetsHtmlMap = processAsset(r.data.treeAssets) } t.$nextTick(function () { Object.keys(t.assetsHtmlMap).length > 0 ? t.$refs.publishWebDialog.open() : t.publishAjax() }) } } instance.$options.computed.disableRadio = () => [ 'original', 'adapt', 'reprint' ] instance._computedWatchers.disableRadio.getter = instance.$options.computed.disableRadio instance._computedWatchers.disableRadio.dirty = true instance.checkSourceRadio = () => {} patch(instance, 'onRadioClick', onRadioClick => { return function (type) { if (type === 'homework') { this.isHomework = !this.isHomework this.radioHomeworkTooltipShow = this.isHomework if (this.radioHomeworkTooltipShow) { this.$refs.homeworkPoptip.show() } else { this.$refs.homeworkPoptip.hide() } } else return onRadioClick.call(this, type) } }) patch(instance.$options, 'render', render => { return function (createElement) { if (this.isHomework === undefined) { this.isHomework = this.sourceType === 'homework' } if ( !this.selectedTagList.every(tag => this.tagList.includes(tag)) ) { this.tagList = Array.from( new Set([...this.tagList, ...this.selectedTagList]) ) } const res = render.call(this, createElement) const publishBtns = res.children[0].children[1].children[1].children[8] publishBtns.children.splice(0, 1) publishBtns.data.style = { float: 'right' } const operate = res.children[0].children[1].children[0].children[3] operate.children.at(-1).data.staticStyle = { 'margin-right': '24px' } operate.children.push( createElement( 'Poptip', { ref: 'homeworkPoptip', attrs: { trigger: 'noop', offset: '-14 16', placement: 'bottom-start' } }, [ createElement('div', [ createElement('div', { class: 'publish_radio' + (this.isHomework ? ' publish_radio-checked' : ''), on: { click: e => { e.stopPropagation() this.onRadioClick('homework') this.$forceUpdate() } } }) ]), createElement( 'div', { staticClass: 'publish_radio-poptip', attrs: { slot: 'content' }, slot: 'content' }, [ createElement( 'p', { staticClass: 'publish_radio-detail' }, [ '选中此选项会导致你的作品', createElement('br'), '通常无法被看见。' ] ), createElement('div', { staticClass: 'publish_radio-close', on: { click: () => { return this.$refs.homeworkPoptip.hide() } } }) ] ) ] ), createElement('div', { staticClass: 'publish_radio-text' }, [ createElement( 'p', { staticClass: 'publish_radio-text-content' }, [ createElement('Poptip', [ ' 发布为', createElement( 'span', { style: { display: 'inline-block', lineHeight: '30px' } }, '作业' ), createElement( 'p', { attrs: { slot: 'content' }, slot: 'content' }, [ '其它人将会在随堂练习中看到这个 ', createElement('br'), '作品。' ] ) ]) ] ) ]) ) const workName = res.children[0].children[1].children[1].children[1] const textArea = res.children[0].children[1].children[1].children[7].children[0] const helpClick = res.children[0].children[1].children[1].children[5].children[0] .children[1] helpClick.data.on.click = () => { const p = prompt('请输入需要添加的标签。') if (p && !this.tagList.includes(p)) this.tagList.push(p) } helpClick.data.staticClass = 'iconfont icon-edit' helpClick.data.attrs = { title: '添加自定义标签...' } delete workName.children[0].data.attrs.maxlength delete textArea.children[0].data.attrs.maxlength textArea.children.pop() return res } }) }) } }) plug.plug('editor', '各种编辑器优化。', () => { if ( window.location.href.startsWith( 'https://code.xueersi.com/toolkit/template' ) ) { vueMixinManager.on('6', instance => { instance.closeModal() }) } else { // replace default code ;(() => { const template = plug.get_option_or('template', { cpp: `#include <iostream> int main() { // 这是 Xueersi Aurora 的 C++ 默认模板。你可以在 Aurora 设置中变更它。 std::cout << "[__AURORA_HITOKOTO__]" << std::endl; return 0; } `, python: `# 这是 Xueersi Aurora 的 Python 默认模板。你可以在 Aurora 设置中变更它。 print("[__AURORA_HITOKOTO__]") `, webpy: `# 这是 Xueersi Aurora 的 Web Python 默认模板。你可以在 Aurora 设置中变更它。 print("[__AURORA_HITOKOTO__]") `, offlinepy: `# 这是 Xueersi Aurora 的离线 Python 默认模板。你可以在 Aurora 设置中变更它。 print("[__AURORA_HITOKOTO__]") `, hw1py: `# 这是 Xueersi Aurora 的光环板 Python 默认模板。你可以在 Aurora 设置中变更它。 print("[__AURORA_HITOKOTO__]") `, hw2py: `# 这是 Xueersi Aurora 的编程掌机 Python 默认模板。你可以在 Aurora 设置中变更它。 print("[__AURORA_HITOKOTO__]") ` }) let loaded = false // Universal (Vue.js / React) patch webpackListener.push(exports => { if ( typeof exports === 'function' && exports.default === exports && typeof exports.Axios === 'function' && !loaded ) { loaded = true exports.interceptors.response.use(function (resp) { const map = { 1: 'cpp', 2: 'python', 3: 'webpy', 4: 'webpy', 5: 'hw1py', 7: 'hw2py', 28794692: 'offlinepy' } if ( resp.config.url.startsWith('/compilers/v2/') || resp.config.url.startsWith('/api/projects/v2/') || resp.config.url.startsWith('/projects/v2/') || (resp.config.url === '/community/v4/projects/detail' && window.location.pathname.startsWith('/ide/code/')) ) { if (resp.data.stat === 1) { if (resp.data.data.id in map) { const t = template[map[resp.data.data.id]] if (t.includes('[__AURORA_HITOKOTO__]')) { return fetch('https://v1.hitokoto.cn/?c=k') .then( v => { logger.log( '随机名言来自 Hitokoto,请访问 https://hitokoto.cn/ 以了解更多相关信息。' ) return v.json() }, () => ({ hitokoto: '谨以此献给学而思最好的那段时光, 以及我们失去的一切。', from_who: '凌' }) ) .then(v => { let hitokoto = '' if (!v.from_who && !v.from) hitokoto = `${v.hitokoto}` else if (!v.from_who) hitokoto = `${v.hitokoto} —— 「${v.from}」` else if (!v.from) hitokoto = `${v.hitokoto} —— ${v.from_who}` else hitokoto = `${v.hitokoto} —— ${v.from_who}「${v.from}」` resp.data.data.xml = t.replace( /\[__AURORA_HITOKOTO__\]/g, hitokoto ) return resp }) } resp.data.data.xml = template[map[resp.data.data.id]] } } } return resp }) } }) })() // no timer ;(() => { let loaded = false vueMixinManager.on('IdeEditor', instance => { instance.fnTryLockRun = () => { return true } if (window.Sk && !loaded) { loaded = true let v = Infinity Object.defineProperty(window.Sk, 'execLimit', { get: () => v, set: val => { if (val == 1) { v = 1 queueMicrotask(() => { v = Infinity }) } } }) Object.defineProperty(window.Sk, 'yieldLimit', { get: () => Infinity, set: () => {} }) } }) })() // xterm v5 addStyle( new URL( 'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/css/xterm.min.css' ) ) class Decoder { constructor() { this.bytesLeft = 0 this.codePoint = 0 this.lowerBound = 0 } decode(data) { let tmp = '' for (let idx = 0; idx < data.length; idx++) { const code = data.charCodeAt(idx) if (0 === this.bytesLeft) { if (code <= 127) tmp += data.charAt(idx) else if (192 <= code && code <= 223) { this.codePoint = code - 192 this.bytesLeft = 1 this.lowerBound = 128 } else if (224 <= code && code <= 239) { this.codePoint = code - 224 this.bytesLeft = 2 this.lowerBound = 2048 } else if (240 <= code && code <= 247) { this.codePoint = code - 240 this.bytesLeft = 3 this.lowerBound = 65536 } else if (248 <= code && code <= 251) { this.codePoint = code - 248 this.bytesLeft = 4 this.lowerBound = 2097152 } else if (252 <= code && code <= 253) { this.codePoint = code - 252 this.bytesLeft = 5 this.lowerBound = 67108864 } else tmp += '�' } else if (128 <= code && code <= 191) { this.bytesLeft-- this.codePoint = (this.codePoint << 6) + (code - 128) if (this.bytesLeft === 0) { const charCode = this.codePoint if ( charCode < this.lowerBound || (55296 <= charCode && charCode <= 57343) || charCode > 1114111 ) tmp += '�' else if (charCode < 65536) tmp += String.fromCharCode(charCode) else { charCode -= 65536 tmp += String.fromCharCode( 55296 + ((charCode >>> 10) & 1023), 56320 + (1023 & charCode) ) } } } else { tmp += '�' this.bytesLeft = 0 idx-- } } return tmp } } const decoderInstance = new Decoder() class Xterm { constructor( elem, Terminal, WebglAddon, FitAddon, Unicode11Addon, CanvasAddon, WebLinksAddon ) { this.elem = elem this.term = new Terminal({ fontSize: 15, fontFamily: '"Jetbrains Mono", "Fira Code", "Cascadia Code", "Noto Emoji", "Segoe UI Emoji", "Lucida Console", Menlo, courier-new, courier, monospace', theme: xterm_theme, cursorBlink: true, allowProposedApi: true, allowTransparency: true, cursorStyle: 'bar' }) this.term.on = () => {} this.term.setOption = (name, value) => { this.term.options[name] = value this.fit() } this.fitAddon = new FitAddon() this.term.loadAddon(this.fitAddon) try { this.term.loadAddon(new WebglAddon()) } catch { this.term.loadAddon(new CanvasAddon()) } this.term.loadAddon(new Unicode11Addon()) this.term.unicode.activeVersion = '11' this.term.loadAddon(new WebLinksAddon()) this.term.onData(e => { if (window.WebpyInputCtrl && window.WebpyInputCtrl.runByWebPy) { const n = !!window.WebpyInputCtrl && window.WebpyInputCtrl.onData(e) if (n !== false) { if (n === true) this.write(e) else this.write(n) } } }) this.term.open(elem) this.fit() window.addEventListener( 'resize', (this.resizeListener = () => this.fit()) ) this.term.focus() this.term.blur() this.resizeHandler = null this.activate = true this.decoder = decoderInstance } fit() { this.fitAddon.fit() } info() { return { columns: this.term.cols, rows: this.term.rows } } output(e) { const n = this.decode(e) this.term.write(n) // var n = this.decoder.decode(e) // t(n) || this.term.write(n) } decode(data) { return this.decoder.decode(data) } outputNoEncode(e) { this.term.write(e) } write(e) { this.term.write(e) } writeln(e) { this.term.writeln(e) } showMessage(_message, _timeout) { // Unused } removeMessage() { // Unused } setWindowTitle(title) { document.title = title } setPreferences(_options) { // Unused } onInput(callback) { this.term.onData(ev => { if (this.activate) callback(ev) }) } onResize(callback) { this.term.onResize(ev => { if (this.activate) callback(ev.cols, ev.rows) }) } deactivate() { // if (this.resizeHandler) this.resizeHandler.dispose() this.activate = false this.term.blur() } reset() { this.term.clear() this.term.reset() } close() { window.removeEventListener('resize', this.resizeListener) this.term.dispose() } } let termInstance = null vueMixinManager.on('IdeEditor', instance => { patch(instance, 'fnRunCode', fnRunCode => { return function (...args) { if (termInstance) termInstance.activate = true return fnRunCode.call(this, ...args) } }) }) const xtermDeps = Promise.all([ import('https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'), import('https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'), import('https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'), import( 'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm' ), import('https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'), import( 'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm' ) ]) vueMixinManager.on('WsTermComp', instance => { const fnInitWS = instance.fnInitWS instance.fnInitWS = async function (api) { // 移植自 XesExt v2 const xterm = document.getElementById('terminal') if (xterm && this.canInitWs) { if (!this.xterm) { xterm.style.backgroundColor = xterm.parentNode.style.backgroundColor = xterm_theme.background const [ { Terminal }, { WebglAddon }, { FitAddon }, { Unicode11Addon }, { CanvasAddon }, { WebLinksAddon } ] = await xtermDeps termInstance = this.xterm = new Xterm( xterm, Terminal, WebglAddon, FitAddon, Unicode11Addon, CanvasAddon, WebLinksAddon ) this.xterm.term.options.fontSize = Number(this.fontSize) } fnInitWS.call(this, api) } } }) // monaco editor addStyle( new URL( 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css' ) ) addStyle(` .editor, .ace-editor { padding: 0 !important; text-align: left !important; } `) window.MonacoEnvironment = { getWorkerUrl(fileName) { if (fileName === 'workerMain.js') { // fix SecurityError exception return `data:text/javascript;base64,${btoa( `(function(fetch){globalThis.fetch=function(url,...args){return fetch.call(this,'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/base/worker/'+url,...args);};})(globalThis.fetch);importScripts('https://cdn.jsdelivr.net/npm/[email protected]/min/vs/base/worker/workerMain.js');` )}` } } } const languageMap = { c_cpp: 'cpp' } class DummyAceEditor { constructor(defaultLanguage) { this.value = '' this.language = defaultLanguage } setValue(value) { this.value = value } getSession() { return { setMode: mode => { const lang = mode.substring(9) this.language = languageMap[lang] ?? lang }, setUndoManager: () => {} } } } const monacoDeps = import( 'https://cdn.jsdelivr.net/npm/[email protected]/+esm' ) vueMixinManager.on('AceEditor', instance => { instance.foldCode = instance.addCodeTips = instance.addCodeOfflineTips = () => {} instance.$options.mounted = [ async function () { const lang = this.lang ?? 'c_cpp' const dummy = new DummyAceEditor(languageMap[lang] ?? lang) this.editor = new Proxy(dummy, { get(_, p) { if (Reflect.has(dummy, p)) return Reflect.get(dummy, p) return () => {} } }) const Monaco = await monacoDeps // Based on https://github.com/zsodur/monaco-editor-copilot, MIT license const MonacoEditorCopilot = editor => { async function fetchCompletion( config, handleInsertion, WindowAI ) { const handleMessage = message => { handleInsertion(message) } let text = '' const [response] = await WindowAI.generateText( { messages: [ { role: 'assistant', content: `你是一个代码补全器,帮我重构这段代码,或实现注释中给出的请求。代码的语言为 ${config.lang},只需给出重构的代码,不需要任何解释,但可以在注释中补充人类可读的说明。` }, { role: 'user', content: config.code } ] }, { onStreamResult: (resp, err) => { if (err) logger.error('在 AI 生成时发生错误。', err) else if (resp) { text += resp.message.content handleMessage(text) } } } ) text = response.message.content.trim() handleMessage(text) } const handleCompletion = async ( editor, config, cursorStyleLoading, cursorStyleNormal, WindowAI ) => { const currentPosition = editor.getPosition() if (!currentPosition) { return } cursorStyleLoading() let lastText = '' const handleInsertion = text => { const position = editor.getPosition() if (!position) { return } const offset = editor.getModel()?.getOffsetAt(position) if (offset === undefined) { return } const edits = [ { range: { startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: position.lineNumber, endColumn: position.column }, text: text.slice(lastText.length) } ] lastText = text editor.executeEdits('', edits) } try { await fetchCompletion(config, handleInsertion, WindowAI) cursorStyleNormal() } catch (error) { cursorStyleNormal() logger.error('在 AI 生成时发生错误。', error) } } const cursorStyleLoading = () => { editor.updateOptions({ cursorStyle: 'underline' }) } const cursorStyleNormal = () => { editor.updateOptions({ cursorStyle: 'line' }) } cursorStyleNormal() let refactorAction = { id: 'aurora.refactor', label: '用 AI 重构选中代码', keybindings: [Monaco.KeyMod.CtrlCmd | Monaco.KeyCode.KeyB], contextMenuGroupId: 'aurora', run: async () => { let WindowAI = window.ai if (!WindowAI || !WindowAI.generateText) { const { SSE } = await import( 'https://cdn.jsdelivr.net/npm/[email protected]/+esm' ) logger.log( 'window.ai 未安装或不支持,已回退到学而思 AI,生成结果质量可能变差。' ) logger.log( '您可以访问 https://windowai.io/ 来获取关于 window.ai 的相关信息。' ) WindowAI = { generateText: async (session, func) => { const history = session.messages.slice(0, -1) const prompt = session.messages.at(-1).content const authData = await ( await fetch('https://code.xueersi.com/api/ai/auth') ).json() const token = `Bearer ${authData.data.token}` let responseText = '' let source = new SSE( 'https://codeapi.xueersi.com/ai/aigc/v2/chat', { method: 'POST', headers: { Authorization: token, 'Content-Type': 'application/json' }, payload: JSON.stringify({ prompt: prompt, history: history, stream: true, max_tokens: 0 }) } ) source.addEventListener('message', function (data) { const content = JSON.parse(data.data) responseText += content.message.content func.onStreamResult(content) }) source.stream() return [{ message: { content: responseText } }] } } } if (editor.getRawOptions().readOnly) return const originalTitle = document.title document.title = 'Generating...' // 最麻烦的一集 const selectedRange = this.editor.getSelection() const selectedText = this.editor .getModel() .getValueInRange(selectedRange) editor.executeEdits('', [ { range: selectedRange, text: '' } ]) logger.log( '您正在使用 AI 生成功能。请注意开发者不对 AI 生成结果的质量作保证,请遵循相关地区的法律法规。' ) await handleCompletion( editor, { code: selectedText, lang: editor.getModel().getLanguageId() }, cursorStyleLoading, cursorStyleNormal, WindowAI ) document.title = originalTitle } } editor.addAction(refactorAction) } this.editor = Monaco.editor.create(this.$el, { value: dummy.value, automaticLayout: true, language: dummy.language, fontSize: this.fontSize, readOnly: !!this.readOnly }) const engine = new URL( plug.get_option_or('searchEngine', 'https://www.bing.com/?q=') ) this.editor.addAction({ id: 'aurora.search', label: `用 ${engine.host} 搜索选中内容`, contextMenuGroupId: 'aurora', run: () => { const a = document.createElement('a') a.target = '_blank' a.href = `${engine.toString()}${this.editor .getModel() .getValueInRange(this.editor.getSelection())}` a.click() } }) MonacoEditorCopilot(this.editor) this.editor.getModel().setEOL(0) this.editor.onDidChangeModelContent(() => { const value = this.editor.getValue() this.$emit('listenEvent', { type: 'update', user_code: value }) this.contentBackup = value }) this.editor.setOptions = this.editor.setBehavioursEnabled = this.editor.setOptions = this.editor.resize = () => {} this.editor.gotoLine = line => { this.editor.revealLineInCenter(line) } this.editor.moveCursorTo = (lineNumber, column) => this.editor.setPosition({ lineNumber: Number(lineNumber) + 1, column: Number(column) + 1 }) this.editor.setReadOnly = readOnly => this.editor.updateOptions({ readOnly }) this.editor.setFontSize = fontSize => this.editor.updateOptions({ fontSize }) this.tryHandleExceptionForTerm = errors => { const SeverityMap = { error: 8, warning: 4, note: 1 } if (!errors || errors.length === 0) { Monaco.editor.setModelMarkers( this.editor.getModel(), 'error', [] ) return } Monaco.editor.setModelMarkers( this.editor.getModel(), 'error', errors.map(error => ({ startLineNumber: Number(error.Line), endLineNumber: Number(error.Line), message: error.Info.split('\r\n')[0].trim(), startColumn: Number(error.Postion), // NOTE: This is a typo from Xueersi developer endColumn: Infinity, severity: SeverityMap[error.Type] })) ) } this.editor.getSession = () => ({ setMode: mode => { const lang = mode.substring(9) this.editor.getModel().setLanguage(languageMap[lang] ?? lang) }, setUndoManager() {} }) this.editor.onDidChangeCursorPosition(ev => { this.$emit('listenEvent', { type: 'cursorChange', user_code_cursor: { row: ev.position.lineNumber - 1, column: ev.position.column - 1 } }) }) this.editor.onDidFocusEditorText(() => { this.$store.dispatch('editorFocus', true) }) this.$store.dispatch('setEditor', this.editor) } ] }) } }) plug.plug('hidden', '访问不公开的作品。', () => { let loaded = false // Universal (Vue.js / React) patch webpackListener.push(exports => { if ( typeof exports === 'function' && exports.default === exports && typeof exports.Axios === 'function' && !loaded ) { loaded = true exports.interceptors.request.use(function (config) { if (config.url.startsWith('/compilers/v2/')) { config.url = '/community/v4/projects/detail' config.params = { id: config.params.id, lang: 'python' } } else if (config.url.startsWith('/api/projects/v2/')) { const code = config.url.split('/').at(-1) config.url = '/api/community/v4/projects/detail' config.params = { id: code, lang: 'scratch' } } else if (config.url.startsWith('/projects/v2/')) { const code = config.url.split('/').at(-1) config.url = '/community/v4/projects/detail' config.params = { id: code, lang: 'scratch' } } return config }) exports.interceptors.response.use(function (resp) { if (resp.config.url === '/community/v4/projects/detail') { if (resp.data.stat === 1) { if (!resp.data.data.published) resp.data.data.published_at = resp.data.data.modified_at } } return resp }) } }) }) plug.plug('dislike', '显示踩数。', () => { const UnlikeImg = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAv3pUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMgDPvPKXYE8oCG49C1k3aDHX+BpFVZZwmT2MhNA/vn/YJHB6GA5EVLLSUZpEqlZoUmRxuMSQZ708LDWYfTIJPYbvZWS7w/dDwDjkBM+RKkzzDW2agS+foTRH5xn6jXWwTVCGJyAyOgxaSl6nL9hXVPM9QPdBKdx771i21vy/YdJtoZORkzqw/A/Qhws0IHC7ncjNn1COuL/LOnA/AFZY9Zb2AiYesAAAGEaUNDUElDQyBwcm9maWxlAAB4nH2RPUjDQBzFX9NKi1QcrCDiELA62UVFHKWKRbBQ2gqtOphc+gVNGpIUF0fBteDgx2LVwcVZVwdXQRD8AHF2cFJ0kRL/lxRaxHhw3I939x537wChWWWqGZgDVM0y0om4mMuvisFXhBBAGIMYlZipJzOLWXiOr3v4+HoX41ne5/4cfUrBZIBPJJ5jumERbxDPbFo6533iCCtLCvE58YRBFyR+5Lrs8hvnksMCz4wY2fQ8cYRYLHWx3MWsbKjE08RRRdUoX8i5rHDe4qxW66x9T/7CcEFbyXCd5ggSWEISKYiQUUcFVViI0aqRYiJN+3EP/7DjT5FLJlcFjBwLqEGF5PjB/+B3t2ZxatJNCseBnhfb/hgDgrtAq2Hb38e23ToB/M/Aldbx15rA7CfpjY4WPQL6t4GL644m7wGXO8DQky4ZkiP5aQrFIvB+Rt+UBwZugd41t7f2Pk4fgCx1tXwDHBwC4yXKXvd4d6i7t3/PtPv7AYV+cq40kTkPAAANdmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDplNDNjMDY2My03NTIxLTQyM2ItYjhmMC00NjYwYjRiNTI0NjQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTEwMmJjMDEtY2Y4NC00NDQwLThlYjktZmI1ZGI0ZDc0ZWVkIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NDE2YTU1MDUtODNhYS00MWJjLTliM2YtMTkxNzA3NmQxZTg1IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE3MjI3NzQxODg2OTE4NzUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zOCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDg6MDRUMjA6MjM6MDgrMDg6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjA4OjA0VDIwOjIzOjA4KzA4OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjAyYjQ3ZmMtYjUzNi00NDhlLTk0OGItYWZmMzc4ODRiMTY2IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDI0LTA4LTA0VDIwOjIzOjA4Ii8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PhEGw8QAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfoCAQMFwiLkxoZAAACMklEQVRIx7WVP0+UQRjEf7PvwoHKH41G0URMVGoLrQwEYiGRwkJM/AT6OWysrP0UYiFCDBiQwkITtVBJLIyNlYIaUDzufHcsPM0FuTuOOyfZajc7+8w8z6wApmYXhekBrpI8btELiOawiT0NWpa4YTgMSFLRKT2IlUOdwLjta4g+INA8iiGEHsMZ24PAHgDbSdJwAFBKBZwuAL27JPkDCTr+vUOdYebhvEKg1+gEkLVAQkqp5l4s5lk0nAQK/EcEKWRCQ61W05AIO2IPAbH124Jrb2WhUJEu/teKUkoDQN8u5qZZ6TjVrmpk15YOON1m2by9dG6fP0a1KxI+0mIaVItXxyO0AqQ2yZawN4AS8LOyyohvEXEfc7TSeaGGj1kDT4rAGnglSC/BA4aDQLApChaixCOkNSfOirRfkpKRkKwUZY4bDdYgy4U+AtOIJzbvBZvA7apHGygJ4N7s446U3CFSkEQyCIHcDVy3ubRNFuaS3lq+g/VKULwyMZY37x4wNbPQA9yyObelMy1pFeum5ReTE2Objcyr29Y2qvxRWx9UwsyDX++EpCGRfhNEb0Mk/EzwY6ftGBuMRQLWMam6IyWVUtQ7ClneHiI7AZ9A1XOWDJ/JvTE5Ouydh2r9ksqg58BGVYaVZL9Rcrm59K6DQwf6y11dnUvAHLAKfBVaJoS7ZFmxPeFUwdzSU62tf98rcSzg7hjjh4S+XL44kreV6O9MzS6GjFz9+7rT6Mh5NxuCvwDVzNSdNFOkZAAAAABJRU5ErkJggg==` addStyle(` .card-bottom-data-right-unlike { margin-right: 12px; vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap } .icon-cai:before { display: inline-block; content: "\\e61f"; transform: rotate(180deg); } .recom-card .card-bottom-data-right-unlikes>i,.recom-card .card-bottom-data-right-views>i { margin-right: 5px } .recom-card .card-bottom-data-right-unlikes { margin-right: 12px } .recom-card .card-bottom-data-right-unlikes,.recom-card .card-bottom-data-right-views { vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap } .card-bottom-data-right-unlike:before { content: " "; width: 16px; height: 16px; background: url(${UnlikeImg}) no-repeat; background-size: 100% 100%; display: inline-block; margin-right: 5px; vertical-align: middle; margin-top: -2px } .work-card .work-detail .work-list .work-count .unlike_icon { margin-right: .5em } .work-card .work-detail .work-list .work-count .unlike_icon:before { background: url(${UnlikeImg}) } `) vueMixinManager.on('commentPiece', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) /** @type {VElement} */ const VElement = res.constructor const unlike = res.children[1].children[2].children[1].children[1].children[1] unlike.children.push( new VElement( undefined, undefined, undefined, ` ${this.commentDetail.unlikes} `, undefined, this ) ) return res } }) }) vueMixinManager.on('109', instance => { function patchLike(instance, p) { instance[p] = function () { let flag = false const proxy = new Proxy(instance, { get: (_, p) => { if (p === 'projectId' && !flag) { // Skip first time comparison flag = true return '0' } return Reflect.get(instance, p) } }) return instance.$options.methods[p].call(proxy) } } if (instance.projectData) { // ✌就要踩 patchLike(instance, 'fnSetLike') patchLike(instance, 'fnSetUnlike') patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) const operateLeft = res.componentOptions.children[1].children[1].children[1] .children[0].children[1].children[0] const operateRight = res.componentOptions.children[1].children[1].children[1] .children[0].children[1].children[1] const dislike = operateLeft.children[2] if ( typeof this.projectData.unlikes === 'number' && this.projectData.unlikes > 0 ) { dislike.children.push( createElement('span', `${this.projectData.unlikes}`) ) } operateLeft.children.pop() // 删除“分享” operateRight.children.splice(0, 1) // 删除“公约” return res } }) } }) vueMixinManager.on('WorkPiece', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) if (this.workDetail.removed) { return res } const workCount = res.children[0].children[1].children[1].children[1] workCount.children.splice( 1, 0, createElement( 'i', { staticClass: 'unlike_icon' }, `${this.workDetail.unlikes}` ) ) return res } }) }) vueMixinManager.on('Card', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const res = render.call(this, createElement) const rankDetail = res.children[0].children[0].children[6].children[1].children[1] rankDetail.children.splice( 2, 0, createElement('img', { attrs: { width: '13', src: UnlikeImg }, staticClass: 'rank-heart-img' }), createElement( 'span', { staticClass: 'rank-heart' }, `${this.cell.unlikes}` ) ) return res } }) }) vueMixinManager.on('NewCard', instance => { patch(instance.$options, 'render', render => { return function (createElement) { const ret = render.call(this, createElement) let work if (instance.$parent.$parent.list) { work = instance.$parent.$parent.list.find( v => v && this.id === v.id ) const cardBottomRight = ret.children[1].children[1].children[1] cardBottomRight.children.splice( 1, 0, createElement( 'div', { staticClass: 'card-bottom-data-right-unlike' }, `${work.unlikes}` ) ) } else if ( instance.$parent.$parent.$parent.$parent.$parent .keduoRecommendData?.[0]?.items ) { work = instance.$parent.$parent.$parent.$parent.$parent.keduoRecommendData[0].items.find( v => this.id === v.id ) if (work) { const cardBottomRight = ret.children[1].children[1].children[1] cardBottomRight.children.splice( 1, 0, createElement( 'div', { staticClass: 'card-bottom-data-right-unlikes' }, [ createElement('i', { staticClass: 'iconfont icon-cai' }), `${work.unlikes}` ] ) ) } else { work = instance.$parent.$parent.$parent.$parent.$parent.tagWorkList .flatMap(v => v.items) .find(v => this.id === v.id) const cardBottomRight = ret.children[1].children[1].children[1] cardBottomRight.children.splice( 1, 0, createElement( 'div', { staticClass: 'card-bottom-data-right-unlike' }, `${work.unlikes}` ) ) } } else if (instance.$parent.$parent.$parent.projects) { work = instance.$parent.$parent.$parent.projects .flat() .find(v => this.id === v.id) const cardBottomRight = ret.children[1].children[1].children[1] cardBottomRight.children.splice( 1, 0, createElement( 'div', { staticClass: 'card-bottom-data-right-unlike' }, `${work.unlikes}` ) ) } return ret } }) }) }) plug.done() if (window === window.top) { // Something went wrong on dependencies (https://github.com/ai/nanoid/pull/490), so Markdown based dashboard is suspended. // async function printImage(url) { // const xhr = await fetch(url) // const fr = new FileReader() // fr.addEventListener('load', () => { // const style = `font-size: 300px; background-image: url("${fr.result}"); background-size: contain; background-repeat: no-repeat;` // console.log('%c ', style) // }) // fr.readAsDataURL(await xhr.blob()) // } // const plugin = document.createElement('div') // plugin.insertAdjacentHTML( // 'beforeend', // `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/github-markdown.min.css"` // ) // const article = document.createElement('article') // article.className = 'markdown-body' // article.insertAdjacentHTML('beforeend', '<h1>Xueersi Aurora 已加载。</h1>') // plugin.appendChild(article) // logger.clear() // printImage( // '' // ) logger.log('Xueersi Aurora 已加载。') logger.log( '关于开发自己的功能,可以参见 %cAurora.help("development")%c。', CODEFONT_CSS, '' ) window.Aurora.enable() logger.log('请使用 %cAurora.help()%c 查看帮助。', CODEFONT_CSS, '') } })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址