// ==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, '')
}
})()