To open a record, select a node in the graph or a title in the index.
Cosmoscope
意识形态批判
意识形态是什么?
意识形态是一种无意识信仰或恋物癖
- 不是因为上帝存在,所以你才跪下,而是因为你跪下,所以上帝存在,当你跪下时,信仰就会不请自来。
- 意识形态不需要你相信,只需要你按规行事,其隐含的逻辑是 信仰的物质仪式比内心认同更难反思和摧毁
- 意识形态中处于我们的“行为”,而不只是“观念”这边他是外在于我们意识的社会客观结构
- 要改变无意识信仰或恋物癖(意识形态),在心理上改变是无效的,必须改变客观状态,改变替我们相信的他者
现代社会恋物癖
即商品恋物癖,人与人的关系由封建社会的主奴关系转移到物与物的关系之中(社会关系:生产关系、等级关系不再直接以统治和奴役的人际关系显现,占用更多商品即为主)
恋物癖是一种误认的结构效果,是一个符号网络的效果被误解为一个对象的直接属性
(例如国王与臣民是互相认同后国王和臣民的概念才成立的;商品A与商品B的价值是在等价与非等价的概念后才具有的。其都非自身的天然属性)
认识或者揭示恋物癖的本质无法对其造成影响
商品恋物癖的特别症状--劳动力商品(无产阶级),症状是无意识体系的断裂点,揭示了其平稳运作的矛盾。揭示了资本主义的崩溃点,劳动力商品通过出卖自己,颠覆了普遍自由的普适性,暴露了其等价交换的虚假性与排他性。
意识形态批判的目的是揭示这一症状,还必须在符号结构中对其进行符号阐释,实现符号重构,使劳动力商品(症状)转化成无产阶级(圣状),圣状即在符号秩序中丧失居所被迫颠覆符号秩序建构出新型的符号秩序。
幻象
幻象是意识形态的核心部分,决定了人们看待世界的方式
在幻象中欲望是被建构,幻象总是他者的异化,欲望总是他者的欲望建构的幻象,掩盖了大他者的不一致性(阶级矛盾)
穿越幻象是颠覆意识形态统治的途径,意识到幻象只是幻象并与其保持距离。
当今最大的幻象是资本主义生产力发展的宏大叙事.
逾越快感与受虐快感
剩余快感可表达为两种形式
一、逾越快感,即违反法则,偷偷享乐
二、受虐快感,即服从法则,在法则禁锢中享受快感
在当今强大的资本主义秩序中
"左翼"们(后现代主义、法兰克福学派、女性主义、后殖民主义等左派)
**自知无力抵抗,**无法构想出整体式的社会变革
只能以多元、包容、共存等无伤大雅的概念进行假意批判
却实则补充甚至发展了资本主义体制
因此,他们被称为傻瓜,在可允许的范围内反叛,享受逾越快感
右翼则"化痛苦为力量",将苦难当做命运的考验
下位者们努力改变自己,以适应不公的现实制度
上位者们劝人向"善",告知努力就会有回报
并且认为稳定胜于一切,哪怕采取不正当的镇压,也要誓死维护现有秩序
因此,他们被称为坏蛋,遵循法则、以苦为乐,享受受虐快感
总之,剩余快感就是"奴仆们"在侍奉"主子"的过程中领取的奖励
大他者
大他者:社会规则,文化秩序,神信仰,意识形态,权利常识,习惯
齐泽克呼吁,不要"为了大他者牺牲",而要"为了空无(未来)牺牲(革命)
一个人如何才能根本的与符号秩序决裂?
你必须放弃使你进入符号秩序的某种内在诱惑(对象 a )
因为它是你苟活于符号秩序中的幻象和欲望
只有当你宁可牺牲最宝贵的东西也绝不委曲求全,这才是决裂的真正姿态
齐泽克倡导“向自己最宝贵的东西开枪"的疯狂创举
例如,电影《普通嫌疑犯》中,当主人公发现妻子和女儿被暴徒以枪挟持,他采取了将妻女射死的激进姿态。这个行为使他能够残忍地追踪到这一帮派的所有成员,并将他们全部杀死。主人公彻底否决了作为匪徒的大他者以及它给予的求饶希望,从而开启了复仇之路。
再如,革命并不是为了某个社会理想_做出的牺牲,它的因果逻辑是回溯生成的,在革命时,革命者总是前途渺茫,他们脱离了原有的社会符号秩序,却还没有新的身份和秩序,这实际上已将革命等同于"为空无牺牲"了,为空无牺牲,是一种绝不妥协的绝对否定性,它意味着让我们去做"成为我们所不是"的激进行为,撕毁与大他者的约定,放弃符号委任的身份,置身于实在界的恐怖空无中。
天使 Seraphim
风在粗粝旷阔的砾石原野上鼓动着,宛如心脏的鼓点阵阵跳动,形单影只的旅人缠绕着灰白色的面巾看不清样貌,突兀的恍似孤石。即便是平地,他的一步仿佛用尽了所有的气力,只能在这心跳的间隙间缓缓地挪动。
天使降临了,旅人勉强地抬起头,看向前方徐徐展现的白光,白光中浮现了一只硕大的眼球,瞳孔是黑色的没有一丝亮色,粗壮的墨黑睫毛从眼球四周规律般的排布,并不弯曲螺旋状伸向外围,睫毛的尖端被一圈圈泛着银色金属光泽的椭圆圆盘围绕着、旋转着,以眼球与旅人的视线形成轴转动着。
神迹显现了,旅人从家乡疯癫乞丐听到的预言是真的,突如其来的雨摧毁了家乡,起初的细雨没人在意,虽然雨滴中夹杂着细碎的灰白绒毛,雨一直在下,一个星期过去了,虽然有些许奇怪,但村民们早已习惯了,撑着粗布制成的伞在雨中行走,渐渐地,人们发现水虽浸润土地并遁入不见,房顶道路田垄目光皆是,绒毛千织万絮覆盖着万物。
Hexo 星空背景和流星特效
注: 该效果仅在dark mode下生效。
- 在
[BlogRoot]/source/js
目录下新建universe.js
,输入以下代码:
function dark() {window.requestAnimationFrame\=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame;var n,e,i,h,t=.05,s=document.getElementById("universe"),o=!0,a="180,184,240",r="226,225,142",d="226,225,224",c=\[\];function f(){n=window.innerWidth,e=window.innerHeight,i=.216\*n,s.setAttribute("width",n),s.setAttribute("height",e)}function u(){h.clearRect(0,0,n,e);for(var t=c.length,i=0;i<t;i++){var s=c\[i\];s.move(),s.fadeIn(),s.fadeOut(),s.draw()}}function y(){this.reset\=function(){this.giant\=m(3),this.comet\=!this.giant&&!o&&m(10),this.x\=l(0,n-10),this.y\=l(0,e),this.r\=l(1.1,2.6),this.dx\=l(t,6\*t)+(this.comet+1\-1)\*t\*l(50,120)+2\*t,this.dy\=-l(t,6\*t)-(this.comet+1\-1)\*t\*l(50,120),this.fadingOut\=null,this.fadingIn\=!0,this.opacity\=0,this.opacityTresh\=l(.2,1\-.4\*(this.comet+1\-1)),this.do\=l(5e-4,.002)+.001\*(this.comet+1\-1)},this.fadeIn\=function(){this.fadingIn&&(this.fadingIn\=!(this.opacity\>this.opacityTresh),this.opacity+=this.do)},this.fadeOut\=function(){this.fadingOut&&(this.fadingOut\=!(this.opacity<0),this.opacity\-=this.do/2,(this.x\>n||this.y<0)&&(this.fadingOut\=!1,this.reset()))},this.draw\=function(){if(h.beginPath(),this.giant)h.fillStyle\="rgba("+a+","+this.opacity+")",h.arc(this.x,this.y,2,0,2\*Math.PI,!1);else if(this.comet){h.fillStyle\="rgba("+d+","+this.opacity+")",h.arc(this.x,this.y,1.5,0,2\*Math.PI,!1);for(var t=0;t<30;t++)h.fillStyle\="rgba("+d+","+(this.opacity\-this.opacity/20\*t)+")",h.rect(this.x\-this.dx/4\*t,this.y\-this.dy/4\*t-2,2,2),h.fill()}else h.fillStyle\="rgba("+r+","+this.opacity+")",h.rect(this.x,this.y,this.r,this.r);h.closePath(),h.fill()},this.move\=function(){this.x+=this.dx,this.y+=this.dy,!1\===this.fadingOut&&this.reset(),(this.x\>n-n/4||this.y<0)&&(this.fadingOut\=!0)},setTimeout(function(){o=!1},50)}function m(t){return Math.floor(1e3\*Math.random())+1<10\*t}function l(t,i){return Math.random()\*(i-t)+t}f(),window.addEventListener("resize",f,!1),function(){h=s.getContext("2d");for(var t=0;t<i;t++)c\[t\]=new y,c\[t\].reset();u()}(),function t(){document.getElementsByTagName('html')\[0\].getAttribute('data-theme')=='dark'&&u(),window.requestAnimationFrame(t)}()};
dark()
- 在
[BlogRoot]/source/css
目录下新建universe.css
,输入以下代码:
/* 背景宇宙星光 */
#universe{
display: block;
position: fixed;
margin: 0;
padding: 0;
border: 0;
outline: 0;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
/* 这个是调置顶的优先级的,-1在文章页下面,背景上面,个人推荐这种 */
z-index: -1;
}
- 在主题配置文件
_config.butterfly.yml
的inject
配置项下填入:
inject:
head:
- <link rel="stylesheet" href="/css/universe.css">
bottom:
- <canvas id="universe"></canvas>
- <script defer src="/js/universe.js"></script>
- 重新编译即可看到效果。
Hexo 音乐播放器 - aplayer
初步设置
- 在博客根目录
[BlogRoot]
下打开终端,运行以下指令:
npm install hexo-tag-aplayer --save
- 在网站配置文件
_config.yml
中修改aplayer
配置项为:
# 音乐插件
aplayer:
meting: true
asset_inject: false
- 在主题配置文件中修改
aplayerInject
配置项为:
# Inject the css and script (aplayer/meting)
aplayerInject:
enable: true
per_page: true
普通界面播放器
在博客的音乐页面(\source\music\index.md文件)添加如下:
{% meting "7422861869" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:400px" "preload:none" "theme:#ad7a86"%}
或者使用 HTML 格式
<div class="aplayer" data-id="000PeZCQ1i4XVs" data-server="tencent" data-type="artist" data-preload="auto" data-theme="#3F51B5"></div>
全局吸底模式
在主题配置文件中设置:
inject:
head:
bottom:
- <div class="aplayer no-destroy" data-id="7422861869" data-server="netease" data-type="playlist" data-fixed="true" data-autoplay="true" data-lrcType="-1"> </div>
几种不同的模式
普通列表模式
{% meting "7422861869" "netease" "playlist" "autoplay" "mutex:false" "listmaxheight:100px" "preload:none" "autoplay = false" "theme:#ad7a86"%}
单曲播放
<div class="aplayer no-destroy" data-id="1441758494" data-server="netease" data-type="song" data-autoplay="true" data-lrcType="-1"> </div>
迷你模式
<div class="aplayer no-destroy" data-id="1441758494" data-server="netease" data-type="song" data-mini="true" data-autoplay="true" data-lrcType="-1"> </div>
其他
参数
server
可选:netease
(网易云音乐),tencent
(QQ音乐),kugou
(酷狗音乐),xiami
(虾米音乐),baidu
(百度音乐)。建议网易云
type
可选:song
(歌曲),playlist
(歌单),album
(专辑),search
(搜索关键字),artist
(歌手)。添加单曲选的歌曲,歌单选择playlist,可以自行尝试。
id
获取示例: 打开网易云音乐,选择喜欢的歌单,在网页版打开,获取歌单list,填入即可。使用的时候将上边的ID号换为自己喜欢的歌单即可。==注意歌单中不能包括VIP音乐,否则无法解析==。建议单独建立一个歌单,以后有喜欢的音乐添加进去,网页也会自动同步添加。
lrcType
设置为 -1
默认显示歌词,放在fixed模式下比较合适。
换页不中断音乐
在主题配置文件中设置:
pjax:
enable: ture
exclude:
Hexo 文章加密
- 在根目录执行以下命令
npm install --save hexo-blog-encrypt
- Front matter配置方法
---
title: Hello World
tags:
- 作为日记加密
date: 2016-03-30 21:12:21
password: mikemessi
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
theme: xray
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
---
- 配置文件中针对tags的加密
# Security
encrypt: # hexo-blog-encrypt
abstract: 有东西被加密了, 请输入密码查看.
message: 您好, 这里需要密码.
tags:
- {name: tagName, password: 密码A}
- {name: tagName, password: 密码B}
theme: xray
wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试.
wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.
Hexo 自定义Page - 网格页面
效果展示:
- 创建pug页面文件
创建themes/butterfly/layout/includes/page/grid.pug
#grid
if site.data.grid
each i in site.data.grid
.grid-item
h2.grid-item-title= i.class_name
.grid-item-description= i.description
.grid-item-content
each item, index in i.grid_list
.grid-item-content-item
.grid-item-content-item-cover
img.grid-item-content-item-image(src=url_for(item.image) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt=item.name)
.grid-item-content-item-info
.grid-item-content-item-name= item.name
.grid-item-content-item-specification= item.specification
.grid-item-content-item-description= item.description
.grid-item-content-item-toolbar
if item.link.includes('https://') || item.link.includes('http://')
a.grid-item-content-item-link(href= item.link, target='_blank') 详情
else
a.grid-item-content-item-link(href= item.link, target='_blank') 查看文章
- 将文件植入butterfly
在themes/butterfly/layout/page.pug中添加grid.pug
when 'grid'
include includes/page/grid.pug
- 创建数据文件
创建source/_data/grid.yml
- class_name: 追番
description: ❤
grid_list:
- name: 深夜重拳
specification: 真夜中ぱんチ
description:
image: https://lain.bgm.tv/pic/cover/l/0c/cf/477207_0lA1U.jpg
link: https://www.bgm.tv/subject/477207
- name: 亚托莉 -我挚爱的时光-
specification: ATRI -My Dear Moments-
description: 「我想完成主人留下的最后一道命令。在那之前,我来做夏生的脚!」在逐渐沉入海中的安宁小镇,少年与机器人少女难以忘怀的夏天开始了——
image: https://lain.bgm.tv/pic/cover/l/66/6d/397604_TgJ63.jpg
link: https://bgm.tv/subject/397604
- class_name: 阅读
description: ❤
grid_list:
- name: 最后机会所在的城市 - 阿德里安·柴可夫斯基
specification: City of Last Chances
description: 伊尔玛一直都很黑暗,但从未像现在这样黑暗。这座城市在Palleseen占领的重拳之下,在犯罪黑社会的窒息之下,在工厂主的靴子之下,在可怜的穷人的重压之下,在古老诅咒的负担之下。点燃这场大火的火花是什么?尽管这座城市有难民、流浪者、杀人犯、疯子、狂热分子和小偷,但催化剂一如既往地将是锚木——那片黑暗的树林,那片原始的遗迹,那座满月时通往陌生遥远海岸的门户。
image: https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1655508932i/60147395.jpg
link: https://www.goodreads.com/book/show/60147395-city-of-last-chances
- name: 灵魂囚笼 - 阿德里安·柴可夫斯基
specification: Cage of Souls
description: 太阳膨胀了,生病了,也许快死了。在其危险的光线下,沙德拉帕,最后一个城市,拥有不到10万人的灵魂。Shadrapar建在无数文明的废墟上,在早已死去的祖先的废墟上幸存下来,是一个博物馆、一个避难所、一个监狱,在一个对人类来说越来越陌生的世界上。
image: https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1547654406i/40803025.jpg
link: https://www.goodreads.com/book/show/40803025-cage-of-souls
- 引入css代码
将以下css注入到主题配置文件中
.grid-item-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 -8px;
}
.grid-item-content-item {
width: calc(25% - 12px);
border-radius: 12px;
border: var(--style-border-always);
overflow: hidden;
margin: 8px 6px;
background: var(--heo-card-bg);
box-shadow: var(--heo-shadow-border);
min-height: 400px;
position: relative;
}
@media screen and (max-width: 1200px) {
.grid-item-content-item {
width: calc(50% - 12px);
}
}
@media screen and (max-width: 768px) {
.grid-item-content-item {
width: 100%;
}
}
.grid-item-content-item-info {
padding: 8px 16px 16px 16px;
margin-top: 12px;
}
.grid-item-content-item-name {
font-size: 18px;
font-weight: bold;
line-height: 1;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: fit-content;
}
.grid-item-content-item-specification {
font-size: 12px;
color: var(--heo-secondtext);
line-height: 1;
margin-bottom: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.grid-item-content-item-description {
line-height: 20px;
color: var(--heo-secondtext);
height: 60px;
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 14px;
}
a.grid-item-content-item-link {
font-size: 12px;
background: var(--heo-gray-op);
padding: 4px 8px;
border-radius: 8px;
cursor: pointer;
}
a.grid-item-content-item-link:hover {
background: var(--heo-main);
color: var(--heo-white);
}
h2.equipment-item-title {
line-height: 1;
}
.equipment-item-description {
line-height: 1;
margin: 4px 0 8px 0;
color: var(--heo-secondtext);
}
.grid-item-content-item-cover {
width: 100%;
height: 200px;
background: var(--heo-secondbg);
display: flex;
justify-content: center;
}
img.grid-item-content-item-image {
object-fit: cover;
height: 100%;
}
div#equipment {
margin-top: 26px;
}
.grid-item-content-item-toolbar {
display: flex;
justify-content: space-between;
position: absolute;
bottom: 12px;
left: 0;
width: 100%;
padding: 0 16px;
}
a.bber-reply {
cursor: pointer;
}
- 创建 Grid 页面
创建/source/grid/index.md
---
title: 近期所看
date: 2024-09-01 11:54:30
aside: false
type: grid
---
群聚算法
群聚算法的核心
- 群聚
- 对齐
- 分割
注:以GDScript为演示代码
extends CharacterBody2D
class_name Enemy
@onready var sprite = $Sprite2D
@onready var player:Node2D = get_tree().get_first_node_in_group("player")
@onready var speed = 200.0
# 转向微分
var steering
# 当前方向
var current_direction = Vector2.ZERO
# 目标方向
var desired_direction = Vector2.UP
# 目标速度
var desired_velocity = Vector2.ZERO
# 玩家方向
var player_direction = Vector2.ZERO
# 群聚点方向
var flock_direction = Vector2.ZERO
# 群聚间隔阻碍均方向
var aave = Vector2.ZERO
# 群聚均方向
var dave = Vector2.ZERO
# 群聚均位置
var pave = Vector2.ZERO
# 视野距离
var sight_distance = 200
# 间隔距离
var interval_distance = 30
# 视野范围因子,为视野范围的一半
var sight_range_factor = deg_to_rad(45)
# 避障方向
var avoid_obstacle_direction = Vector2.ZERO
# 避障距离
var avoid_obstacle_diatance := 50.0
# 避障因子
var avoid_obstacle_factor:float = 1.0
# 是否存在障碍
var has_obstacle := false
# 障碍位置
var obstacle_position := Vector2.ZERO
func _physics_process(delta):
player = get_tree().get_first_node_in_group("player")
player_direction = global_position.direction_to(player.global_position)
caculate_flock()
caculate_avoid_obstacle()
if has_obstacle:
avoid_obstacle_direction = (obstacle_position - global_position).normalized().orthogonal()
avoid_obstacle_factor = 200/(global_position.distance_to(obstacle_position))
desired_direction = avoid_obstacle_direction
else:
desired_direction = (player_direction * 0.4 + flock_direction * 0.3 + dave * 0.3 + aave * 0.8).normalized()
desired_velocity = desired_direction * speed
steering = (desired_velocity - velocity) * delta * 3 * avoid_obstacle_factor
velocity += steering
current_direction = velocity/speed
sprite.flip_h = current_direction.x > 0
move_and_slide()
queue_redraw()
# 群聚算法 (群聚、对齐、分隔)
func caculate_flock():
var enemies:Array = get_tree().get_nodes_in_group("enemy")
aave = Vector2.ZERO
dave = Vector2.ZERO
pave = Vector2.ZERO
if enemies.size() <= 1:return
for enemy in enemies:
var e:Enemy = enemy
var enemy_direction = global_position.direction_to(e.global_position)
if e.global_position.distance_to(global_position) <= sight_distance and current_direction.angle_to(enemy_direction) <= sight_range_factor:
dave += e.current_direction
pave += e.global_position
if e.global_position.distance_to(global_position) <= interval_distance:
aave += (-enemy_direction)
dave = dave.normalized()
pave = pave/enemies.size()
aave = aave.normalized()
flock_direction = global_position.direction_to(pave)
# 避障计算
func caculate_avoid_obstacle():
avoid_obstacle_direction = Vector2.ZERO
var space_state = get_world_2d().direct_space_state
var direction = current_direction
var query = PhysicsRayQueryParameters2D.create(global_position,global_position + (direction* sight_distance))
query.collide_with_areas = true
var result = space_state.intersect_ray(query)
if not result.is_empty() and result["collider"].is_in_group("tilemap"):
has_obstacle = true
obstacle_position = result["position"]
if has_obstacle and global_position.distance_to(obstacle_position) > avoid_obstacle_diatance:
has_obstacle = false
obstacle_position = Vector2.ZERO
avoid_obstacle_factor = 1.0
#func _draw():
# draw_line(Vector2.ZERO,current_direction * sight_distance,Color8(255, 0, 0) ,6)
LINQ 初步使用
查询表达式 query
链式表达式 chained
延迟执行与消耗
一个表达式只有当“消耗”时才会真正开始执行
消耗条件:
1.Dump()、ToDictionary()、ToList()、ToArray()
2.Count()、Min() Max() Sum()
3.遍历foreach
4.Take() First() Last()
LINQ并不是只针对可枚举类型的(IEnumerable)类型的扩展方法,可以用于:
- IEnumerable
- IOrderedEnumerable
- IQueryable
- ParallelQuery
- ...
选择列表中大于等于4的偶数
var res =
from n in list
where n % 2 = 0 && n >= 4
orderby n
select n;
# 链式表达式不需要以select结尾
var res = list.Where(n=>n%2=0 && n>=4).OrderBy(n=n);
求两个数组交集
var res = arr1.Intersect(arr2);
var res =
from n in arr1
where arr2.Contains(n)
select n;
分组
var res = arr.GrounpBy(x=>x).Select(g => new {g.Key, Count = g.Count()});// 返回一个匿名类的列表
var res = arr.GrounpBy(x=>x).ToDictionary(g => g.Key, g => g.Count()});// 返回一个数组
var res =
from x in arr
group x by x into g
select new {g.key, Count = g.Count()};
一些常用函数:
#多线程
var arr = Enumerable
.Range(1,10)
.AsParallel() //转成多线程
.AsOrdered() //排序
.Select(x => {
Thread.Sleep(500);
return x * x;
})
.AsSequential() //转回单线程
#展平
var res = mat.SelectMany(n => n).ToArry();
#笛卡尔积
var prods =
from i in Enumerable.Range(0,5)
from j in Enumerable.Range(0,5)
from k in Enumerable.Range(0,5)
select $"{i},{j},{k}";
统计字母频率
var words = new string[]{"tom", "jerry","spike","tyke"
,“butch”,“quacker”}
var query =
from w in words
from c in w
group c by c into g
select new {g.Key , Count = g.Count()} into a
orderby a.Count descending
select a;
var query = words
.SelectMany(c => c)
.GroupBy(c => c)
.Select(g => new {g.Key,Count = g.Count()})
.OrderByDescending(g => g.Count);
# 批量下载文件
var tasks = urls
.Select(url => DownloadAsync(url,url.Split('/').Last()));
var tasks =
from url in urls
let filename = url.Split('/').Last()
orderby filename
select DownloadAsync(url,filename);
await Task.WhenAll(tasks);
async Task DownloadAsync(string url,string filename)
{
await Task.Delay(1000);
$"{filename} download.".Dump();
}
# 寻找派生类
var types = Assembly
.GetAssembly(typeof(Exception))
.GetTypes()
.Cache();
types
.Where(t=>t.IsAssignableTo(typeof(Exception)))
.Select(t=>t.name)
.OrderBy(t=>t.Length)
新手常见错误
绕远路
- 不知道First() Last() Average()方法
- 不知道 Count() First() Min() Sum()等方法可以传参
- 不知道 Max() MaxBy() 区别
- 不知道各种 Default
滥用开销大
- 滥用 ToList ToDictionary等等
- 滥用 Count()
- 滥用OrderBy() [可以用Arry.Sort替代]
- 不知道 First() 和 Single() 区别
- First 拿到第一个
- Single 拿到唯一的一个(会进行遍历)
Hexo X Obsidian 组合
文章模板 - 快速创建Post
配置插件
安装第三方插件 Templater
创建 Templates
目录,修改配置指定 Template 的目录。修改配置项 Template folder location
为 Templates
然后再此目录下创建 Front-matter.md
文件,此文件用作 hexo 的 frontmatter 模板。
---
title: <% tp.file.title %>
categories:
- <% tp.file.folder(relative=true) %>
tags:
- ''
abbrlink: <% tp.user.get_url() %>
date: <% tp.date.now(format="YYYY-MM-DD HH:mm:ss") %>
cover: ''
---
其中:
模板 | 作用 |
---|---|
tp.file.title | 获取到的就是文件名 |
tp.file.folder(relative=true) | 是获取文件所在的相对路径,就是所在目录名字 |
tp.user.get_url() | 是自定义方法,脚本后面展示,用于自动生成博客的 url |
tp.date.now(format=”YYYY-MM-DD HH:mm:ss”) | 以指定格式格式化时间 |
详细的变量使用请查看 Templater
官方文档https://silentvoid13.github.io/Templater/
自定义脚本
创建目录 Scripts
, 然后在设置里配置 Script files folder location
为 Scripts
获取 GUID 脚本 Scripts/get_url.js
这里是取出当前时间戳,然后去掉毫秒,再按照 0-9
+ a-z
一共 36 个字符来表示,得到一个 6 位不重复的字符串,用作文章的链接。
function generateTimestampUrl() {
var timestamp = Math.round(new Date() / 1000);
var url = timestamp.toString(36)
return url;
}
module.exports = generateTimestampUrl;
然后每次创建新 markdown 文件的时候,只需要点击 templater 按钮, 然后就会自动生成我们需要的 frontmatter.
隐藏 Templates 和 Scripts 目录
安装 Hidden Folder 插件,可以隐藏这两个目录。
安装Obsidian文字处理插件
Pangu
Linter
参考:
构建 Obsidian 的 Hexo 写作工作流 | 半方池水半方田 (uuanqin.top)
Hexo 博客适配 Obsidian 新语法 | 半方池水半方田 (uuanqin.top)
NPBehave Introduction
本文侧重点在 NPBehave 的代码逻辑而非使用。
前言
基础介绍
NPBahave 是 GitHub 上开源的一个行为树,其代码简洁有力,与 Unity 耦合较低,适合拿来做双端行为树。
meniku/NPBehave: Event Driven Behavior Trees for Unity 3D (github.com)
NPBehave 基于功能强大且灵活的基于代码的方法,从 behavior 库定义行为树,并混合了虚幻引擎的一些很棒的行为树概念。与传统的行为树不同,事件驱动的行为树不需要每帧从根节点遍历。它们保持当前状态,只有在实际需要时才继续遍历。这使得它们的性能更高,使用起来也更简单。
使用案例
案例一:Hello World
代码会一直输出"Hello World"
private Root behaviorTree;
behaviorTree = new Root(
new Action(() => Debug.Log("Hello World!"))
);
behaviorTree.Start();
案例二:阻止结点执行
使用 WaitUntilStopped 可以控制结点执行完向父级告知是否停止
// 序列Composite
behaviorTree = new Root(
new Sequence(
new Action(() => Debug.Log("Hello World!")),
new WaitUntilStopped()
)
);
behaviorTree.Start();
运行逻辑
重要模块功能注释
时钟 Clock
时钟用于连续或者间断执行函数。
注:Observer 即一个委托 Action
内部:
Timer
属性
timerPool
:Timer 列表
updateObservers
: 每帧执行的 Observer 列表
timers
:单帧执行的 Observers 字典(由 Timer 决定), Observer-Timer 的字典
addTimers
:缓存的待添加 Timer,字典,Observer-Timer
removeTimers
:缓存的待移除 Timer,HashSet
addObservers
:缓存的待添加 Observer,HashSet
removeObserver
:缓存的待移除 Observer,HashSet
isInUpdate
:是否在执行 Observer,用于控制 Timer 与 Observer 缓存的增删
elapsedTime
:该 Clock 执行函数(调用 Update)经过的时间
方法
AddTimer
:将一个 Observer 添加到 timers/addTimers、从 removeTimers 中移除(由 isInUpdate 决定),并赋予 Timer 传参
RemoveTimer
:将一个 Observer 从 timers/addTimers 中移除、添加到 removeTimers 中(由 isInUpdate 决定)
HasTimer
:返回是否有指定的 Observer 存在于 timers/addTimers/ !removeTimers
AddUpdateObserver
:将一个 Observer 添加到 updateObservers/addObservers、从 removeObservers 中移除(由 isInUpdate 决定)
RemoveObservers
:将一个 Observer 从 Observers/addObservers 中移除、添加到 removeObservers 中(由 isInUpdate 决定)
HasUpdateObservers
:返回是否有指定的 Observer 存在于 updateObservers/addObservers/!removeObservers
Update
:核心函数 1. 执行 updateObservers 中的委托(不存在于 removeObservers) 2. 执行 timers 中的委托(不存在于 removeObservers) 3. 使 timer/observer 的 add/remove 缓存生效,后清空缓存
- getTimerPool:返回分配的 Timer,如果 timePool 有未 used 的 Timer 则返回此 Timer,无则创建新 Timer 加入 timerPool 并返回
黑版 Blackboard
黑板是一个树状图存储数据的数据结构。
内部
Type
Notification
:通知
属性
clock
isNotiflying
parentBlackboard
: 父黑板
children
: 子黑板哈希集
data
: string-object 数据字典
observers
:string-ActionList 委托列表字典
addObservers
:string-Action 待添加的委托字典
removeObservers
:string-Action 待移除的委托字典
notifcations
:通知列表
notificationsDispatch
:待分发通知列表
方法
Enable
:将自身添加入父黑板的子黑板(父黑板需存在)
Disable
:将自身从父黑板的子黑板移除(父黑板需存在),调用 clock 的 RemoveTimer 将自身的 NotifiyObservers 函数移除。
Set
:赋值传入键值对至 data。 添加 Add/Change Notification 至 notifications, 调用 clock 的 AddTimers 传入 并调用自身的 NotifiyObservers 函数,并调用父黑板的 Set 方法。
Unset
:从 data 中移除传入键,添加 Remove Notification 至 notifications。, 调用 clock 的 AddTimers 传入 并调用自身的 NotifiyObservers 函数。
Get
:返回传入键在自身 data 映射的值,如果自身 data 无该键则调用父黑板的 Get 方法
Isset
:返回传入键是否存在于自身 data 或父黑板 data 中
AddObserver
:调用 getObserverList 获取传入键的委托列表,将一个 Observer 添加到 observers/addObservers、从 removeObservers 中移除(由 isNotiflying 决定)
RemoveObserver
:调用 getObserverList 获取传入键的委托列表,将一个 Observer 从 observers/addObservers 中移除、添加到 removeObservers 中(由 isNotiflying 决定)
NotiflyObservers
: 1. 清空 notificationsDispatch;将 notifications 添加至自身与之黑板的 notificationsDispatch;调用子黑板 clock 的 AddTimer 方法传入子黑板的 NotifiyObservers 方法;清空 notifications 2. 将 notificationsDispatch 的每个 notification 传入相同键的 Observer 作为参数进行调用 3. 使 add/remove Observer 缓存生效,后清空缓存
getObserverList
:返回从传入的委托列表字典中获取传入键对应的值,如无则创建再返回
结点 Node
内部
State
:枚举,表示 Node 状态(INACTIVE/ACTIVE/STOP_REQUESTED)
属性
CurrentState
:State,表示该 Node 状态,默认为 INACTIVE
RootNode
:Root,根结点
ParentNode
:Container,父结点
Label
:string
Name
:string
Blackboard
Clock
方法
IsStopRequested
:返回自身 CurrentState 是否为 STOP_REQUESTED
IsActive
:返回自身 CurrentState 是否为 ACTIVE
SetRoot
SetParent
Start
:设置 CurrntState 为 ACTIVE,调用 DoStart 方法
Stop
:设置 CurrntState 为 STOP_REQUESTED,调用 DoStop 方法
ParentCompositeStopped
:调用 DoParentCompositeStopped 方法
DoStart
:空方法
DoStop
:空方法
DoParentCompositeStopped
:空方法
Stopped
:设置 CurrntState 为 INACTIVE,调用 ParentNode 的 ChildStopped(this,true)*
ToString
:返回 Name+{Label}的拼接字符串
GetPath
:返回父路径+“/"+Name
容器 Container : Node
属性
Collapse
:bool,容器是否损坏
方法
ChildStopped
:传参调用 DoChildStopped 方法
DoChildStopped
装饰器 Decorator : Container
属性
Decoratee
:Node,被装饰结点
方法
SetRoot
ParentCompositeStopped
:调用基类、被装饰结点该方法
根结点 Root : Decorator
属性
mainNode
:Node
Blackboard
Clock
方法
DoStart
: Enable Blackboard,Start mainNode
DoStop
:当 mainNode 是激活时调用其 Stop(),反之从 Clock 中移除其 Start 方法委托
DoChildStop(NodeA,boolA)
:如果自身处于告知停止状态则 Disable Blackboard 并 Stopped(boolA),反之将 mainNode.Start 方法传入 Clock.AddTimer
运行逻辑解释
案例一:输出“Hello World”解释
private Root behaviorTree;
behaviorTree = new Root(
new Action(() => Debug.Log("Hello World!"))
);
//-> mainNode = Action
behaviorTree.Start();
/*
-> Root.DoStart->Action.DoStart
{
Debug.Log("Hello World!")
Action(Node).Stopped(success=true)
}
->Action.ParentNode(Root).ChildStopped(Node=Action,success=true)
->Root.DoChildStopped(Node=Action,success=true) if !IsStopRequested ->
{
this.clock.AddTimer(0, 0, this.mainNode.Start) //再次调用Action.Start
}
如上图流程所示,执行会进入一个调用Action.Start的循环,结果是一直输出"Hello World"
*/
总结:当 Action 结点执行完毕后会向父级结点传递一个子结点执行成功并终止的结果,Root 收到该结果会再次执行子结点。
案例二:Sequence、WaitUntilStopped 解释
// 序列Composite
behaviorTree = new Root(
new Sequence(
new Action(() => Debug.Log("Hello World!")),
new WaitUntilStopped()
)
);
/* {
mainNode = Sequence
Sequence.Chidren = [Action,WaitUntilStopped]
}*/
behaviorTree.Start();
/*
->Sequence.DoStart->Sequence.ProcessChildren
->Action.Start
{
Debug.Log("Hello World!")
Action(Node).Stopped(success=true)
}
->Action.ParentNode(Sequence).ChildStoped->Sequence.DoChildStop(success=true)
->ProcessChildren if result=true
->WaitUntilStopped(Node).Start->Node.DoStart{}
//此时因为Node的Start方法为空,所以暂停执行
*/
总结:使用 Sequence 结点可以按传入结点列表的顺序执行结点,当运行 WaitUntilStopped 结点时因为其未方法为空所以暂停执行了。
结点类型汇总
Root
- Root(Node mainNode):无休止地运行 mainNode,不论任何情况
- Root(Blackboard Blackboard, Node mainNode):使用给定的黑板,而不是实例化一个;无休止地运行给定的 mainNode,不论任何情况
- Root(Blackboard blackboard, Clock clock, Node mainNode):使用给定的黑板而不是实例化一个;使用给定的时钟,而不是使用 UnityContext 中的全局时钟;无休止地运行给定的 mainNode,不论任何情况
组合结点
Selector
- Selector(params Node[] children):按顺序运行子元素,直到其中一个子元素成功(如果其中一个子元素成功,则成功)。
Sequence
- Sequence(params Node[] children):按顺序运行子节点,直到其中一个失败(如果所有子节点都没有失败,则成功)。
Parallel
- Parallel(Policy successPolicy, Policy failurePolicy, params Node[] children): 并行运行子节点。
- 当 failurePolocity 为 Polociy.ONE。当其中一个孩子失败时,并行就会停止,返回失败。
- 当 successPolicy 为 Policy.ONE。当其中一个孩子失败时,并行将停止,返回成功。
- 如果并行没有因为 Policy.ONE 而停止。它会一直执行,直到所有的子节点都完成,然后如果所有的子节点都成功或者失败,它就会返回成功。
RandomSelector
- RandomSelector(params Node[] children):按随机顺序运行子进程,直到其中一个子进程成功(如果其中一个子进程成功,则成功)。注意,对于打断规则,最初的顺序定义了优先级。
RandomSequence
- RandomSequence(params Node[] children):以随机顺序运行子节点,直到其中一个失败(如果没有子节点失败,则成功)。注意,对于打断规则,最初的顺序定义了优先级。
任务结点
Action
- Action(System.Action action):(总是立即成功完成)
- Action(System.Func singleFrameFunc): 可以成功或失败的操作(返回 false to fail)
- Action(Func<bool, Result> multiframeFunc):可以在多个帧上执行的操作(
Result.BLOCKED——你的行动还没有准备好
Result.PROGRESS——当你忙着这个行为的时候,
Result.SUCCESS 或 Result.FAILED——成功或失败)。 - Action(Func<Request, Result> multiframeFunc2): 与上面类似,但是 Request 会给你一个状态信息:
Request.START 表示它是您的操作或返回结果的第一个标记或者是 Result.BLOCKED 最后一个标记。
Request.UPDATE 表示您最后一次返回 Request.PROGRESS;
Request.CANCEL 意味着您需要取消操作并返回结果。成功或者 Result.FAILED。
Wait
- Wait(float seconds): 等待给定的秒,随机误差为 0.05 *秒
- Wait(float seconds, float randomVariance): 用给定的随机变量等待给定的秒数
- Wait(string blackboardKey, float randomVariance = 0f):
- Wait(System.Func function, float randomVariance = 0f): 等待在给定的 blackboardKey 中设置为 float 的秒数
WaitUntilStopped
- WaitUntilStopped(bool sucessWhenStopped = false):等待被其他节点停止。它通常用于 Selector 的末尾,等待任何 before 头的同级 BlackboardCondition、BlackboardQuery 或 Condition 变为活动状态。
装饰器结点
BlackboardCondition
- BlackboardCondition(string key, Operator operator, object value, Stops stopsOnChange, Node decoratee): 只有当黑板的键匹配 op / value 条件时,才执行 decoratee 节点。如果 stopsOnChange 不是 NONE,则节点将根据 stopsOnChange stop 规则观察黑板上的变化并停止运行节点的执行。
- BlackboardCondition(string key, Operator operator, Stops stopsOnChange, Node decoratee): 只有当黑板的键与 op 条件匹配时才执行 decoratee 节点(例如,对于一个只检查 IS_SET 的操作数操作符)。如果 stopsOnChange 不是 NONE,则节点将根据 stopsOnChange stop 规则观察黑板上的变化并停止运行节点的执行。
BlackboardQuery
- BlackboardQuery(string[] keys, Stops stopsOnChange, System.Func query, Node decoratee):BlackboardCondition 只允许检查一个键,而这个将观察多个黑板键,并在其中一个值发生变化时立即计算给定的查询函数,从而允许您在黑板上执行任意查询。它将根据 stopsOnChange stop 规则停止运行节点。
Condition
- Condition(Func condition, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点
- Condition(Func condition, Stops stopsOnChange, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点。根据 stopsOnChange stop 规则重新评估每个帧的条件并停止运行节点。
- Condition(Func condition, Stops stopsOnChange, float checkInterval, float randomVariance, Node decoratee): 如果给定条件返回 true,则执行 decoratee 节点。在给定的校验间隔和随机方差处重新评估条件,并根据 stopsOnChange stop 规则停止运行节点。
Cooldown
- Cooldown(float cooldownTime, Node decoratee):立即运行 decoratee,但前提是最后一次执行至少没有超过 cooldownTime
- Cooldown(float cooldownTime, float randomVariation, Node decoratee): 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime
- Cooldown(float cooldownTime, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee): 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime,当 resetOnFailure 为真时,如果修饰节点失败,则重置冷却时间
- Cooldown(float cooldownTime, float randomVariation, bool startAfterDecoratee, bool resetOnFailiure, Node decoratee) 立即运行 decoratee,但前提是最后一次执行至少没有超过使用 randomVariation 进行的 cooldownTime,当 startAfterDecoratee 为 true 时,将在 decoratee 完成后而不是启动时启动冷却计时器。当 resetOnFailure 为真时,如果修饰节点失败,则重置冷却时间。
Failer
- Failer(Node decoratee): 总是失败,不管装饰者的结果如何。
Inverter
- Inverter(Node decoratee): 如果 decoratee 成功,则逆变器失败;如果 decoratee 失败,则逆变器成功。
Observer
- Observer(Action onStart, Action onStop, Node decoratee): 一旦 decoratee 启动,运行给定的 onStart lambda;一旦 decoratee 结束,运行 onStop(bool result) lambda。它有点像一种特殊的服务,因为它不会直接干扰 decoratee 的执行。
Random
- Random(float probability, Node decoratee): 以给定的概率,0 到 1 运行 decoratee。
Repeater
- Repeater(Node decoratee): 无限重复给定的装饰,除非失败
- Repeater(int loopCount, Node decoratee): 执行给定的 decoratee 循环次数(0 表示 decoratee 永远不会运行)。如果 decoratee 停止,循环将中止,并且中继器失败。如果 decoratee 的所有执行都成功,那么中继器将会成功。
Service
- Service(Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后每次运行服务。
- Service(float interval, Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后按给定的间隔运行服务。
- Service(float interval, float randomVariation, Action service, Node decoratee): 运行给定的服务函数,启动 decoratee,然后在给定的时间间隔内以随机变量的方式运行服务。
Succeeder
- Succeeder(Node decoratee): 永远要成功,不管装饰器是否成功
TimeMax
- TimeMax(float limit, bool waitForChildButFailOnLimitReached, Node decoratee): 运行给定的 decoratee。如果 decoratee 没有在限制时间内完成,则执行将失败。如果 waitforchildbutfailonlimitarrived 为 true,它将等待 decoratee 完成,但仍然失败。
- TimeMax(float limit, float randomVariation, bool waitForChildButFailOnLimitReached, Node decoratee):运行给定的 decoratee。如果 decoratee 没有在限制和随机变化范围内完成,则执行将失败。如果 waitforchildbutfailonlimitarrived 为 true,它将等待 decoratee 完成,但仍然失败。
TimeMin
- TimeMin(float limit, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到限制时间之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果被装饰者在达到限制时间之前失败,装饰者将立即停止。
- TimeMin(float limit, bool waitOnFailure, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到限制时间之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果 waitOnFailure 为真,那么当 decoratee 失败时,decoratee 也将等待。
- TimeMin(float limit, float randomVariation, bool waitOnFailure, Node decoratee): 运行给定的 decoratee。如果 decoratee 在达到随机变化时间限制之前成功完成,decorator 将等待直到达到限制,然后根据 decoratee 的结果停止执行。如果 waitOnFailure 为真,那么当 decoratee 失败时,decoratee 也将等待。
WaitForCondition
- WaitForCondition(Func condition, Node decoratee): 延迟 decoratee 节点的执行,直到条件为真,检查每一帧
- WaitForCondition(Func condition, float checkInterval, float randomVariance, Node decoratee): 延迟 decoratee 节点的执行,直到条件为真,使用给定的 checkInterval 和 randomVariance 进行检查
扩展内容
Hexo 自定义Page - 音乐页面
主题配置文件中配置CDN.option.meting.js
// 配置CDN或者是本地引用
meting_js: https://npm.elemecdn.com/[email protected]/assets/js/Meting2.min.js
创建 source/music/index.md
``` md
title: 音乐馆
date: 2024-09-04 22:40:30
type: music
aplayer: true
top_img: false
comments: false
aside: false
### 创建 `themes/butterfly/layout/includes/page/music.pug`
// id:歌单 server:音乐服务提供者
#anMusic-page
meting-js(id="1111111" server="netease" type="playlist" mutex="true" preload="auto" theme="var(--furo-main)" order="list")
### 修改 `themes/butterfly/layout/page.pug`
``` diff
#page
- if top_img === false
+ if top_img === false && page.type != 'music'
h1.page-title= page.title
case page.type
when 'link'
include includes/page/flink.pug
...
+ when 'music'
+ include includes/page/music.pug
default
include includes/page/default-page.pug
修改themes/butterfly/layout/includes/layout.pug
body
if theme.background
#web_bg
+ #an_music_bg
创建引用 js css
var furo = {
//设置音乐背景为固定
setDefaultMusigBg:function(){
const anMusicBg = document.getElementById("an_music_bg");
anMusicBg.style.backgroundImage = "url(/img/background.jpg)";
console.info(anMusicBg);
},
// 设置背景为音乐背景
changeMusicBg: function (isChangeBg = true) {
if (window.location.pathname != "/music/") {
return;
}
const anMusicBg = document.getElementById("an_music_bg");
if (isChangeBg) {
// player listswitch 会进入此处
const musiccover = document.querySelector("#anMusic-page .aplayer-pic");
anMusicBg.style.backgroundImage = musiccover.style.backgroundImage;
} else {
// 第一次进入,绑定事件,改背景
let timer = setInterval(() => {
const musiccover = document.querySelector("#anMusic-page .aplayer-pic");
// 确保player加载完成
console.info(anMusicBg);
if (musiccover) {
clearInterval(timer);
anMusicBg.style.backgroundImage = musiccover.style.backgroundImage;
// 绑定事件
furo.addEventListenerChangeMusicBg();
// 暂停nav的音乐
if (
document.querySelector("#nav-music meting-js").aplayer &&
!document.querySelector("#nav-music meting-js").aplayer.audio.paused
) {
furo.musicToggle();
}
}
}, 100);
}
},
addEventListenerChangeMusicBg: function () {
const anMusicPage = document.getElementById("anMusic-page");
const aplayerIconMenu = anMusicPage.querySelector(
".aplayer-info .aplayer-time .aplayer-icon-menu"
);
anMusicPage
.querySelector("meting-js")
.aplayer.on("loadeddata", function () {
furo.changeMusicBg();
console.info("player loadeddata");
});
aplayerIconMenu.addEventListener("click", function () {
document.getElementById("menu-mask").style.display = "block";
document.getElementById("menu-mask").style.animation =
"0.5s ease 0s 1 normal none running to_show";
});
document.getElementById("menu-mask").addEventListener("click", function () {
if (window.location.pathname != "/music/") return;
anMusicPage
.querySelector(".aplayer-list")
.classList.remove("aplayer-list-hide");
});
},
};
// 调用
// furo.changeMusicBg(false);
furo.setDefaultMusigBg();
:root {
--music-list-title-color: #fff
}
#page:has(#anMusic-page) {
border: 0;
box-shadow: none !important;
padding: 0 !important;
background: transparent !important;
}
#an_music_bg {
display: none;
/* filter: blur(100px); */
opacity: 0.4;
position: fixed;
z-index: -999;
background-attachment: local;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
width: 100%;
height: 100%;
/* top: -50%;
left: -50%; */
transform: rotate(0deg);
}
body:has(#anMusic-page) #an_music_bg {
display: block;
}
body:has(#anMusic-page) {
background: rgb(13, 13, 13);
}
#anMusic-page meting-js .aplayer {
display: flex;
flex-direction: row-reverse;
background: rgba(0, 0, 0, 0);
border: none;
box-shadow: none;
}
body:has(#anMusic-page) #web_bg {
display: none;
}
body:has(#anMusic-page) #footer,
body:has(#anMusic-page) #nav-music {
display: none;
}
#anMusic-page .aplayer-body {
width: 40%;
height: 75vh;
}
#anMusic-page ol>li:hover {
background: #ffffff33;
border-radius: 6px;
}
#anMusic-page .aplayer-pic {
float: none;
width: 180px;
height: 180px;
border-radius: 12px;
margin: auto;
left: 0;
right: 0;
}
#anMusic-page .aplayer-info {
margin: 0 20px 0 20px;
border-bottom: none;
}
#anMusic-page .aplayer-info .aplayer-music {
text-align: center;
height: auto;
margin: 15px;
}
#anMusic-page .aplayer-info .aplayer-music .aplayer-author,
#anMusic-page .aplayer-info .aplayer-music .aplayer-title {
font-size: 2rem;
font-weight: 700;
color: #ffffff;
}
#anMusic-page .aplayer-info .aplayer-lrc {
height: 800%;
margin-top: 80px;
mask-image: linear-gradient(to bottom,
#000,
#000,
#000,
#000,
#000,
#000,
#000,
#000,
#000,
#000,
#0000,
#0000);
}
#anMusic-page .aplayer-info .aplayer-lrc p {
font-size: 14px;
color: #ffffff;
}
#anMusic-page .aplayer .aplayer-lrc:after,
#anMusic-page .aplayer .aplayer-lrc:before {
display: none;
}
/* 控制器 */
#anMusic-page .aplayer-info .aplayer-controller {
position: fixed;
max-width: 1500px;
margin: auto;
left: 0;
right: 0;
bottom: 50px;
}
#anMusic-page .aplayer-info .aplayer-controller .aplayer-bar-wrap {
margin: 0 160px 0 150px;
}
#anMusic-page .aplayer-info .aplayer-controller .aplayer-played {
background: var(--music-list-title-color) !important;
}
#anMusic-page .aplayer-info .aplayer-controller .aplayer-thumb {
-webkit-transform: none;
transform: none;
background: #fff !important;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back,
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward,
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
display: inline;
position: fixed;
}
#anMusic-page .aplayer-info .aplayer-time {
position: absolute;
width: 100%;
bottom: 21px;
height: 0;
display: flex;
justify-content: flex-end;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner {
margin-right: 18px;
margin-top: -8px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back {
position: absolute;
left: 0;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
position: absolute;
left: 40px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward {
position: absolute;
left: 80px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon {
width: 2rem;
height: 2rem;
margin-left: 15px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-menu {
display: none;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon path {
fill: var(--music-list-title-color);
opacity: 0.8;
}
#anMusic-page .aplayer-list {
width: 60%;
max-height: none !important;
height: 100%;
}
#anMusic-page ol {
max-height: 75vh !important;
padding-right: 25px;
}
#anMusic-page ol>li {
border-top: 1px solid transparent;
font-size: 14px;
}
#anMusic-page ol>li.aplayer-list-light {
background: rgb(255 255 255 / 20%);
border-radius: 6px;
}
#anMusic-page ol>li span {
color: var(--music-list-title-color);
}
#anMusic-page ol>li.aplayer-list-light .aplayer-list-cur {
display: none;
}
#anMusic-page ol>li span.aplayer-list-author {
opacity: 0.6;
}
/* 导航栏 */
.page:has(#anMusic-page) #nav {
backdrop-filter: none !important;
background: 0 0 !important;
border-bottom: none !important;
}
.page:has(#anMusic-page) #page-header.not-top-img #nav a,
.page:has(#anMusic-page) #page-header #nav .back-home-button {
color: var(--music-list-title-color);
}
body:has(#anMusic-page) .s-sticker div {
color: var(--music-list-title-color) !important;
}
body:has(#anMusic-page) .aplayer .aplayer-info .aplayer-controller .aplayer-time .aplayer-icon.aplayer-icon-loop {
margin-right: 15px;
}
[data-theme="dark"] .page:has(#anMusic-page) #page-header:before {
background-color: transparent;
}
/* **** 移动端样式 ***** */
@media screen and (max-width: 768px) {
body:has(#anMusic-page) #rightside {
display: none;
}
body:has(#anMusic-page) #content-inner,
body:has(#anMusic-page) #page {
z-index: auto;
}
/* 歌曲列表 */
#anMusic-page .aplayer-list {
position: fixed;
z-index: 1002;
width: 100%;
bottom: -76%;
left: 0;
background: var(--sidebar-bg);
height: auto;
border-radius: 15px 15px 0px 0px;
padding: 15px 0px;
}
#anMusic-page .aplayer-list.aplayer-list-hide {
bottom: 0% !important;
}
#anMusic-page ol {
max-height: 60vh !important;
padding-right: 0px;
}
#anMusic-page ol>li {
display: flex;
margin: 0 10px;
}
#anMusic-page ol>li span {
color: var(--font-color);
}
#anMusic-page ol>li span.aplayer-list-title {
width: 30%;
}
#anMusic-page ol>li.aplayer-list-light {
background: #33a673;
padding: 5px 20px;
border-radius: 10px;
}
#anMusic-page ol>li.aplayer-list-light span {
color: #fff;
}
#anMusic-page ol>li span.aplayer-list-title {
max-width: 55%;
width: auto;
display: -webkit-box;
-webkit-line-clamp: 1;
overflow: hidden;
-webkit-box-orient: vertical;
}
#anMusic-page ol>li span.aplayer-list-author {
position: absolute;
right: 10px;
width: auto;
max-width: 35%;
display: -webkit-box;
-webkit-line-clamp: 1;
overflow: hidden;
-webkit-box-orient: vertical;
}
#anMusic-page ol>li.aplayer-list-light span.aplayer-list-author {
right: 15px;
}
/* 歌词信息 */
#anMusic-page .aplayer-body {
width: 100%;
position: fixed;
margin: auto;
left: 0;
right: 0;
top: 100px;
}
#anMusic-page .aplayer-info .aplayer-lrc {
margin-top: 40px;
height: auto;
max-height: 200%;
min-height: 100%;
mask-image: linear-gradient(to bottom,
#000,
#000,
#000,
#000,
#0000,
#0000);
}
#anMusic-page .aplayer-info .aplayer-lrc p.aplayer-lrc-current {
color: #33a673;
}
/* 控制按键和进度条 */
#anMusic-page .aplayer-info .aplayer-controller {
width: 100%;
bottom: 100px;
}
#anMusic-page .aplayer-info .aplayer-controller .aplayer-bar-wrap {
margin: 0 30px;
}
#anMusic-page .aplayer-info .aplayer-time {
bottom: -40px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner {
position: absolute;
width: 100%;
margin-right: 0;
margin-top: -33px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner .aplayer-dtime {
position: absolute;
right: 30px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-time-inner .aplayer-ptime {
position: absolute;
left: 35px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-back {
margin: auto;
right: 110px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-play {
margin: auto;
right: 0;
left: 0;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-forward {
margin: auto;
left: 110px;
right: 0;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-order {
position: absolute;
left: 22px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-loop {
position: absolute;
right: 25px;
}
#anMusic-page .aplayer-info .aplayer-time .aplayer-icon-menu {
display: inline;
position: absolute;
right: 25px;
top: -90px;
}
#anMusic-page .aplayer-volume-bar-wrap {
bottom: 0px;
right: 7px;
}
#anMusic-page .aplayer .aplayer-info .aplayer-controller .aplayer-volume-wrap {
left: -66px;
}
}
Hexo X Obsidian 实现关系图谱页面
前期准备
安装 hexo-filter-titlebased-link 插件:解决文章双向链接渲染
npm install hexo-filter-titlebased-link --save
准备 obsidian2cosma 脚本:转换 Obsidian 文章为 Cosma 可识别的文章
下载此脚本重命名为obsidian2cosma.py
,存放在根目录my_scripts
文件夹。
uuanqin/obsidian2cosma: Convert Obsidian vaults to collection of notes readable by Cosma and Zettlr (github.com)
因为本站文章的 abbrlink 非 int 类型而是 string 类型,该脚本不支持因而进行了些修改:
// 155 行处
- return int("0x" + hex_abbr, 16)
+ return hex_abbr
安装 Cosma:将文章生成关系图谱网站
Github:graphlab-fr/cosma: Cosma is a document graph visualization tool. It modelizes interlinked Markdown files and renders them as an interactive network in a web interface. (github.com)
Officials Webstie:Cosma | Installing Cosma (arthurperret.fr)
npm install @graphlab-fr/cosma -g
修改 Cosma 源码:为部署进行适配
修改默认cosma配置文件
修改源码中的core\models\config.js
- 解决需要手动填写
cosma c
命令生成config.yml
中files_origin
的问题 - 调整cosma配置文件修改样式
static base = Object.freeze({
select_origin: 'directory',
files_origin: './', //源文件路径
nodes_origin: '',
links_origin: '',
nodes_online: '',
links_online: '',
images_origin: '',
export_target: '',
history: true,
focus_max: 2,
record_types: { undefined: { fill: '#E6E7E7', stroke: '#858585' } }, //结点颜色
link_types: { undefined: { stroke: 'simple', color: '#e1e1e1' } },
references_as_nodes: false,
references_type_label: 'references',
record_filters: [],
graph_background_color: '#ffffff',
graph_highlight_color: '#ff6a6a',
graph_highlight_on_hover: true,
graph_text_size: 10,
graph_arrows: true,
node_size_method: 'degree',
node_size: 10,
node_size_max: 20,
node_size_min: 2,
attraction_force: 200,
attraction_distance_max: 250,
attraction_vertical: 0,
attraction_horizontal: 0,
views: {},
record_metas: [],
generate_id: 'always',
link_context: 'tooltip',
hide_id_from_record_header: false,
title: '',
author: '',
description: '',
keywords: [],
link_symbol: '',
csl: '',
bibliography: '',
csl_locale: '',
css_custom: '',
devtools: false,
lang: 'en',
});
替换点击关系图谱中结点为跳转为文章页面
修改源码中的`core\static\bundle.js
- ("data-node",(function(t){return t.id})).append("a").attr("href",(function(t){return"#"+t.id})
+ ("data-node",(function(t){return t.id})).append("a").attr("href",(function(t){return"/posts/"+t.id+".html"})
(废弃)修剪页面只显示画布
该方法已废弃,新实现通过hexo的custom_css完成
修改源码中的 core\static\styles\styles.css
+ aside,main,.close-button,#graph-controls {
+ display: none;
+ }
操作部署
生成关系图谱页面
- 在根目录创建
\source\DO_NOT_RENDER\cosmoscope\
文件夹存在 - 将
\source\DO_NOT_RENDER
路径添加到_config.yml
的skip_render
参考:Configuration | Hexoskip_render: "source/DO_NOT_RENDER/**"
- 在根目录
package.json
中scripts
项里添加以下:
"scripts": {
"ob2co": "python my_scripts/obsidian2cosma.py -i source/_posts -o my_scripts/cosma_dir/data --ignore -f --method abbrlink --attrreplacement categories,types date,begin --verbose",
"cosma-data": "cd my_scripts/cosma_dir/data && cosma c && cosma m",
"cosma-copy": "copy \".\\my_scripts\\cosma_dir\\data\\cosmoscope.html\" \".\\themes\\butterfly\\layout\\includes\\page\\Html\\cosmoscope.html\" ",
"cosma-rm-output": "echo \"删除转换后的输出文件 - Cosma\" && rmdir /s /q .\\my_scripts\\cosma_dir\\data\\",
"gen-cosma": "npm run ob2co && npm run cosma-data && npm run cosma-copy && npm run cosma-rm-output"
},
- 脚本中
cosma-copy
将生成的html文件移动到了主题内文件夹,可以自定义移动路径 - 使用
npm run gen-cosma
生成关系图谱页面,每次更新改关系图谱页面时需手动执行该命令
配置到网页页面
参考 自定义cosmoscope页面
配置cosmoscope.pug
:
#cosmoscope-page
include Html/cosmoscope.html
配置source/cosmoscope/index.md
使用custom_css对页面样式进行修改,主要是对除canvas进行裁剪:
/* Cosmoscope 文章星图页面 */
body:has(#cosmoscope-page) {
display: block !important;
}
#content-inner:has(#cosmoscope-page) {
display: block!important;
max-width:none;
padding: 0 !important;
border: 0;
}
#content-inner:has(#cosmoscope-page) > #page {
background: none;
}
.graph-wrapper {
background: rgba(0,0,0,0) !important;
}
body:has(#cosmoscope-page) aside,#footer,main,.close-button,#graph-controls {
display: none;
}
(可选)配置到网站导航
在主题配置文件中添加以下配置:
menu:
文图: /cosmoscope || fas fa-arrow-right-arrow-left
(可选)配置到网站侧边栏
参考:自定義側邊欄 | Butterfly
创建`source/_data/widget.yml
- class_name: seamless-card
id_name:
name:
icon:
order:
html: |
<div style="height: 250px">
<iframe src=\".\\themes\\butterfly\\layout\\includes\\page\\Html\\cosmoscope.html\"frameborder="no" style="width: 100%; height: 100%"></iframe>
</div>
记录拓展
存在问题
- 当文章的文件名与其meta.title不一致时,无法生成正确的连接。
- cosmoscope.html中的部分文段在执行命令时会报错(
YAMLException: end of the stream or a document separator is expected (24:26)
),无影响待解决。
后续改进
暂无
参考文章
Butterfly 侧边栏实现 Obsidian 关系图谱 | 半方池水半方田 (uuanqin.top)
Hexo 博客适配 Obsidian 新语法 | 半方池水半方田 (uuanqin.top)
LimboAI Introduction
Help
Click here to access Cosma's documentation
Shortcuts
Space | Re-run the force-layout algorithm |
S | Move the cursor to Search |
Alt + click | (on a record type) Deselect other types |
R | Reset zoom |
Alt + R | Reset the display |
C | Zoom in on the selected node |
F | Switch to Focus mode |
Escape | Close the active record |
Version 2.4.1 • License GPL-3.0-or-later
- Arthur Perret
- Guillaume Brioudes
- Olivier Le Deuff
- Clément Borel
- ANR research programme HyperOtlet
- D3 v4.13.0
- Mike Bostock (BSD 3-Clause)
- Nunjucks v3.2.3
- James Long (BSD 2-Clause)
- Js-yaml v4.1.0
- Vitaly Puzrin (MIT License)
- Markdown-it v12.3.0
- Vitaly Puzrin, Alex Kocharin (MIT License)
- Citeproc v2.4.62
- Frank Bennett (CPAL, AGPL)
- Fuse-js v6.4.6
- Kiro Risk (Apache License 2.0)