feat: 更新CSS样式、添加TinyMCE插件及优化发布页面
修复移动端登录框样式问题 更新公共JS文件中的授权令牌 添加TinyMCE插件(代码、视觉块、预览等) 优化发布管理页面的编辑器和布局 调整登录组件的响应式样式
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -882,6 +882,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-header {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-list .rule-item .rule-item-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-list .rule-item .rule-item-text {
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.signInBox-mask .signInBox .signInBox-head .header-bi {
|
||||
width: 60px;
|
||||
@@ -959,6 +971,7 @@
|
||||
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-header {
|
||||
font-size: 18px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-list .rule-item .rule-item-icon {
|
||||
@@ -974,7 +987,8 @@
|
||||
|
||||
.signInBox-mask .signInBox .signInBox-content .sign-in-box .outer-ring .rule-box .rule-list .rule-item .rule-item-text {
|
||||
font-size: 13px;
|
||||
padding: 15px 0;
|
||||
padding: 10px 0;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1261,7 +1261,7 @@ body {
|
||||
.side-box.essence-side-box .side-header {
|
||||
margin-bottom: 21px !important;
|
||||
}
|
||||
.side-box.essence-side-box .box .item {
|
||||
.side-box.essence-side-box .box .item:not(:last-of-type) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.side-box.essence-side-box .box .item .dot {
|
||||
|
||||
@@ -1526,7 +1526,7 @@ body {
|
||||
margin-bottom: 21px !important;
|
||||
}
|
||||
|
||||
.side-box.essence-side-box .box .item {
|
||||
.side-box.essence-side-box .box .item:not(:last-of-type) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -388,6 +388,9 @@
|
||||
.item-box {
|
||||
padding: 18px 20px 0;
|
||||
}
|
||||
.head-top .sign-in {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 680px) {
|
||||
#sectionIndex .matter .matter-content .info-box .right .bottom .btn {
|
||||
|
||||
@@ -463,6 +463,10 @@
|
||||
.item-box {
|
||||
padding: 18px 20px 0;
|
||||
}
|
||||
|
||||
.head-top .sign-in {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
|
||||
565
homepage-other-V2.html
Normal file
565
homepage-other-V2.html
Normal file
@@ -0,0 +1,565 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>so猫的个人主页 -- 寄托天下</title>
|
||||
<link rel="stylesheet" href="/css/public.css" />
|
||||
<link rel="stylesheet" href="/css/homepage-other.css" />
|
||||
|
||||
<meta name="description" content="寄托天下留学论坛上查看so猫的个人主页">
|
||||
<meta name="keywords" content="so猫, 寄托天下, 留学论坛">
|
||||
<meta name="author" content="">
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="so猫的个人主页">
|
||||
<meta property="og:description" content="寄托天下留学论坛上查看so猫的个人主页">
|
||||
<meta property="og:image" content="">
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:title" content="so猫的个人主页">
|
||||
<meta property="twitter:description" content="寄托天下留学论坛上查看so猫的个人主页">
|
||||
<meta property="twitter:image" content="">
|
||||
<!-- 网站图标 -->
|
||||
<link rel="icon" href="https://www.gter.net/favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="https://www.gter.net/favicon.ico" type="image/x-icon">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#pre-loader {
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#pre-loader .three-bounce>div {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
top: 50%;
|
||||
margin-top: -9px;
|
||||
background: #aeadba;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
#pre-loader .three-bounce .one {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
#pre-loader .three-bounce .two {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
|
||||
0%,
|
||||
100%,
|
||||
80% {
|
||||
transform: scale(0);
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
var STYLEID = '2',
|
||||
STATICURL = 'static/',
|
||||
IMGDIR = 'https://bbs.gter.net/template/archy_plt8/image',
|
||||
VERHASH = 'Z62',
|
||||
charset = 'gbk',
|
||||
discuz_uid = '0',
|
||||
cookiepre = '4B5x_c0ae_',
|
||||
cookiedomain = 'gter.net',
|
||||
cookiepath = '/',
|
||||
showusercard = '1',
|
||||
attackevasive = '0',
|
||||
disallowfloat = '',
|
||||
creditnotice = ',',
|
||||
defaultstyle = '',
|
||||
REPORTURL = 'aHR0cDovL2Jicy5ndGVyLm5ldC9mb3J1bS5waHA/dGlkPTI0MDYzNTYmZ290bz1sYXN0cG9zdA==',
|
||||
SITEURL = 'https://app.gter.net/',
|
||||
JSPATH = 'static/js/';
|
||||
</script>
|
||||
<script src="https://app.gter.net/bottom?tpl=header&menukey=bbs"></script>
|
||||
<script src="https://framework.x-php.com/gter/bbs/static/js/common.js" charset="gbk"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
window.__ASSET_VERSION__ = 'Z70';
|
||||
window.isMobile = window.innerWidth <= 768;
|
||||
</script>
|
||||
<div id="ajaxwaitid"></div>
|
||||
<div id="append_parent"></div>
|
||||
<div class="head-top flexacenter" style="width: 1200px;margin: 20px auto 30px;z-index: 8;">
|
||||
<a href="/" class="flexacenter" target="_blank">
|
||||
<img class="logo" src="https://oss.gter.net/logo" alt="" />
|
||||
</a>
|
||||
<div class="flex1"></div>
|
||||
<div class="input-box flexacenter">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder-box" style="transition: transform .3s ease"></div>
|
||||
</div>
|
||||
|
||||
<input class="input flex1" type="text" maxlength="140" /> <img class="icon" onclick="searchEvent()" src="https://framework.x-php.com/gter/forum/img/search-icon.svg?v=HP1TnTC4iXqb" />
|
||||
<div class="search-box-history">
|
||||
<div class="search-box-history-title">历史搜索</div>
|
||||
<div class="search-box-history-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-list flexacenter"> </div>
|
||||
|
||||
<div class="sign-in sign-in-no flexacenter"></div>
|
||||
|
||||
<div class="head-more flexcenter" onclick="openHeadPop()">
|
||||
<img class="more-icon" style="width: 18px;height: 15px;" src="https://framework.x-php.com/gter/forum/img/threeAcross.svg?v=HP1TnTC4iXqb" />
|
||||
</div>
|
||||
|
||||
<div class="head-pop" style="display: none;">
|
||||
<div class="head-more-pop">
|
||||
<div class="head-more-userinfo flex1 flexacenter">
|
||||
<div class="head-more-left flexacenter"><img class="head-more-userinfo-avatar" src="" alt="">
|
||||
<div class="head-more-userinfo-username"></div>
|
||||
</div>
|
||||
<div class="head-more-right">
|
||||
<div class="loginBtn flexcenter" onclick="go_ajax_Login()">登录/注册</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-list"><a class="tab-item flexacenter" href="https://www.gter.net" target="_blank">寄托首页</a><a class="tab-item flexacenter pitch" href="https://f.gter.net" target="_blank">论坛</a><a class="tab-item flexacenter" href="https://app.gter.net/admissionOfficer" target="_blank">招生官</a><a class="tab-item flexacenter" href="https://bbs.gter.net/thread-2345065-1-1.html" target="_blank">加群</a><a class="tab-item flexacenter" href="https://offer.gter.net" target="_blank">Offer榜</a></div>
|
||||
|
||||
<div class="sign-in sign-in-no flexacenter"></div>
|
||||
|
||||
<a class="head-more-post flexcenter" href="/publish" target="" onclick="skipLoginUrl(event)">
|
||||
<div class="head-more-post-icon flexcenter"><img class="head-more-post-img" src="https://framework.x-php.com/gter/forum/img/addyellow.svg?v=HP1TnTC4iXqb" /></div>发布帖子
|
||||
</a>
|
||||
<img class="cross-icon" onclick="crossHeadPop()" src="https://framework.x-php.com/gter/forum/img/cross.svg?v=HP1TnTC4iXqb">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="valueA" style="display: none;">https://framework.x-php.com/gter/forum/</div>
|
||||
|
||||
|
||||
|
||||
<div id="pre-loader">
|
||||
<div class="three-bounce" p-id="11">
|
||||
<div class="one" p-id="12"></div>
|
||||
<div class="two" p-id="13"></div>
|
||||
<div class="three" p-id="14"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" id="homepage-other" v-cloak>
|
||||
<div class="templateValue" ref="tokenRef">IK8gQW_rhIzjn5y_27ky2ZvwRQxRrg7wAsfg1NYIwUkqAJdjHi9EmGZmMjM~</div>
|
||||
<!-- <head-top></head-top> -->
|
||||
|
||||
<div class="head-navigation flexacenter">
|
||||
<img class="icon" src="https://framework.x-php.com/gter/forum/img/index-icon.png?v=HP1TnTC4iXqb" />
|
||||
<a class="text" href="/" target="_blank">论坛</a>
|
||||
<img class="arrows" src="https://framework.x-php.com/gter/forum/img/arrows-gray.svg?v=HP1TnTC4iXqb" />
|
||||
<div class="text one-line-display">so猫的个人主页</div>
|
||||
</div>
|
||||
|
||||
<div class="matter flexflex">
|
||||
<div class="card-user flexcenter">
|
||||
<div class="name-area">
|
||||
<img v-if="info.avatar" class="avatar" :src="info.avatar" alt="用户头像" />
|
||||
|
||||
<h3 class="username flexcenter">{{ info.nickname }}</h3>
|
||||
<p class="uid flexcenter">
|
||||
UID: {{ info.uin }}
|
||||
<img class="icon" @click="copy(info.uin)" src="https://framework.x-php.com/gter/forum/img/copy-icon.png?v=HP1TnTC4iXqb" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="medal-area" v-if="medallist.length != 0">
|
||||
<p class="title">勋章 {{ medallist.length }}</p>
|
||||
<div class="list flexflex">
|
||||
<img v-for="item in medallist" :key="item.medalid" :src="item.image" :alt="item.description" class="item" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-area">
|
||||
<div class="item msg flexcenter" @click="sendMessage()">发私信</div>
|
||||
<template v-if="isManager">
|
||||
<a class="item flexcenter" target="_blank" href="https://demo.gter.net/admin">用户管理</a>
|
||||
<a class="item flexcenter" target="_blank" href="https://demo.gter.net/admin">内容管理</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="matter-content flex1">
|
||||
<div class="message-box">
|
||||
<!-- 头部区域 -->
|
||||
<div class="header flexacenter">
|
||||
<img v-if="info.avatar" :src="info.avatar" alt="用户头像" class="avatar" />
|
||||
<span class="username">{{ info.nickname }}</span>
|
||||
<img v-if="info?.group?.image" class="icon" :src="info?.group?.image" />
|
||||
</div>
|
||||
|
||||
<!-- 信息列表区域 -->
|
||||
<div class="info-list flexflex">
|
||||
<template v-if="isManager">
|
||||
<div class="item flexacenter">
|
||||
<span class="label">注册时间</span>
|
||||
<span class="value">{{ info.register_at || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="item flexacenter">
|
||||
<span class="label">最后登录</span>
|
||||
<span class="value">{{ info.lastlogintime || '暂无' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="item flexacenter">
|
||||
<span class="label">在线时长</span>
|
||||
<span class="value">{{ info.oltime || 0 }} 小时</span>
|
||||
</div>
|
||||
|
||||
<template v-if="isManager">
|
||||
<div class="item flexacenter">
|
||||
<span class="label">上次访问 IP</span>
|
||||
<span class="value">{{ info.lastloginip || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="item flexacenter">
|
||||
<span class="label">Email</span>
|
||||
<span class="value">{{ info.email || '暂无' }}</span>
|
||||
<span v-if="info.email" class="status blue flexacenter">已认证</span>
|
||||
</div>
|
||||
<div class="item flexacenter">
|
||||
<span class="label">手机号</span>
|
||||
<span class="value">{{ info.mobile || '暂无' }}</span>
|
||||
<span v-if="info.mobile" class="status blue flexacenter">已认证</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="item flexacenter">
|
||||
<span class="label">累计签到</span>
|
||||
<span class="value">{{ info.sign_count || 0 }} 天</span>
|
||||
</div>
|
||||
|
||||
<div class="item flexacenter">
|
||||
<span class="label">本月签到</span>
|
||||
<span class="value">{{ info.sign_month || 0 }} 天</span>
|
||||
</div>
|
||||
|
||||
<template v-if="isManager">
|
||||
<div class="item flexacenter">
|
||||
<span class="label">寄托币</span>
|
||||
<span class="value">{{ info.gtercoin || 0 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 统计标签区域 -->
|
||||
<div class="stats flexacenter" v-if="creationType.length != 0">
|
||||
<template v-for="(item, index) in creationType" :key="index">
|
||||
<span class="item flexacenter">
|
||||
<div class="text">{{ item.text }} ×</div>
|
||||
<div class="num">{{ item.number }}</div>
|
||||
</span>
|
||||
<div class="line" v-if="index != creationType.length - 1">|</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Offer标签区域 -->
|
||||
<div class="tags flexflex" v-if="schoolTags.length != 0">
|
||||
<template v-for="(item, index) in schoolTags" :key="index">
|
||||
<a v-if="item.type == 'offer'" class="item flexacenter" target="_blank" :href="'/details/' + item.uniqid">
|
||||
<img class="icon" src="https://framework.x-php.com/gter/forum/img/offer-icon.png?v=HP1TnTC4iXqb" mode="heightFix" />
|
||||
{{ item.school }}
|
||||
</a>
|
||||
<a v-else class="item flexacenter" target="_blank" :href="'/details/' + item.uniqid">
|
||||
<img class="icon" src="https://framework.x-php.com/gter/forum/img/mj-icon.png?v=HP1TnTC4iXqb" mode="heightFix" />
|
||||
{{ item.school }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-area">
|
||||
<div class="classify flexacenter">
|
||||
<div class="item" :class="{'pitch': item.type === classify}" v-for="item in classifyList" :key="item.type" @click="classifyChange(item.type)">{{ item.text }}</div>
|
||||
</div>
|
||||
|
||||
<div class="issue-data flexacenter">
|
||||
共
|
||||
<div class="num">{{ count }}</div>
|
||||
个创作,获得
|
||||
<div class="num">{{ classify == 'all' ? totalLikes : (likeObjValue[classify] || 0) }}</div>
|
||||
个赞
|
||||
</div>
|
||||
|
||||
<div class="list-box" v-if="list.length != 0">
|
||||
<template v-for="(item,index) in list" :key="index">
|
||||
<item-offer v-if=" item.type == 'offer'" :itemdata="item"></item-offer>
|
||||
<item-summary v-else-if="item.type == 'offer_summary'" :itemdata="item"></item-summary>
|
||||
<item-vote v-else-if="item.type == 'vote'" :itemdata="item"></item-vote>
|
||||
<item-mj v-else-if="item.type == 'interviewexperience'" :itemdata="item"></item-mj>
|
||||
<item-tenement v-else-if="item.type == 'tenement'" :itemdata="item"></item-tenement>
|
||||
<item-forum v-else :itemdata="item"></item-forum>
|
||||
</template>
|
||||
</div>
|
||||
<load-box :loading="loading"></load-box>
|
||||
|
||||
<div v-if="list.length == 0 && page == 0" class="empty flexcenter">
|
||||
<img class="empty-icon" src="https://framework.x-php.com/gter/forum/img/empty-icon.png?v=HP1TnTC4iXqb" />
|
||||
<div class="empty-text">- 暂无内容 -</div>
|
||||
</div>
|
||||
|
||||
<div v-if="list.length != 0 && page != 0" class="load-more flexcenter">加载更多…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://framework.x-php.com/gter/forum/js/vue.global.js?v=HP1TnTC4iXqb"></script>
|
||||
<script src="https://framework.x-php.com/gter/forum/js/axios.min.js?v=HP1TnTC4iXqb"></script>
|
||||
<script src="https://framework.x-php.com/gter/forum/js/public.js?v=HP1TnTC4iXqb"></script>
|
||||
<!-- <script src="https://f.gter.net/js/public.js"></script> -->
|
||||
|
||||
<script type="module" src="https://framework.x-php.com/gter/forum/js/homepage-other.js?v=HP1TnTC4iXqb"></script>
|
||||
<!-- <script type="module" src="https://f.gter.net/js/homepage-other.js"></script> -->
|
||||
<script type="module" src="https://framework.x-php.com/gter/forum/../image/gter/commonCom/sign-in/sign-in.js?v=HP1TnTC4iXqb"></script>
|
||||
|
||||
|
||||
<script src="https://app.gter.net/bottom?tpl=footer,popupnotification"></script>
|
||||
|
||||
<script>
|
||||
if (location.href.indexOf('details') != -1 || location.href.indexOf('thread') != -1) {
|
||||
const postList = document.querySelector('.head-top .post-list')
|
||||
postList.innerHTML = `<a href="/publish" target="_blank" style="margin-right: 10px"> <img class="post-item" src="https://framework.x-php.com/gter/forum/img/post-thread.png?v=HP1TnTC4iXqb" /> </a> <a href="https://offer.gter.net/post" target="_blank" style="margin-right: 10px"> <img class="post-item" src="https://framework.x-php.com/gter/forum/img/post-offer.png?v=HP1TnTC4iXqb" /> </a> <a href="https://offer.gter.net/post/summary" target="_blank" style="margin-right: 10px"> <img class="post-item" src="https://framework.x-php.com/gter/forum/img/post-summary.png?v=HP1TnTC4iXqb" /> </a> <a href="https://interviewexperience.gter.net/publish" target="_blank" style="margin-right: 10px"> <img class="post-item" src="https://framework.x-php.com/gter/forum/img/post-mj.png?v=HP1TnTC4iXqb" /> </a> <a href="https://vote.gter.net/publish" target="_blank"> <img class="post-item" src="https://framework.x-php.com/gter/forum/img/post-vote.png?v=HP1TnTC4iXqb" /> </a>`
|
||||
postList.style.display = 'flex'
|
||||
} else if (location.href.indexOf('search') != -1) {
|
||||
const box = document.querySelector(".head-top")
|
||||
box.querySelector(".input-box").style.display = "none"
|
||||
box.querySelector(".sign-in").style.display = "none"
|
||||
} else if (location.href.indexOf("publish") != -1) {
|
||||
const box = document.querySelector(".head-top")
|
||||
if (box) document.body.removeChild(box)
|
||||
} else {
|
||||
const signInList = document.querySelectorAll('.head-top .sign-in')
|
||||
signInList.forEach(element => {
|
||||
element.innerHTML = `<div class="sign-in-no-box" onclick="headSignIn()">
|
||||
<img class="sign-in-bj" src="https://framework.x-php.com/gter/forum/img/sign-in-bj.svg?v=HP1TnTC4iXqb" /><img class="coin-bj" src="https://framework.x-php.com/gter/forum/img/coin-bj.svg?v=HP1TnTC4iXqb" />
|
||||
<img class="coin-icon" src="https://framework.x-php.com/gter/forum/img/coin-icon.png?v=HP1TnTC4iXqb" /><span class="text flex1">签到领寄托币</span>
|
||||
<div class="sign-go flexcenter">
|
||||
<img class="sign-go-bj" src="https://framework.x-php.com/gter/forum/img/sign-go.svg?v=HP1TnTC4iXqb" /> GO
|
||||
</div>
|
||||
<img class="petal1" src="https://framework.x-php.com/gter/forum/img/petal1.png?v=HP1TnTC4iXqb" />
|
||||
<img class="petal2" src="https://framework.x-php.com/gter/forum/img/petal2.png?v=HP1TnTC4iXqb" />
|
||||
<img class="petal3" src="https://framework.x-php.com/gter/forum/img/petal3.png?v=HP1TnTC4iXqb" />
|
||||
</div>
|
||||
<div class="sign-in-already-box">
|
||||
<img class="sign-icon" src="https://framework.x-php.com/gter/forum/img/sign-icon.png?v=HP1TnTC4iXqb" />
|
||||
<span>已签到,明天再来</span>
|
||||
</div>`
|
||||
|
||||
element.style.display = 'flex'
|
||||
})
|
||||
|
||||
let userInfoWinTimerCount = 0;
|
||||
const userInfoWinTimer = setInterval(() => {
|
||||
if (location.host == "127.0.0.1:5501") return;
|
||||
if (todaysignedState) {
|
||||
clearInterval(userInfoWinTimer);
|
||||
|
||||
if (todaysigned == 1) {
|
||||
signInList.forEach(element => {
|
||||
element.classList.add('sign-in-already')
|
||||
element.classList.remove("sign-in-no");
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
userInfoWinTimerCount++;
|
||||
if (userInfoWinTimerCount >= 3000) clearInterval(userInfoWinTimer);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function headSignIn() {
|
||||
SignInComponent.initComponent();
|
||||
}
|
||||
|
||||
const searchInput = document.querySelector('.head-top .input')
|
||||
|
||||
// 绑定 blur 和 focus 事件
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('blur', function () {
|
||||
setTimeout(() => {
|
||||
const historyBox = document.querySelector('.head-top .search-box-history')
|
||||
if (historyBox) historyBox.style.display = 'none'
|
||||
}, 300);
|
||||
|
||||
const inputBox = document.querySelector('.head-top .input-box')
|
||||
if (inputBox) inputBox.classList.remove('pitch')
|
||||
startCarousel();
|
||||
|
||||
})
|
||||
searchInput.addEventListener('focus', () => {
|
||||
const historyBox = document.querySelector('.head-top .search-box-history')
|
||||
const historyItem = historyBox.querySelectorAll(".search-box-history-item")
|
||||
if (historyBox && historyItem.length > 0) historyBox.style.display = 'block'
|
||||
|
||||
const inputBox = document.querySelector('.head-top .input-box')
|
||||
if (inputBox) inputBox.classList.add('pitch')
|
||||
|
||||
if (carouselTimer) clearInterval(carouselTimer);
|
||||
})
|
||||
// 绑定回车事件
|
||||
searchInput.addEventListener('keydown', (e) => {
|
||||
if (e.key == 'Enter') searchEvent()
|
||||
})
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const value = e.target.value || ''
|
||||
const placeholder = document.querySelector(".head-top .placeholder")
|
||||
if (value) placeholder.style.display = 'none'
|
||||
else placeholder.style.display = 'block'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let historySearchList = []
|
||||
// 获取历史搜索
|
||||
const getHistorySearch = () => {
|
||||
const data = JSON.parse(localStorage.getItem("history-search")) || [];
|
||||
historySearchList = data;
|
||||
|
||||
let itemAll = ``
|
||||
data.forEach((item, index) => itemAll += `<div class="search-box-history-item one-line-display" onclick="searchEvent('${item}')">${item}</div>`) // 绑定事件 searchEvent 点击搜索)
|
||||
|
||||
const historyList = document.querySelector('.search-box-history-list')
|
||||
historyList.innerHTML = itemAll
|
||||
};
|
||||
|
||||
if (location.href.indexOf("/publish") == -1 && location.href.indexOf("/search") == -1) getHistorySearch();
|
||||
|
||||
|
||||
const searchEvent = (value) => {
|
||||
if (window.innerWidth <= 480) {
|
||||
redirectToExternalWebsite("/search");
|
||||
return
|
||||
}
|
||||
const kw = value || searchInput.value || hotSearchWords[currentIndex]?.keyword || "";;
|
||||
if (!kw) return;
|
||||
historySearchList.unshift(kw);
|
||||
historySearchList = [...new Set(historySearchList)];
|
||||
if (historySearchList.length > 10) historySearchList = historySearchList.splice(0, 10);
|
||||
localStorage.setItem("history-search", JSON.stringify(historySearchList));
|
||||
redirectToExternalWebsite("/search/" + kw);
|
||||
|
||||
searchInput.value = ""
|
||||
}
|
||||
|
||||
let hotSearchWords = [];
|
||||
|
||||
const renderingPlaceholder = () => {
|
||||
let itemAll = ``
|
||||
hotSearchWords.forEach(item => {
|
||||
itemAll += `<div class="item one-line-display" >大家都在搜:${item.keyword}</div>`
|
||||
})
|
||||
|
||||
const sliceHotSearchWords = hotSearchWords.slice(0, 2)
|
||||
sliceHotSearchWords.forEach(item => {
|
||||
itemAll += `<div class="item one-line-display" >大家都在搜:${item.keyword}</div>`
|
||||
})
|
||||
|
||||
const placeholderBox = document.querySelector('.placeholder .placeholder-box')
|
||||
placeholderBox.innerHTML = itemAll
|
||||
|
||||
}
|
||||
|
||||
const getWConfigg = () => {
|
||||
ajaxGet("/v2/api/config/website").then((res) => {
|
||||
if (res.code == 200) {
|
||||
let data = res["data"] || {};
|
||||
hotSearchWords = data.hotSearchWords || [];
|
||||
renderingPlaceholder()
|
||||
data.time = new Date().toISOString();
|
||||
localStorage.setItem("wConfig", JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkWConfig = () => {
|
||||
const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {};
|
||||
if (wConfig.time) {
|
||||
const time = new Date(wConfig.time);
|
||||
const now = new Date();
|
||||
if (now - time > 24 * 60 * 60 * 1000) getWConfigg();
|
||||
else {
|
||||
hotSearchWords = wConfig.hotSearchWords || [];
|
||||
renderingPlaceholder()
|
||||
}
|
||||
} else getWConfigg();
|
||||
};
|
||||
|
||||
checkWConfig()
|
||||
|
||||
|
||||
|
||||
const renderCurrentIndex = () => {
|
||||
const placeholderBox = document.querySelector('.placeholder .placeholder-box')
|
||||
if (placeholderBox) placeholderBox.style.transform = `translateY(${-currentIndex * 36}px)`
|
||||
}
|
||||
|
||||
let currentIndex = 0; // 当前显示的关键词索引
|
||||
let carouselTimer = null; // 轮播定时器
|
||||
|
||||
// 启动轮播函数
|
||||
const startCarousel = () => {
|
||||
// 清除已有的定时器
|
||||
if (carouselTimer) clearInterval(carouselTimer);
|
||||
// 设置新的定时器,每秒滚动一次
|
||||
carouselTimer = setInterval(() => {
|
||||
if (hotSearchWords.length > 1) {
|
||||
if (currentIndex >= hotSearchWords.length - 1) {
|
||||
currentIndex++;
|
||||
setTimeout(() => {
|
||||
currentIndex = 0;
|
||||
}, 2300);
|
||||
} else currentIndex++;
|
||||
}
|
||||
renderCurrentIndex()
|
||||
}, 2300);
|
||||
};
|
||||
|
||||
startCarousel();
|
||||
|
||||
const openHeadPop = () => {
|
||||
if (window["userInfoWin"]?.uin > 0 || window["userInfoWin"]?.uid > 0) {
|
||||
// 登录
|
||||
const headMoreLeft = document.querySelector(".head-pop .head-more-left")
|
||||
headMoreLeft.innerHTML = `<img class="head-more-userinfo-avatar" src="${window["userInfoWin"]?.avatar}" alt=""><div class="head-more-userinfo-username">${window["userInfoWin"]?.nickname}</div>`
|
||||
} else {
|
||||
const avatar = document.querySelector(".head-pop .head-more-userinfo-avatar")
|
||||
avatar.src = "/img/defaultAvatar.png"
|
||||
const headMoreRight = document.querySelector(".head-pop .head-more-right")
|
||||
headMoreRight.style.display = "block"
|
||||
}
|
||||
document.querySelector(".head-pop").classList.add("head-pop-show");
|
||||
}
|
||||
|
||||
const skipLoginUrl = (e) => {
|
||||
if (window["userInfoWin"]?.uin > 0 || window["userInfoWin"]?.uid > 0) { }
|
||||
else {
|
||||
e.preventDefault();
|
||||
go_ajax_Login();
|
||||
}
|
||||
}
|
||||
|
||||
const crossHeadPop = () => document.querySelector(".head-pop").classList.remove("head-pop-show");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -24,7 +24,7 @@ const ajax = (url, data) => {
|
||||
url = url.indexOf("https://") > -1 ? url : forumBaseURL + url;
|
||||
if (data) data["v"] = vParam || "v2";
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (location.hostname == "127.0.0.1") axios.defaults.headers.common["Authorization"] = "n1pstcsmw6m6bcx49z705xhvduqviw29";
|
||||
if (location.hostname == "127.0.0.1") axios.defaults.headers.common["Authorization"] = "d1329afaff3230eae2463306371e74eb";
|
||||
|
||||
axios
|
||||
.post(url, data, {
|
||||
@@ -89,7 +89,7 @@ const ajaxGet = (url) => {
|
||||
url = `${url}${paramSymbol}v=${vParam || "v2"}`;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (location.hostname == "127.0.0.1") axios.defaults.headers.common["Authorization"] = "n1pstcsmw6m6bcx49z705xhvduqviw29";
|
||||
if (location.hostname == "127.0.0.1") axios.defaults.headers.common["Authorization"] = "d1329afaff3230eae2463306371e74eb";
|
||||
|
||||
axios
|
||||
.get(
|
||||
|
||||
@@ -3,19 +3,15 @@ const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
|
||||
|
||||
const editApp = createApp({
|
||||
setup() {
|
||||
const { Editor, FileUploader } = window.textbus;
|
||||
const LANG = location.href.indexOf("lang=en") > 0 ? "en" : "zh_CN";
|
||||
|
||||
const title = ref("");
|
||||
const saveStatus = ref("");
|
||||
const uniqid = ref("");
|
||||
const info = ref({});
|
||||
const token = ref("");
|
||||
let editor = null;
|
||||
|
||||
const draftKey = "publish_admin_draft";
|
||||
let uConfigData = {};
|
||||
let imageLength = 10;
|
||||
let videoLength = 5;
|
||||
const maxSize = 20 * 1024 * 1024; // 1M
|
||||
|
||||
const formatTime = (d) => {
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
@@ -27,15 +23,19 @@ const editApp = createApp({
|
||||
const imgElements = dom.querySelectorAll("img");
|
||||
imgElements.forEach((imgEl) => {
|
||||
let url = imgEl.getAttribute("src")?.trim() || "";
|
||||
const urlObj = new URL(url);
|
||||
const aid = urlObj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
if (Number(aid)) {
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
try {
|
||||
const urlObj = new URL(url, location.origin);
|
||||
const aid = urlObj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
if (Number(aid)) {
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing image URL:", url, e);
|
||||
}
|
||||
});
|
||||
return images;
|
||||
@@ -46,19 +46,31 @@ const editApp = createApp({
|
||||
const result = [];
|
||||
|
||||
videoElements.forEach((videoEl) => {
|
||||
const posterurl = videoEl.getAttribute("poster")?.trim() || ""; // 视频地址
|
||||
const urlObj = new URL(posterurl);
|
||||
const posterid = urlObj.searchParams.get("aid");
|
||||
const posterurl = videoEl.getAttribute("poster")?.trim() || "";
|
||||
let posterid = null;
|
||||
let cleanPosterurl = posterurl;
|
||||
|
||||
try {
|
||||
const urlObj = new URL(posterurl, location.origin);
|
||||
posterid = urlObj.searchParams.get("aid");
|
||||
const queryIndex2 = posterurl.indexOf("?");
|
||||
cleanPosterurl = queryIndex2 !== -1 ? posterurl.substring(0, queryIndex2) : posterurl;
|
||||
} catch (e) {}
|
||||
|
||||
const sourceEl = videoEl.querySelector("source");
|
||||
if (!sourceEl) return;
|
||||
|
||||
const url = sourceEl.getAttribute("src") || "";
|
||||
let aid = null;
|
||||
let cleanUrl = url;
|
||||
|
||||
try {
|
||||
const obj = new URL(url, location.origin);
|
||||
aid = obj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
} catch (e) {}
|
||||
|
||||
const url = sourceEl.getAttribute("src") || null;
|
||||
const obj = new URL(url);
|
||||
const aid = obj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
const queryIndex2 = posterurl.indexOf("?");
|
||||
const cleanPosterurl = queryIndex2 !== -1 ? posterurl.substring(0, queryIndex2) : posterurl;
|
||||
result.push({
|
||||
aid: Number(aid),
|
||||
posterid: Number(posterid),
|
||||
@@ -75,18 +87,15 @@ const editApp = createApp({
|
||||
// 提交
|
||||
const submit = (status) => {
|
||||
const infoTarget = { ...info.value } || {};
|
||||
// 获取 HTML 内容
|
||||
|
||||
let content = "";
|
||||
if (editor && typeof editor.getHTML === 'function') {
|
||||
content = editor.getHTML();
|
||||
} else if (editor && editor.output) {
|
||||
content = editor.output.content; // Fallback if getHTML isn't direct
|
||||
if (window.tinymce && window.tinymce.activeEditor) {
|
||||
content = window.tinymce.activeEditor.getContent();
|
||||
}
|
||||
|
||||
// 创建临时 DOM 用于提取图片和视频
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = content;
|
||||
|
||||
|
||||
const images = extractImages(tempDiv);
|
||||
const videos = extractVideos(tempDiv);
|
||||
|
||||
@@ -155,7 +164,8 @@ const editApp = createApp({
|
||||
if (infoTarget.title) title.value = infoTarget.title;
|
||||
|
||||
nextTick(() => {
|
||||
initEditor();
|
||||
// Pass content directly to init
|
||||
initTinyMCE(infoTarget.content || "");
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -163,41 +173,37 @@ const editApp = createApp({
|
||||
});
|
||||
};
|
||||
|
||||
// 上传图片/视频 获取url
|
||||
const uploading = (file, name, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const upload = () => {
|
||||
let config = uConfigData;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append(config.requestName, file); // 文件数据
|
||||
formData.append("name", name); // 文件名
|
||||
formData.append("type", type); // 文件名
|
||||
formData.append(config.requestName || "file", file);
|
||||
formData.append("name", name);
|
||||
formData.append("type", type);
|
||||
if (config.params && config.params.data) {
|
||||
formData.append("data", config.params.data);
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", config.url, true);
|
||||
xhr.withCredentials = true; // 允许携带 Cookie
|
||||
|
||||
// 监听上传进度
|
||||
xhr.upload.onprogress = function (event) {
|
||||
if (event.lengthComputable) {
|
||||
// const percentComplete = (event.loaded / event.total) * 100;
|
||||
// progress.value = Math.round(percentComplete);
|
||||
}
|
||||
};
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
const res = JSON.parse(xhr.responseText);
|
||||
if (res.code == 200) {
|
||||
const data = res.data;
|
||||
resolve(data);
|
||||
} else {
|
||||
creationAlertBox("error", res.message || "上传失败");
|
||||
reject(res);
|
||||
try {
|
||||
const res = JSON.parse(xhr.responseText);
|
||||
if (res.code == 200) {
|
||||
const data = res.data;
|
||||
resolve(data);
|
||||
} else {
|
||||
creationAlertBox("error", res.message || "上传失败");
|
||||
reject(res);
|
||||
}
|
||||
} catch (e) {
|
||||
creationAlertBox("error", "解析响应失败");
|
||||
reject(e);
|
||||
}
|
||||
} else {
|
||||
creationAlertBox("error", "上传失败");
|
||||
@@ -225,59 +231,22 @@ const editApp = createApp({
|
||||
});
|
||||
};
|
||||
|
||||
// 自定义上传适配器
|
||||
class CustomUploader extends FileUploader {
|
||||
uploadFile(type, file) {
|
||||
// type 可能是 'image' 或 'video' 等,取决于调用方
|
||||
// uploading 函数接受 (file, name, type)
|
||||
return uploading(file, file.name, type).then(res => {
|
||||
// 构造带 aid 的 url
|
||||
return `${res.url}?aid=${res.aid}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const initEditor = () => {
|
||||
const editorConfig = {
|
||||
content: info.value?.content || "",
|
||||
providers: [{
|
||||
provide: FileUploader,
|
||||
useFactory: () => new CustomUploader()
|
||||
}],
|
||||
// 默认情况下,xnote 使用悬浮/气泡菜单
|
||||
// 我们不配置 toolbar 容器,让其使用默认行为
|
||||
};
|
||||
|
||||
try {
|
||||
editor = new Editor(editorConfig);
|
||||
editor.mount(document.getElementById("editor-text-area"));
|
||||
|
||||
// 监听内容变化
|
||||
if (editor.onChange) {
|
||||
editor.onChange.subscribe(() => {
|
||||
saveStatus.value = "有未保存的更改";
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
}
|
||||
|
||||
// 点击空白处 focus 编辑器
|
||||
document.getElementById("editor-text-area").addEventListener("click", (e) => {
|
||||
// 如果点击的是容器本身(空白处),则聚焦
|
||||
if (e.target.id === "editor-text-area") {
|
||||
// editor.focus() 如果存在
|
||||
// Textbus editor 实例通常不需要手动 focus,除非是 command
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 提取视频第一帧作为封面
|
||||
const getVideoFirstFrame = (file) => {
|
||||
return new Promise((resolve) => {
|
||||
// 提取视频第一帧作为封面 (支持 File 或 URL)
|
||||
const getVideoFirstFrame = (source) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const video = document.createElement("video");
|
||||
video.src = URL.createObjectURL(file);
|
||||
video.currentTime = 1; // 截取第 1 秒
|
||||
video.setAttribute("crossOrigin", "anonymous"); // Allow cross-origin for cover generation
|
||||
|
||||
if (source instanceof File) {
|
||||
video.src = URL.createObjectURL(source);
|
||||
} else if (typeof source === "string") {
|
||||
video.src = source;
|
||||
} else {
|
||||
reject(new Error("Invalid source for video cover generation"));
|
||||
return;
|
||||
}
|
||||
|
||||
video.currentTime = 1;
|
||||
video.onloadeddata = () => {
|
||||
video.currentTime = 1;
|
||||
};
|
||||
@@ -291,9 +260,480 @@ const editApp = createApp({
|
||||
resolve(coverFile);
|
||||
}, "image/jpeg");
|
||||
};
|
||||
video.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const initTinyMCE = (initialContent) => {
|
||||
if (window.tinymce.get("editor-text-area")) {
|
||||
window.tinymce.get("editor-text-area").remove();
|
||||
}
|
||||
|
||||
// Calculate height based on window size to match original CSS calc(100vh - 370px)
|
||||
// const editorHeight = window.innerHeight - 330;
|
||||
|
||||
const editorConfig = {
|
||||
selector: "#editor-text-area",
|
||||
language: LANG === "en" ? "en" : "zh_CN",
|
||||
language_url: LANG === "en" ? undefined : "/js/tinymce/langs/zh_CN.js",
|
||||
plugins: "image media table link lists code charmap emoticons wordcount fullscreen preview searchreplace autolink directionality visualblocks visualchars template codesample",
|
||||
toolbar: "undo redo | blocks | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media | removeformat | emoticons | fullscreen",
|
||||
menubar: false,
|
||||
fixed_toolbar_container: "#editor-toolbar",
|
||||
// height: editorHeight > 300 ? editorHeight : 300, // Ensure minimum height
|
||||
// resize: false,
|
||||
height: "100%", // Use CSS height
|
||||
resize: true, // Allow user to resize if needed, or rely on container
|
||||
branding: false,
|
||||
promotion: false,
|
||||
convert_urls: false,
|
||||
media_live_embeds: true, // Enable live video previews
|
||||
initialValue: initialContent, // Set initial content here
|
||||
|
||||
// Add urlconverter_callback to handle network resources
|
||||
urlconverter_callback: (url, node, on_save, name) => {
|
||||
// Return URL as is, let image upload handler process it if needed
|
||||
return url;
|
||||
},
|
||||
|
||||
// Intercept Media Dialog URL input
|
||||
media_url_resolver: function (data, resolve /*, reject*/) {
|
||||
const url = data.url;
|
||||
|
||||
// Only intercept http/https URLs that are NOT from our domain (already uploaded)
|
||||
if (url && (url.startsWith("http://") || url.startsWith("https://")) && !url.includes("?aid=")) {
|
||||
// Check if it's a video file type we care about
|
||||
const isVideo = /\.(mp4|webm|ogg|mov|mkv|avi|flv|wmv)$/i.test(url);
|
||||
|
||||
if (isVideo && uConfigData && uConfigData.url) {
|
||||
creationAlertBox("info", "Uploading network video...");
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", url);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
ajax(uConfigData.url, formData)
|
||||
.then(async (res) => {
|
||||
if (res.code == 200 && res.data) {
|
||||
const newUrl = `${res.data.url}?aid=${res.data.aid}`;
|
||||
|
||||
try {
|
||||
// Generate cover
|
||||
const coverFile = await getVideoFirstFrame(newUrl);
|
||||
const coverRes = await uploading(coverFile, "cover.jpg", "image");
|
||||
const coverUrl = `${coverRes.url}?aid=${coverRes.aid}`;
|
||||
|
||||
// Return the full HTML embed code
|
||||
// TinyMCE expects HTML when using resolve() for embeds
|
||||
const videoHtml = `<video src="${newUrl}" poster="${coverUrl}" controls="controls" width="300" height="150"></video>`;
|
||||
resolve({ html: videoHtml });
|
||||
|
||||
// Mark as processed (need to wait for insertion)
|
||||
setTimeout(() => {
|
||||
const editor = window.tinymce.activeEditor;
|
||||
if (editor) {
|
||||
const insertedVideo = editor.dom.select(`video[src="${newUrl}"]`)[0];
|
||||
if (insertedVideo) {
|
||||
// We need to access processedNodes from the outer scope if possible,
|
||||
// but this config object is defined inside initTinyMCE.
|
||||
// However, processedNodes is defined in setup().
|
||||
// We can't access it here easily unless we move processedNodes to higher scope or use a global/editor property.
|
||||
// Let's attach it to the editor instance in setup().
|
||||
if (editor.processedNodes) {
|
||||
editor.processedNodes.add(insertedVideo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
creationAlertBox("success", "Video uploaded successfully");
|
||||
} catch (e) {
|
||||
console.error("Cover generation failed", e);
|
||||
const videoHtml = `<video src="${newUrl}" controls="controls" width="300" height="150"></video>`;
|
||||
resolve({ html: videoHtml });
|
||||
creationAlertBox("success", "Video uploaded (cover failed)");
|
||||
}
|
||||
} else {
|
||||
creationAlertBox("error", "Video upload failed");
|
||||
resolve({ html: `<span class="error">Upload failed: ${url}</span>` }); // Or just resolve empty to cancel?
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Media resolver upload failed", err);
|
||||
resolve({ html: "" }); // Or fallback to original url? resolve({ html: `<video src="${url}" controls></video>` })
|
||||
});
|
||||
|
||||
// Return early, we will resolve asynchronously
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Default behavior for other URLs or if logic skipped
|
||||
resolve({ html: "" }); // Letting it empty might trigger default embed logic?
|
||||
// Actually, if we return empty string, TinyMCE might just insert nothing or error.
|
||||
// The default behavior is to rely on promises.
|
||||
// If we want default behavior, we should probably NOT define this option or call a fallback?
|
||||
// Wait, media_url_resolver replaces the default logic.
|
||||
// If we don't handle it, we must return a promise that resolves to HTML.
|
||||
// If we want the default behavior (creating a video tag for the URL), we have to do it ourselves.
|
||||
|
||||
const defaultHtml = `<video src="${url}" width="300" height="150" controls="controls"></video>`;
|
||||
resolve({ html: defaultHtml });
|
||||
},
|
||||
|
||||
// Handle pasted/dropped images
|
||||
images_upload_handler: (blobInfo, progress) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (blobInfo.blob().size > maxSize) {
|
||||
reject({ message: "图片大小不能超过 20MB", remove: true });
|
||||
return;
|
||||
}
|
||||
uploading(blobInfo.blob(), blobInfo.filename(), "image")
|
||||
.then((res) => {
|
||||
resolve(res.url + "?aid=" + res.aid);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject({ message: err.message || "Image upload failed", remove: true });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
file_picker_callback: (callback, value, meta) => {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", "file");
|
||||
|
||||
if (meta.filetype === "image") {
|
||||
input.setAttribute("accept", "image/png, image/jpeg, image/jpg");
|
||||
} else if (meta.filetype === "media") {
|
||||
input.setAttribute("accept", "video/flv, video/mkv, video/avi, video/rm, video/rmvb, video/mpeg, video/mpg, video/ogg, video/ogv, video/mov, video/wmv, video/mp4, video/webm, video/m4v");
|
||||
}
|
||||
|
||||
input.addEventListener("change", (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (file.size > maxSize) {
|
||||
creationAlertBox("error", "文件大小不能超过 20MB");
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.filetype === "image") {
|
||||
uploading(file, file.name, "image")
|
||||
.then((res) => {
|
||||
callback(res.url + "?aid=" + res.aid, { alt: file.name });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
creationAlertBox("error", "Image upload failed");
|
||||
});
|
||||
} else if (meta.filetype === "media") {
|
||||
creationAlertBox("info", "Uploading video, please wait...");
|
||||
uploading(file, file.name, "video")
|
||||
.then(async (videoRes) => {
|
||||
try {
|
||||
const coverFile = await getVideoFirstFrame(file);
|
||||
const coverRes = await uploading(coverFile, coverFile.name, "image");
|
||||
callback(videoRes.url + "?aid=" + videoRes.aid, { poster: coverRes.url + "?aid=" + coverRes.aid });
|
||||
creationAlertBox("success", "Video uploaded successfully");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
callback(videoRes.url + "?aid=" + videoRes.aid);
|
||||
creationAlertBox("success", "Video uploaded (cover generation failed)");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
creationAlertBox("error", "Video upload failed");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
input.click();
|
||||
},
|
||||
|
||||
setup: (editor) => {
|
||||
const processedNodes = new WeakSet();
|
||||
editor.processedNodes = processedNodes; // Expose to editor instance for media resolver access
|
||||
|
||||
editor.on("change keyup", () => {
|
||||
saveStatus.value = "有未保存的更改";
|
||||
});
|
||||
|
||||
// Also try to set content on init as fallback/confirmation
|
||||
editor.on("init", () => {
|
||||
// Only if empty (though initialValue should handle it)
|
||||
if (!editor.getContent() && initialContent) {
|
||||
editor.setContent(initialContent);
|
||||
}
|
||||
|
||||
// Mark all existing images/videos as processed to prevent auto-upload on click
|
||||
// This handles the case where initial content contains external images that are already "uploaded"
|
||||
// or should be treated as such (not auto-uploaded again).
|
||||
const body = editor.getBody();
|
||||
const imgs = body.querySelectorAll("img");
|
||||
const videos = body.querySelectorAll("video");
|
||||
|
||||
imgs.forEach((node) => processedNodes.add(node));
|
||||
videos.forEach((node) => processedNodes.add(node));
|
||||
});
|
||||
|
||||
// Handle network images/videos paste or drop
|
||||
editor.on("Paste", async (e) => {
|
||||
const clipboardData = e.clipboardData || window.clipboardData;
|
||||
const html = clipboardData.getData("text/html");
|
||||
|
||||
if (html) {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
|
||||
const images = div.querySelectorAll("img");
|
||||
const videos = div.querySelectorAll("video");
|
||||
|
||||
if (images.length > 0) {
|
||||
for (let img of images) {
|
||||
const src = img.getAttribute("src");
|
||||
if (src && !src.includes("?aid=") && src.startsWith("http") && uConfigData && uConfigData.url) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", src);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
const res = await ajax(uConfigData.url, formData);
|
||||
if (res.code == 200 && res.data) {
|
||||
const newUrl = `${res.data.url}?aid=${res.data.aid}`;
|
||||
setTimeout(() => {
|
||||
const editorContent = editor.getContent();
|
||||
if (editorContent.includes(src)) {
|
||||
const newContent = editorContent.replace(src, newUrl);
|
||||
editor.setContent(newContent);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to upload network image:", src, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (videos.length > 0) {
|
||||
for (let video of videos) {
|
||||
const src = video.getAttribute("src");
|
||||
if (src && !src.includes("?aid=") && src.startsWith("http") && uConfigData && uConfigData.url) {
|
||||
try {
|
||||
creationAlertBox("info", "Uploading network video...");
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", src);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
const res = await ajax(uConfigData.url, formData);
|
||||
if (res.code == 200 && res.data) {
|
||||
const newUrl = `${res.data.url}?aid=${res.data.aid}`;
|
||||
|
||||
// Generate cover from the new local URL
|
||||
try {
|
||||
const coverFile = await getVideoFirstFrame(newUrl);
|
||||
const coverRes = await uploading(coverFile, "cover.jpg", "image");
|
||||
const coverUrl = `${coverRes.url}?aid=${coverRes.aid}`;
|
||||
|
||||
setTimeout(() => {
|
||||
const editorContent = editor.getContent();
|
||||
// Replace src and add poster
|
||||
// This regex might need to be more robust
|
||||
if (editorContent.includes(src)) {
|
||||
let newContent = editorContent.replace(src, newUrl);
|
||||
// Find the video tag and inject poster if not present, or replace
|
||||
// Simple string replace for src is safe, but for poster we need DOM manipulation or regex
|
||||
// Let's use DOM for precision
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = newContent;
|
||||
const tempVideo = tempDiv.querySelector(`video[src*="${res.data.url}"]`); // Use partial match or ID
|
||||
if (tempVideo) {
|
||||
tempVideo.setAttribute("poster", coverUrl);
|
||||
editor.setContent(tempDiv.innerHTML);
|
||||
} else {
|
||||
// Fallback: regex replace the tag
|
||||
// This is tricky without unique ID.
|
||||
// Let's just setContent with simple replace first, then update poster via NodeChange logic or DOM
|
||||
editor.setContent(newContent);
|
||||
// We can trigger a NodeChange or find it again
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
console.error("Cover generation failed", e);
|
||||
// Update video without cover
|
||||
setTimeout(() => {
|
||||
const editorContent = editor.getContent();
|
||||
if (editorContent.includes(src)) {
|
||||
const newContent = editorContent.replace(src, newUrl);
|
||||
editor.setContent(newContent);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to upload network video:", src, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for NodeChange to catch inserted images/videos that might be external
|
||||
editor.on("NodeChange", async (e) => {
|
||||
const node = e.element;
|
||||
if (!uConfigData || !uConfigData.url) return;
|
||||
|
||||
// Handle Image
|
||||
if (node.tagName === "IMG" && !node.getAttribute("data-mce-object")) {
|
||||
// Check if already processed
|
||||
if (processedNodes.has(node)) return;
|
||||
|
||||
const src = node.getAttribute("src");
|
||||
|
||||
// Check local or invalid
|
||||
if (!src || src.startsWith("data:") || src.includes("?aid=") || src.startsWith(location.origin) || src.startsWith("/")) {
|
||||
processedNodes.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (src.startsWith("http")) {
|
||||
// Mark as processed
|
||||
processedNodes.add(node);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", src);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
const res = await ajax(uConfigData.url, formData);
|
||||
if (res.code == 200 && res.data) {
|
||||
const newUrl = `${res.data.url}?aid=${res.data.aid}`;
|
||||
editor.dom.setAttrib(node, "src", newUrl);
|
||||
editor.dom.setAttrib(node, "data-mce-src", newUrl);
|
||||
// Remove from processedNodes? No, because now src has ?aid= so it will be ignored anyway.
|
||||
// And if we undo, node is new.
|
||||
} else {
|
||||
console.warn("Upload failed for", src);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to upload network image on NodeChange:", src, err);
|
||||
// Do NOT remove from processedNodes to avoid retry loop on persistent error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Video
|
||||
// TinyMCE 6 with media_live_embeds: true uses <video> or <span class="mce-object-video">
|
||||
if (node.tagName === "VIDEO" || (node.tagName === "IMG" && node.getAttribute("data-mce-object") === "video")) {
|
||||
// Check if already processed
|
||||
if (processedNodes.has(node)) return;
|
||||
|
||||
let src = node.getAttribute("src");
|
||||
// If it's a placeholder img, the src usually points to a transparent pixel, real src is in data-mce-p-src
|
||||
if (node.tagName === "IMG") {
|
||||
src = node.getAttribute("data-mce-p-src") || src;
|
||||
}
|
||||
|
||||
// Check local or invalid
|
||||
if (!src || src.startsWith("data:") || src.includes("?aid=") || src.startsWith(location.origin) || src.startsWith("/")) {
|
||||
processedNodes.add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (src.startsWith("http")) {
|
||||
// Mark as processed
|
||||
processedNodes.add(node);
|
||||
|
||||
try {
|
||||
creationAlertBox("info", "Uploading network video...");
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", src);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
const res = await ajax(uConfigData.url, formData);
|
||||
if (res.code == 200 && res.data) {
|
||||
const newUrl = `${res.data.url}?aid=${res.data.aid}`;
|
||||
|
||||
// Update URL first
|
||||
if (node.tagName === "VIDEO") {
|
||||
editor.dom.setAttrib(node, "src", newUrl);
|
||||
editor.dom.setAttrib(node, "data-mce-src", newUrl);
|
||||
} else {
|
||||
editor.dom.setAttrib(node, "data-mce-p-src", newUrl);
|
||||
}
|
||||
|
||||
// Generate and set poster
|
||||
try {
|
||||
const coverFile = await getVideoFirstFrame(newUrl);
|
||||
const coverRes = await uploading(coverFile, "cover.jpg", "image");
|
||||
const coverUrl = `${coverRes.url}?aid=${coverRes.aid}`;
|
||||
|
||||
// Instead of just setting attributes, replace the entire node with a proper video tag
|
||||
// This ensures consistency and proper rendering in TinyMCE
|
||||
const videoHtml = `<video src="${newUrl}" poster="${coverUrl}" controls="controls" width="300" height="150"></video>`;
|
||||
editor.selection.select(node);
|
||||
editor.insertContent(videoHtml);
|
||||
|
||||
// Mark the new video node as processed
|
||||
// We need to find the newly inserted video node
|
||||
// insertContent might leave the cursor after the video.
|
||||
// A simple way is to query by src again, but that might be ambiguous.
|
||||
// However, since we just inserted it, it should be safe.
|
||||
setTimeout(() => {
|
||||
const insertedVideo = editor.dom.select(`video[src="${newUrl}"]`)[0];
|
||||
if (insertedVideo) {
|
||||
processedNodes.add(insertedVideo);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
creationAlertBox("success", "Video auto-uploaded with cover");
|
||||
} catch (e) {
|
||||
console.error("Auto cover generation failed", e);
|
||||
// Even if cover fails, we should update the src properly if it was a placeholder
|
||||
if (node.tagName === "IMG") {
|
||||
const videoHtml = `<video src="${newUrl}" controls="controls" width="300" height="150"></video>`;
|
||||
editor.selection.select(node);
|
||||
editor.insertContent(videoHtml);
|
||||
setTimeout(() => {
|
||||
const insertedVideo = editor.dom.select(`video[src="${newUrl}"]`)[0];
|
||||
if (insertedVideo) processedNodes.add(insertedVideo);
|
||||
}, 0);
|
||||
}
|
||||
creationAlertBox("success", "Video auto-uploaded (cover failed)");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to upload network video on NodeChange:", src, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
window.tinymce.init(editorConfig);
|
||||
};
|
||||
|
||||
const handleTitleInput = () => {
|
||||
saveStatus.value = "有未保存的更改";
|
||||
};
|
||||
@@ -309,9 +749,9 @@ const editApp = createApp({
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (editor == null) return;
|
||||
if (editor.destroy) editor.destroy();
|
||||
editor = null;
|
||||
if (window.tinymce && window.tinymce.activeEditor) {
|
||||
window.tinymce.activeEditor.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
1
js/tinymce/icons/default/icons.min.js
vendored
Normal file
1
js/tinymce/icons/default/icons.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/tinymce/langs/zh_CN.js
Normal file
1
js/tinymce/langs/zh_CN.js
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/models/dom/model.min.js
vendored
Normal file
4
js/tinymce/models/dom/model.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/autolink/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/autolink/plugin.min.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.8.2 (2023-12-11)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),w=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),h=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),w);if(!h)return null;let v=h.container;const _=k.backwards(h.container,h.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),w),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(h.container,h.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=4&&b.substr(0,4)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}();
|
||||
4
js/tinymce/plugins/charmap/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/charmap/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/code/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/code/plugin.min.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.8.2 (2023-12-11)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
|
||||
4
js/tinymce/plugins/codesample/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/codesample/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/directionality/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/directionality/plugin.min.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.8.2 (2023-12-11)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o<r;o++)e(t[o],o)},c=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),y=m(11),p=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),b=t=>d(t.dom.host),N=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return y(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=N,i=b,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||N(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r<n;r++){const n=t[r];e(n,r)&&o.push(n)}return o})(((t,e)=>{const o=t.length,r=new Array(o);for(let n=0;n<o;n++){const o=t[n];r[n]=e(o,n)}return r})(t.dom.childNodes,d),(t=>h(t,e))))(t),E=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const T=(t,e,n)=>{u(e,(e=>{const c=d(e),m=E(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),S(e)===n?p(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),S(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=A(f,"li[dir],li[style]");u(e,(e=>{p(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},C=(t,e)=>{t.selection.isEditable()&&(T(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},D=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(S(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}();
|
||||
2
js/tinymce/plugins/emoticons/js/emojis.min.js
vendored
Normal file
2
js/tinymce/plugins/emoticons/js/emojis.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/emoticons/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/emoticons/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/fullscreen/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/fullscreen/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/image/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/image/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/link/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/link/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/lists/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/lists/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/media/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/media/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/preview/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/preview/plugin.min.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.8.2 (2023-12-11)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>t=>t.options.get(e),i=n("content_style"),s=n("content_css_cors"),c=n("body_class"),r=n("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const n=(e=>{var n;let l="";const a=e.dom.encode,d=null!==(n=i(e))&&void 0!==n?n:"";l+='<base href="'+a(e.documentBaseURI.getURI())+'">';const m=s(e)?' crossorigin="anonymous"':"";o.each(e.contentCSS,(t=>{l+='<link type="text/css" rel="stylesheet" href="'+a(e.documentBaseURI.toAbsolute(t))+'"'+m+">"})),d&&(l+='<style type="text/css">'+d+"</style>");const y=r(e),u=c(e),v='<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A" && !('+(t.os.isMacOS()||t.os.isiOS()?"e.metaKey":"e.ctrlKey && !e.altKey")+")) {e.preventDefault();}}}, false);<\/script> ",p=e.getBody().dir,w=p?' dir="'+a(p)+'"':"";return"<!DOCTYPE html><html><head>"+l+'</head><body id="'+a(y)+'" class="mce-content-body '+a(u)+'"'+w+">"+e.getContent()+v+"</body></html>"})(e);e.windowManager.open({title:"Preview",size:"large",body:{type:"panel",items:[{name:"preview",type:"iframe",sandboxed:!0,transparent:!1}]},buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{preview:n}}).focus("close")})(e)}))})(e),(e=>{const t=()=>e.execCommand("mcePreview");e.ui.registry.addButton("preview",{icon:"preview",tooltip:"Preview",onAction:t}),e.ui.registry.addMenuItem("preview",{icon:"preview",text:"Preview",onAction:t})})(e)}))}();
|
||||
4
js/tinymce/plugins/searchreplace/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/searchreplace/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/table/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/table/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/template/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/template/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/visualblocks/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/visualblocks/plugin.min.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.8.2 (2023-12-11)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const s=(t,s,o)=>{t.dom.toggleClass(t.getBody(),"mce-visualblocks"),o.set(!o.get()),((t,s)=>{t.dispatch("VisualBlocks",{state:s})})(t,o.get())},o=("visualblocks_default_state",t=>t.options.get("visualblocks_default_state"));const e=(t,s)=>o=>{o.setActive(s.get());const e=t=>o.setActive(t.state);return t.on("VisualBlocks",e),()=>t.off("VisualBlocks",e)};t.add("visualblocks",((t,l)=>{(t=>{(0,t.options.register)("visualblocks_default_state",{processor:"boolean",default:!1})})(t);const a=(t=>{let s=!1;return{get:()=>s,set:t=>{s=t}}})();((t,o,e)=>{t.addCommand("mceVisualBlocks",(()=>{s(t,0,e)}))})(t,0,a),((t,s)=>{const o=()=>t.execCommand("mceVisualBlocks");t.ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:o,onSetup:e(t,s)}),t.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:o,onSetup:e(t,s)})})(t,a),((t,e,l)=>{t.on("PreviewFormats AfterPreviewFormats",(s=>{l.get()&&t.dom.toggleClass(t.getBody(),"mce-visualblocks","afterpreviewformats"===s.type)})),t.on("init",(()=>{o(t)&&s(t,0,l)}))})(t,0,a)}))}();
|
||||
4
js/tinymce/plugins/visualchars/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/visualchars/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/plugins/wordcount/plugin.min.js
vendored
Normal file
4
js/tinymce/plugins/wordcount/plugin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/tinymce/skins/content/default/content.min.css
vendored
Normal file
1
js/tinymce/skins/content/default/content.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
|
||||
1
js/tinymce/skins/ui/oxide/content.min.css
vendored
Normal file
1
js/tinymce/skins/ui/oxide/content.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/tinymce/skins/ui/oxide/skin.min.css
vendored
Normal file
1
js/tinymce/skins/ui/oxide/skin.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/themes/silver/theme.min.js
vendored
Normal file
4
js/tinymce/themes/silver/theme.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
js/tinymce/tinymce.min.js
vendored
Normal file
4
js/tinymce/tinymce.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,3 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
@@ -8,10 +7,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>发布主题</title>
|
||||
<link href="https://framework.x-php.com/gter/forum/css/normalize.min.css" rel="stylesheet">
|
||||
<link href="https://framework.x-php.com/gter/forum/css/editorStyle.css" rel="stylesheet">
|
||||
<!-- <link href="https://framework.x-php.com/gter/forum/css/editorStyle.css" rel="stylesheet"> -->
|
||||
<script src="https://framework.x-php.com/gter/forum/js/vue.global.js"></script>
|
||||
<link rel="stylesheet" href="/css/katex.min.css">
|
||||
<script src="/js/katex.min.js"></script>
|
||||
<script src="https://framework.x-php.com/gter/forum/js/tinymce/tinymce.min.js"></script>
|
||||
<link rel="stylesheet" href="katex/css/katex.min.css">
|
||||
<script src="katex/js/katex.min.js"></script>
|
||||
<!-- <link rel="stylesheet" href="/css/textbus.min.css"> -->
|
||||
<!-- <script src="/js/textbus.min.js"></script> -->
|
||||
|
||||
@@ -24,9 +24,20 @@
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#edit {
|
||||
height: calc(100% - 130px);
|
||||
}
|
||||
|
||||
#edit * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#top-container {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding-left: 30px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#editor-toolbar {
|
||||
@@ -51,11 +62,14 @@
|
||||
box-shadow: 0 2px 10px rgb(0 0 0 / 12%); */
|
||||
|
||||
width: 100vh;
|
||||
height: calc(100% - 40px);
|
||||
margin: 20px auto 20px auto;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#title-container {
|
||||
@@ -71,11 +85,11 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#editor-text-area {
|
||||
margin-top: 20px;
|
||||
/* #editor-text-area { */
|
||||
.tox.tox-tinymce {
|
||||
/* height: 500px; */
|
||||
/* max-height: 80vh; */
|
||||
height: calc(100vh - 370px);
|
||||
/* height: calc(100vh - 330px) !important; */
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
color: rgb(51, 51, 51);
|
||||
@@ -83,6 +97,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@@ -191,17 +207,17 @@
|
||||
.action-buttons .right-section .publish-btn:hover {
|
||||
background-color: #40d1aa;
|
||||
}
|
||||
|
||||
.tox-tinymce {
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="https://app.gter.net/bottom?tpl=header&menukey=bbs"></script>
|
||||
<div class="container" id="edit" v-cloak>
|
||||
<div id="top-container">
|
||||
<p>
|
||||
<a href="./"><< 返回</a>
|
||||
</p>
|
||||
</div>
|
||||
<a href="./" id="top-container"><< 返回</a>
|
||||
<div style="border-bottom: 1px solid #e8e8e8;">
|
||||
<div id="editor-toolbar"></div>
|
||||
</div>
|
||||
@@ -232,10 +248,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="https://framework.x-php.com/gter/forum/js/editor.js"></script>
|
||||
<script src="https://framework.x-php.com/gter/forum/js/axios.min.js"></script>
|
||||
<script src="https://framework.x-php.com/gter/forum/js/public.js"></script>
|
||||
<script type="module" src="https://framework.x-php.com/gter/forum/js/publish_admin.js"></script>
|
||||
<script src="/js/public.js"></script>
|
||||
<script type="module" src="/js/publish_admin.js"></script>
|
||||
<!-- <script src="https://framework.x-php.com/gter/forum/js/public.js"></script>
|
||||
<script type="module" src="https://framework.x-php.com/gter/forum/js/publish_admin.js"></script> -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user