(function() {
'use strict';
GM_addStyle(`
td.ms-vb2 {
max-width: 400px !important;
}
`);
GM_addStyle(`
.a_f_cb6f7c2e:not(.b_f_cb6f7c2e):not(.z_f_cb6f7c2e) {
margin: 0px 0 !important;
}
`);
GM_addStyle(`
.ms-list-TitleLink {
min-width: 400px !important;
}
`);
function convertToCSV(items) {
if (items.length === 0) return '';
const headers = Object.keys(items[0]);
let csv = headers.join(',') + '\r\n';
items.forEach(item => {
const row = headers.map(header => {
let value = item[header] !== undefined ? item[header] : '';
if (value === null || value === undefined || header === 'burnDownChart') {
value = '';
} else if (typeof value === 'object') {
value = JSON.stringify(value, null, 2);
} else {
value = String(value);
}
if (value.includes(',') || value.includes('\n') || value.includes('\r') || value.includes('"')) {
value = value.replace(/"/g, '""');
value = `"${value}"`;
}
return value;
});
csv += row.join(',') + '\r\n';
});
return csv;
}
function downloadCSV(csvData, filename = 'data.csv') {
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
(new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.name.includes("SP.UserProfiles.") || entry.name.includes("/users/")) {
const div = document.querySelector('div[data-automation-id="personaDetails"] div.peopleName');
if (div.innerText.indexOf(">") > 0) return;
let mail = entry.name.split("membership%7C")[1]?.split("%27")[0] || "";
if (mail === "") {
mail = entry.name.split("/users/")[1]?.split("?")[0] || ""
}
mail = decodeURIComponent(mail);
console.log("获取到作者信息: ", mail);
div.innerText += "<" + mail + ">";
return;
}
if (entry.name.includes("/SyncFlowInstances")) {
getWikiAuthor();
getRejectInfo();
}
});
})).observe({ entryTypes: ["resource"] });
function getUserInfo(id) {
const siteName = getSiteNameFromURL();
const apiUrl = `/sites/${siteName}/_api/web/GetUserById(${id})`;
const headers = {
"Accept": "application/json"
};
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: headers,
onload: function(response) {
const data = JSON.parse(response.responseText);
resolve(data);
},
onerror: function(error) {
reject(error);
}
});
});
}
function getWikiAuthor() {
const siteName = getSiteNameFromURL();
const apiUrl = `/sites/${siteName}/_api/web/getfilebyserverrelativeurl('${window.location.pathname}')/ListItemAllFields`
const headers = {
"Accept": "application/json"
};
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: headers,
onload: function(response) {
const data = JSON.parse(response.responseText);
const user = data.AuthorId;
getUserInfo(user).then(user => {
const element = document.querySelector('h1#pageTitle.ms-core-pageTitle');
if (element && user.LoginName && user.Email) {
const node = document.createElement('div');
element.appendChild(node);
node.outerHTML = `<div style="float: right;font-size:20px;position: relative;transform: translateY(50%);" title="${user.LoginName}">Author: ${user.Title}<${user.Email}></div>`;
}
}).catch(err => {
console.error("请求失败:", err);
});
const node = document.evaluate("//button[@data-automation-id='pageCommandBarStatus']//span[@class='ms-Button-label ms-ButtonShim-label']", document).iterateNext();
if (node) {
node.addEventListener('click', function() {
window.open(data.test_x0020_Approved?.Url, '_blank');
});
}
},
onerror: function(error) {
alert("请求失败!请稍后再试。");
}
});
}
function getRejectInfo() {
console.log("获取审批拒绝信息");
const labelElement = document.evaluate("//button[@data-automation-id='pageCommandBarStatus']//span[@class='ms-Button-label ms-ButtonShim-label' and text()='Rejected']", document).iterateNext();
if (!labelElement) {
console.log("未定位到 Reject 节点");
return;
}
let title = document.getElementById('title_text').innerText.trim().replaceAll("-", " ");
getRejectList().then(data => {
let firstMatch = data.find(item => item.Title.replaceAll("-", " ").includes(title));
if (!firstMatch) {
title = decodeURI(window.location.pathname.split('/').pop()).split(".")[0]?.replaceAll("-", " ");
firstMatch = data.find(item => item.Title.replaceAll("-", " ").includes(title));
}
if (firstMatch) {
labelElement.title = firstMatch.Body.replaceAll("<br>", "\n").replaceAll("<p>", "").replaceAll("</p>", "").replaceAll(":", ":");
} else {
debugger;
console.log("匹配审批拒绝信息失败");
}
});
}
function getRejectList() {
const siteName = getSiteNameFromURL();
const baseUrl = `/sites/${siteName}/_layouts/15/inplview.aspx?List={3F33E292-D971-4298-99D5-2DABF87EFFBC}&IsXslView=TRUE&IsCSR=TRUE&FilterField1=TaskOutcome&FilterValue1=Rejected`;
const headers = {
"Accept": "application/json"
};
return new Promise(async (resolve, reject) => {
try {
let allData = [];
let firstRow = 0;
let hasMoreData = true;
while (hasMoreData) {
const currentUrl = `${baseUrl}&FirstRow=${firstRow}`;
const response = await new Promise((innerResolve, innerReject) => {
GM_xmlhttpRequest({
method: "POST",
url: currentUrl,
headers: headers,
onload: function(response) {
innerResolve(response);
},
onerror: function(error) {
innerReject(error);
}
});
});
const data = JSON.parse(response.responseText);
if (data.Row && data.Row.length > 0) {
allData = allData.concat(data.Row);
}
if (!data.LastRow || (data.LastRow + 1) < firstRow + data.RowLimit) {
hasMoreData = false;
} else {
firstRow += data.RowLimit;
}
}
resolve(allData);
} catch (error) {
reject(error);
}
});
}
function getArticleList(query) {
const siteName = getSiteNameFromURL();
const baseUrl = `/sites/${siteName}/_api/web/GetListUsingPath(DecodedUrl='/sites/${siteName}/SitePages')/RenderListDataAsStream?InplaceSearchQuery=${query}`;
const headers = {
"Accept": "application/json"
};
return new Promise(async (resolve, reject) => {
try {
let allData = [];
let hasMoreData = true;
let currentUrl = `${baseUrl}&PageFirstRow=1`
while (hasMoreData) {
const response = await new Promise((innerResolve, innerReject) => {
GM_xmlhttpRequest({
method: "POST",
url: currentUrl,
headers: headers,
onload: function(response) {
innerResolve(response);
},
onerror: function(error) {
innerReject(error);
}
});
});
const data = JSON.parse(response.responseText);
if (data.Row && data.Row.length > 0) {
allData = allData.concat(data.Row);
}
if (data.NextHref) {
currentUrl = baseUrl + data.NextHref.replace("?", "&");
} else {
hasMoreData = false;
}
}
allData.sort((a, b) => new Date(a.Created) - new Date(b.Created));
resolve(allData);
} catch (error) {
reject(error);
}
});
}
window.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.key === 'q') {
event.preventDefault();
const keyword = prompt("请输入关键字进行搜索:");
if (keyword) {
const siteName = getSiteNameFromURL();
console.log("SharePoint站点名称: ", siteName);
if (siteName) {
searchUsers(siteName, keyword);
} else {
alert("无法从 URL 获取 SharePoint 站点名!");
}
}
}
});
function generateQueryForCurrentQuarter() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const quarter = Math.floor(month / 3);
const startMonth = quarter * 3;
const endMonth = startMonth + 2;
const startDate = new Date(year, startMonth, 1);
const endDate = new Date(year, endMonth + 1, 0);
const format = (d) => {
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
};
return `Created:${format(startDate)}..${format(endDate)} AND fileType: aspx`;
}
window.addEventListener('keydown', function(event) {
if (event.altKey && event.key === 'q') {
event.preventDefault();
const keyword = prompt("请输入关键字进行搜索:", generateQueryForCurrentQuarter());
if (keyword) {
getArticleList(keyword).then(data => {
console.log("获取到文章列表: ", data.length);
displayArticles(data);
});
}
}
});
function getSiteNameFromURL() {
let regex = /https:\/\/.*\.sharepoint\.(com|cn)\/sites\/(.*)\/(SitePages|_layouts|Lists|Shared%20Documents|SiteAssets|Pages)/;
let match = window.location.href.match(regex);
if (match) {
return match[2];
}
regex = /https:\/\/.*\.sharepoint\.(com|cn)\/sites\/([^\/?]+)/;
match = window.location.href.match(regex);
return match ? match[2] : null;
}
function copyTable(tableElement) {
const range = document.createRange();
const selection = window.getSelection();
selection.removeAllRanges();
range.selectNode(tableElement);
selection.addRange(range);
try {
document.execCommand('copy');
alert('表格已复制到剪切板!');
} catch (err) {
alert('复制失败:' + err);
}
selection.removeAllRanges();
}
function searchUsers(siteName, keyword) {
const apiUrl = `/sites/${siteName}/_api/Web/SiteUsers`;
const headers = {
"Accept": "application/json"
};
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: headers,
onload: function(response) {
const data = JSON.parse(response.responseText);
const users = data.value || [];
const regex = new RegExp(keyword, 'i');
const filteredUsers = users.filter(user => {
return regex.test(user.Title) || regex.test(user.Email) || regex.test(user.UserPrincipalName);
});
if (filteredUsers.length === 0) {
alert("没有找到匹配的用户。");
} else {
displayResults(siteName, filteredUsers);
}
},
onerror: function(error) {
alert("请求失败!请稍后再试。");
}
});
}
function displayArticles(articles) {
const container = document.createElement('div');
container.innerHTML = `
<div tabindex=0 style="max-width:90%;min-width:800px;width:80%;max-height:70%;position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); overflow:hidden; background-color:#fff; border:1px solid #ccc; border-radius:8px; box-shadow:0 0 10px rgba(0, 0, 0, 0.3); padding:10px; z-index: 9999; display:flex; flex-direction:column;}">
<div style="flex: 1; overflow-y: auto">
<style>
#_rl_article td { padding: 5px; border-bottom: 1px solid #ddd; }
#_rl_article a { text-decoration: none; }
</style>
<table id="_rl_article" style="width:100%; border-collapse: collapse; background-color: #fff; border: 1px solid #ddd; border-radius: 5px;">
<thead style="position: sticky; top: 0; background-color: #f8f8f8;">
<tr>
<th>文章</th>
<th width=120>作者</th>
<th width=120>编辑</th>
<th width=140>创建时间</th>
<th width=140>编辑时间</th>
<th width=50>点赞数</th>
<th width=250>提交备注</th>
<th width=80>工作流</th>
<th width=150>邮件通知</th>
</tr>
</thead>
<tbody>
${articles.map(item => `
<tr>
<td><a href="${item.FileRef}">${item.FileLeafRef}</a></td>
<td><a href="mailto:${item.Author[0]?.email}">${item.Author[0]?.title}</a></td>
<td><a href="mailto:${item.Editor[0]?.email}">${item.Editor[0]?.title}</a></td>
<td>${item.Created}</td>
<td>${item.Modified}</td>
<td>${item._LikeCount || ""}</td>
<td>${item._CheckinComment}</td>
<td><a href="${item.test_x0020_Approved}">${item._ModerationStatus}</a></td>
<td>${item.ApproveStatus || ""}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div style="padding-top:10px; display: flex; align-items: center; justify-content: flex-end">按 Esc 关闭
<div id="divCounter"></div>
<button id="copyButton" style="padding: 10px 10px; background-color: #3CB371; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;outline: none;">导出</button>
<button id="closeButton" style="padding: 10px 10px; background-color: #ff4d4f; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;outline: none;">关闭</button>
</div>
</div>`;
document.body.appendChild(container);
document.getElementById("divCounter").innerText = "总数量: " + articles.length + " 篇 ";
const copyButton = container.querySelector('#copyButton');
copyButton.addEventListener('click', function() {
const list = [];
articles.forEach(user => {
list.push({
文章: user.FileRef,
作者: user.Author[0]?.title,
作者邮箱: user.Author[0]?.email,
编辑: user.Editor[0]?.title,
编辑邮箱: user.Editor[0]?.email,
创建时间: user.Created,
创建时间: user.Modified,
点赞数: user._LikeCount || "",
提交备注: user._CheckinComment,
审批状态: user._ModerationStatus,
邮件通知: user.ApproveStatus || "",
});
});
const csvData = convertToCSV(list);
downloadCSV(csvData);
});
const closeButton = container.querySelector('#closeButton');
closeButton.addEventListener('click', function() {
document.body.removeChild(container);
});
container.addEventListener('keydown', function(event) {
if (event.key === 'Escape') { document.body.removeChild(container); };
});
closeButton.focus();
}
function displayResults(site, users) {
const container = document.createElement('div');
container.innerHTML = `
<div tabindex=0 style="max-width:90%; max-height:70%;position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); overflow:hidden; background-color:#fff; border:1px solid #ccc; border-radius:8px; box-shadow:0 0 10px rgba(0, 0, 0, 0.3); padding:10px; z-index: 9999; display:flex; flex-direction:column;}">
<div style="flex: 1; overflow-y: auto">
<style>
#_rl_table td { padding: 5px; border-bottom: 1px solid #ddd; }
#_rl_table a { text-decoration: none; }
</style>
<table id="_rl_table" style="width: 100%; border-collapse: collapse; background-color: #fff; border: 1px solid #ddd; border-radius: 5px;">
<thead style="position: sticky; top: 0; background-color: #f8f8f8;">
<tr>
<th>Title</th>
<th>Email</th>
<th>UserPrincipalName</th>
<th>Files</th>
<th>Wiki</th>
</tr>
</thead>
<tbody>
${users.map(user => `
<tr>
<td><a href="/sites/${site}/SitePages/Forms/AllPages.aspx?view=7&q=author:%20*&useFiltersInViewXml=0&viewpath=/sites/${site}/SitePages/Forms/AllPages.aspx&FilterField1=Editor&FilterValue1=${user.Title}&FilterField1=DocIcon&FilterValue1=aspx&FilterType1=Computed&FilterOp1=In&FilterField2=Author&FilterValue2=${user.Title}&FilterType2=User&FilterOp2=In" target="_blank">${user.Title}</a></td>
<td><a href="mailto:${user.Email}">${user.Email}</a></td>
<td><a href="${user['odata.id']}" target="_blank">${user.UserPrincipalName}</a></td>
<td><a href='/sites/${site}/_layouts/15/search.aspx/siteall?oobRefiners={"FileType":["pptx","docx","xlsx","one","pdf","video","html"]}&q=author:${user.Title}&scope=site' target="_blank">List</a></td>
<td><a href='/sites/DC3/Wiki/SitePages/Forms/AllPages.aspx?viewid=744ee345-265a-4d68-85da-89bc5f89b297&view=7&q=author:*&useFiltersInViewXml=0&FilterField1=Author&FilterValue1=${user.Title}&FilterType1=User&viewpath=/sites/DC3/Wiki/SitePages/Forms/AllPages.aspx&FilterField2=DocIcon&FilterValue2=aspx&FilterType2=Computed&FilterOp2=In' target="_blank">Wiki</a></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div style="padding-top:10px; display: flex; align-items: center; justify-content: flex-end">按 Esc 关闭
<button id="copyButton" style="padding: 10px 10px; background-color: #3CB371; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;outline: none;">复制表格</button>
<button id="closeButton" style="padding: 10px 10px; background-color: #ff4d4f; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;outline: none;">关闭</button>
</div>
</div>
`;
document.body.appendChild(container);
const copyButton = container.querySelector('#copyButton');
copyButton.addEventListener('click', function() {
copyTable(document.getElementById('_rl_table'));
});
const closeButton = container.querySelector('#closeButton');
closeButton.addEventListener('click', function() {
document.body.removeChild(container);
});
container.addEventListener('keydown', function(event) {
if (event.key === 'Escape') { document.body.removeChild(container); };
});
closeButton.focus();
}
})();
打开 sharepoint.com 或 sharepoint.cn 对应的网址,然后 按 Ctrl+Q,输入关键字,即可查询。点击用户名,可以打开新窗口,查看该用户创建的所有文章。